Merge "Change SharedConnectivityService's permission check to use checkCallingOrSelfPermission" into udc-dev
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 11c13c4..f6c18d6 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -1447,7 +1447,6 @@
* reasonable estimates should use the sentinel value
* {@link JobInfo#NETWORK_BYTES_UNKNOWN}.
* </ul>
- * TODO(255371817): update documentation to reflect how this data will be used
* Note that the system may choose to delay jobs with large network
* usage estimates when the device has a poor network connection, in
* order to save battery and possible network costs.
@@ -1478,6 +1477,7 @@
* @see JobInfo#getEstimatedNetworkUploadBytes()
* @see JobWorkItem#JobWorkItem(android.content.Intent, long, long)
*/
+ // TODO(b/255371817): update documentation to reflect how this data will be used
public Builder setEstimatedNetworkBytes(@BytesLong long downloadBytes,
@BytesLong long uploadBytes) {
mNetworkDownloadBytes = downloadBytes;
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
index 242b52c..32502ed 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
@@ -19,6 +19,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.app.ActivityManager;
import android.app.usage.UsageStatsManager;
import android.compat.annotation.UnsupportedAppUsage;
@@ -102,6 +103,7 @@
* The user stopped the job via some UI (eg. Task Manager).
* @hide
*/
+ @TestApi
public static final int INTERNAL_STOP_REASON_USER_UI_STOP =
JobProtoEnums.INTERNAL_STOP_REASON_USER_UI_STOP; // 11.
/**
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java b/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java
index ce5ade5..5a12142 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java
@@ -21,6 +21,7 @@
import android.annotation.NonNull;
import android.app.Notification;
+import android.app.job.JobParameters;
import android.app.job.JobService;
import android.content.pm.UserPackage;
import android.os.UserHandle;
@@ -80,7 +81,7 @@
final NotificationDetails oldDetails = mNotificationDetails.get(hostingContext);
if (oldDetails != null && oldDetails.notificationId != notificationId) {
// App is switching notification IDs. Remove association with the old one.
- removeNotificationAssociation(hostingContext);
+ removeNotificationAssociation(hostingContext, JobParameters.STOP_REASON_UNDEFINED);
}
final int userId = UserHandle.getUserId(callingUid);
// TODO(260848384): ensure apps can't cancel the notification for user-initiated job
@@ -100,7 +101,8 @@
mNotificationDetails.put(hostingContext, details);
}
- void removeNotificationAssociation(@NonNull JobServiceContext hostingContext) {
+ void removeNotificationAssociation(@NonNull JobServiceContext hostingContext,
+ @JobParameters.StopReason int stopReason) {
final NotificationDetails details = mNotificationDetails.remove(hostingContext);
if (details == null) {
return;
@@ -114,7 +116,10 @@
ArraySet<JobServiceContext> associatedContexts = associations.get(details.notificationId);
if (associatedContexts == null || associatedContexts.isEmpty()) {
// No more jobs using this notification. Apply the final job stop policy.
- if (details.jobEndNotificationPolicy == JOB_END_NOTIFICATION_POLICY_REMOVE) {
+ // If the user attempted to stop the job/app, then always remove the notification
+ // so the user doesn't get confused about the app state.
+ if (details.jobEndNotificationPolicy == JOB_END_NOTIFICATION_POLICY_REMOVE
+ || stopReason == JobParameters.STOP_REASON_USER) {
final String packageName = details.userPackage.packageName;
mNotificationManagerInternal.cancelNotification(
packageName, packageName, details.appUid, details.appPid, /* tag */ null,
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 4088a48..bf3789f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1476,7 +1476,8 @@
/* timingDelayConstraintSatisfied */ false,
/* isDeviceIdle */ false,
/* hasConnectivityConstraintSatisfied */ false,
- /* hasContentTriggerConstraintSatisfied */ false);
+ /* hasContentTriggerConstraintSatisfied */ false,
+ 0);
// If the job is immediately ready to run, then we can just immediately
// put it in the pending list and try to schedule it. This is especially
@@ -1894,7 +1895,8 @@
cancelled.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY),
cancelled.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE),
cancelled.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY),
- cancelled.isConstraintSatisfied(JobStatus.CONSTRAINT_CONTENT_TRIGGER));
+ cancelled.isConstraintSatisfied(JobStatus.CONSTRAINT_CONTENT_TRIGGER),
+ 0);
}
// If this is a replacement, bring in the new version of the job
if (incomingJob != null) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
index 1d0fdd9..4357d4f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -670,6 +670,7 @@
+ " [PACKAGE] [JOB_ID]");
pw.println(" Trigger immediate timeout of currently executing jobs, as if their");
pw.println(" execution timeout had expired.");
+ pw.println(" This is the equivalent of calling `stop -s 3 -i 3`.");
pw.println(" Options:");
pw.println(" -u or --user: specify which user's job is to be run; the default is");
pw.println(" all users");
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index ceb47ea..29ab455 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -461,7 +461,8 @@
job.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY),
job.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE),
job.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY),
- job.isConstraintSatisfied(JobStatus.CONSTRAINT_CONTENT_TRIGGER));
+ job.isConstraintSatisfied(JobStatus.CONSTRAINT_CONTENT_TRIGGER),
+ mExecutionStartTimeElapsed - job.enqueueTime);
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
// Use the context's ID to distinguish traces since there'll only be one job
// running per context.
@@ -1356,7 +1357,8 @@
completedJob.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY),
completedJob.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE),
completedJob.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY),
- completedJob.isConstraintSatisfied(JobStatus.CONSTRAINT_CONTENT_TRIGGER));
+ completedJob.isConstraintSatisfied(JobStatus.CONSTRAINT_CONTENT_TRIGGER),
+ 0);
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler",
getId());
@@ -1373,7 +1375,7 @@
JobSchedulerEconomicPolicy.ACTION_JOB_TIMEOUT,
String.valueOf(mRunningJob.getJobId()));
}
- mNotificationCoordinator.removeNotificationAssociation(this);
+ mNotificationCoordinator.removeNotificationAssociation(this, reschedulingStopReason);
if (mWakeLock != null) {
mWakeLock.release();
}
diff --git a/core/api/current.txt b/core/api/current.txt
index 2f1d81d..2161186 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -5279,8 +5279,8 @@
}
public class BroadcastOptions {
+ ctor public BroadcastOptions();
method public boolean isShareIdentityEnabled();
- method @NonNull public static android.app.BroadcastOptions makeBasic();
method @NonNull public android.app.BroadcastOptions setShareIdentityEnabled(boolean);
method @NonNull public android.os.Bundle toBundle();
}
@@ -13577,17 +13577,26 @@
}
public final class CreateCredentialRequest implements android.os.Parcelable {
- ctor public CreateCredentialRequest(@NonNull String, @NonNull android.os.Bundle, @NonNull android.os.Bundle, boolean, boolean);
method public boolean alwaysSendAppInfoToProvider();
method public int describeContents();
method @NonNull public android.os.Bundle getCandidateQueryData();
method @NonNull public android.os.Bundle getCredentialData();
+ method @Nullable public String getOrigin();
method @NonNull public String getType();
method public boolean isSystemProviderRequired();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.credentials.CreateCredentialRequest> CREATOR;
}
+ public static final class CreateCredentialRequest.Builder {
+ ctor public CreateCredentialRequest.Builder(@NonNull android.os.Bundle, @NonNull android.os.Bundle);
+ method @NonNull public android.credentials.CreateCredentialRequest build();
+ method @NonNull public android.credentials.CreateCredentialRequest.Builder setAlwaysSendAppInfoToProvider(boolean);
+ method @NonNull public android.credentials.CreateCredentialRequest.Builder setIsSystemProviderRequired(boolean);
+ method @NonNull @RequiresPermission(android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN) public android.credentials.CreateCredentialRequest.Builder setOrigin(@NonNull String);
+ method @NonNull public android.credentials.CreateCredentialRequest.Builder setType(@NonNull String);
+ }
+
public final class CreateCredentialResponse implements android.os.Parcelable {
ctor public CreateCredentialResponse(@NonNull android.os.Bundle);
method public int describeContents();
@@ -13619,9 +13628,7 @@
public final class CredentialManager {
method public void clearCredentialState(@NonNull android.credentials.ClearCredentialStateRequest, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.credentials.ClearCredentialStateException>);
method public void createCredential(@NonNull android.credentials.CreateCredentialRequest, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.CreateCredentialResponse,android.credentials.CreateCredentialException>);
- method @RequiresPermission(android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN) public void createCredentialWithOrigin(@NonNull android.credentials.CreateCredentialRequest, @Nullable String, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.CreateCredentialResponse,android.credentials.CreateCredentialException>);
method public void getCredential(@NonNull android.credentials.GetCredentialRequest, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>);
- method @RequiresPermission(android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN) public void getCredentialWithOrigin(@NonNull android.credentials.GetCredentialRequest, @Nullable String, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>);
method public boolean isEnabledCredentialProviderService(@NonNull android.content.ComponentName);
method public void registerCredentialDescription(@NonNull android.credentials.RegisterCredentialDescriptionRequest);
method public void unregisterCredentialDescription(@NonNull android.credentials.UnregisterCredentialDescriptionRequest);
@@ -13656,6 +13663,7 @@
method public int describeContents();
method @NonNull public java.util.List<android.credentials.CredentialOption> getCredentialOptions();
method @NonNull public android.os.Bundle getData();
+ method @Nullable public String getOrigin();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.credentials.GetCredentialRequest> CREATOR;
}
@@ -13666,6 +13674,7 @@
method @NonNull public android.credentials.GetCredentialRequest build();
method @NonNull public android.credentials.GetCredentialRequest.Builder setAlwaysSendAppInfoToProvider(boolean);
method @NonNull public android.credentials.GetCredentialRequest.Builder setCredentialOptions(@NonNull java.util.List<android.credentials.CredentialOption>);
+ method @NonNull @RequiresPermission(android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN) public android.credentials.GetCredentialRequest.Builder setOrigin(@NonNull String);
}
public final class GetCredentialResponse implements android.os.Parcelable {
@@ -19361,7 +19370,7 @@
public final class OutputConfiguration implements android.os.Parcelable {
ctor public OutputConfiguration(@NonNull android.view.Surface);
ctor public OutputConfiguration(int, @NonNull android.view.Surface);
- ctor public OutputConfiguration(@NonNull android.util.Size, @NonNull Class<T>);
+ ctor public <T> OutputConfiguration(@NonNull android.util.Size, @NonNull Class<T>);
method public void addSensorPixelModeUsed(int);
method public void addSurface(@NonNull android.view.Surface);
method @NonNull public static java.util.Collection<android.hardware.camera2.params.OutputConfiguration> createInstancesForMultiResolutionOutput(@NonNull android.hardware.camera2.MultiResolutionImageReader);
@@ -32404,6 +32413,8 @@
field public static final int BATTERY_STATUS_NOT_CHARGING = 4; // 0x4
field public static final int BATTERY_STATUS_UNKNOWN = 1; // 0x1
field public static final String EXTRA_BATTERY_LOW = "battery_low";
+ field public static final String EXTRA_CHARGING_STATUS = "android.os.extra.CHARGING_STATUS";
+ field public static final String EXTRA_CYCLE_COUNT = "android.os.extra.CYCLE_COUNT";
field public static final String EXTRA_HEALTH = "health";
field public static final String EXTRA_ICON_SMALL = "icon-small";
field public static final String EXTRA_LEVEL = "level";
@@ -40561,7 +40572,9 @@
public final class CallingAppInfo implements android.os.Parcelable {
ctor public CallingAppInfo(@NonNull String, @NonNull android.content.pm.SigningInfo);
+ ctor public CallingAppInfo(@NonNull String, @NonNull android.content.pm.SigningInfo, @Nullable String);
method public int describeContents();
+ method @Nullable public String getOrigin();
method @NonNull public String getPackageName();
method @NonNull public android.content.pm.SigningInfo getSigningInfo();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -42053,6 +42066,7 @@
}
public final class CallControl {
+ method public void answer(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
method public void disconnect(@NonNull android.telecom.DisconnectCause, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
method @NonNull public android.os.ParcelUuid getCallId();
method public void requestCallEndpointChange(@NonNull android.telecom.CallEndpoint, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
@@ -42065,8 +42079,7 @@
public interface CallControlCallback {
method public void onAnswer(int, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method public void onCallStreamingStarted(@NonNull java.util.function.Consumer<java.lang.Boolean>);
- method public void onDisconnect(@NonNull java.util.function.Consumer<java.lang.Boolean>);
- method public void onReject(@NonNull java.util.function.Consumer<java.lang.Boolean>);
+ method public void onDisconnect(@NonNull android.telecom.DisconnectCause, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method public void onSetActive(@NonNull java.util.function.Consumer<java.lang.Boolean>);
method public void onSetInactive(@NonNull java.util.function.Consumer<java.lang.Boolean>);
}
@@ -50383,12 +50396,6 @@
field public static final int VERTICAL_GRAVITY_MASK = 112; // 0x70
}
- public class HandwritingDelegateConfiguration {
- ctor public HandwritingDelegateConfiguration(@IdRes int, @NonNull Runnable);
- method public int getDelegatorViewId();
- method @NonNull public Runnable getInitiationCallback();
- }
-
public class HapticFeedbackConstants {
field public static final int CLOCK_TICK = 4; // 0x4
field public static final int CONFIRM = 16; // 0x10
@@ -51986,6 +51993,8 @@
method @Nullable public CharSequence getAccessibilityPaneTitle();
method @IdRes public int getAccessibilityTraversalAfter();
method @IdRes public int getAccessibilityTraversalBefore();
+ method @NonNull public String getAllowedHandwritingDelegatePackageName();
+ method @NonNull public String getAllowedHandwritingDelegatorPackageName();
method public float getAlpha();
method public android.view.animation.Animation getAnimation();
method @Nullable public android.graphics.Matrix getAnimationMatrix();
@@ -52041,7 +52050,7 @@
method public float getHandwritingBoundsOffsetLeft();
method public float getHandwritingBoundsOffsetRight();
method public float getHandwritingBoundsOffsetTop();
- method @Nullable public android.view.HandwritingDelegateConfiguration getHandwritingDelegateConfiguration();
+ method @Nullable public Runnable getHandwritingDelegatorCallback();
method public final boolean getHasOverlappingRendering();
method public final int getHeight();
method public void getHitRect(android.graphics.Rect);
@@ -52197,6 +52206,7 @@
method public boolean isFocused();
method public final boolean isFocusedByDefault();
method public boolean isForceDarkAllowed();
+ method public boolean isHandwritingDelegate();
method public boolean isHapticFeedbackEnabled();
method public boolean isHardwareAccelerated();
method public boolean isHorizontalFadingEdgeEnabled();
@@ -52367,6 +52377,8 @@
method public void setAccessibilityTraversalBefore(@IdRes int);
method public void setActivated(boolean);
method public void setAllowClickWhenDisabled(boolean);
+ method public void setAllowedHandwritingDelegatePackage(@NonNull String);
+ method public void setAllowedHandwritingDelegatorPackage(@NonNull String);
method public void setAlpha(@FloatRange(from=0.0, to=1.0) float);
method public void setAnimation(android.view.animation.Animation);
method public void setAnimationMatrix(@Nullable android.graphics.Matrix);
@@ -52409,7 +52421,7 @@
method public void setForegroundTintList(@Nullable android.content.res.ColorStateList);
method public void setForegroundTintMode(@Nullable android.graphics.PorterDuff.Mode);
method public void setHandwritingBoundsOffsets(float, float, float, float);
- method public void setHandwritingDelegateConfiguration(@Nullable android.view.HandwritingDelegateConfiguration);
+ method public void setHandwritingDelegatorCallback(@Nullable Runnable);
method public void setHapticFeedbackEnabled(boolean);
method public void setHasTransientState(boolean);
method public void setHorizontalFadingEdgeEnabled(boolean);
@@ -52422,6 +52434,7 @@
method public void setImportantForAutofill(int);
method public void setImportantForContentCapture(int);
method public void setIsCredential(boolean);
+ method public void setIsHandwritingDelegate(boolean);
method public void setKeepScreenOn(boolean);
method public void setKeyboardNavigationCluster(boolean);
method public void setLabelFor(@IdRes int);
@@ -55620,6 +55633,8 @@
}
public final class InputMethodManager {
+ method public boolean acceptStylusHandwritingDelegation(@NonNull android.view.View);
+ method public boolean acceptStylusHandwritingDelegation(@NonNull android.view.View, @NonNull String);
method public void dispatchKeyEventFromInputMethod(@Nullable android.view.View, @NonNull android.view.KeyEvent);
method public void displayCompletions(android.view.View, android.view.inputmethod.CompletionInfo[]);
method @Nullable public android.view.inputmethod.InputMethodInfo getCurrentInputMethodInfo();
@@ -55641,6 +55656,8 @@
method public boolean isInputMethodSuppressingSpellChecker();
method public boolean isStylusHandwritingAvailable();
method @Deprecated public boolean isWatchingCursor(android.view.View);
+ method public void prepareStylusHandwritingDelegation(@NonNull android.view.View);
+ method public void prepareStylusHandwritingDelegation(@NonNull android.view.View, @NonNull String);
method public void restartInput(android.view.View);
method public void sendAppPrivateCommand(android.view.View, String, android.os.Bundle);
method @Deprecated public void setAdditionalInputMethodSubtypes(@NonNull String, @NonNull android.view.inputmethod.InputMethodSubtype[]);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 8c0a917..47d9ab6 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -173,6 +173,7 @@
field public static final String MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED = "android.permission.MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED";
field public static final String MANAGE_CARRIER_OEM_UNLOCK_STATE = "android.permission.MANAGE_CARRIER_OEM_UNLOCK_STATE";
field public static final String MANAGE_CA_CERTIFICATES = "android.permission.MANAGE_CA_CERTIFICATES";
+ field public static final String MANAGE_CLIPBOARD_ACCESS_NOTIFICATION = "android.permission.MANAGE_CLIPBOARD_ACCESS_NOTIFICATION";
field public static final String MANAGE_CLOUDSEARCH = "android.permission.MANAGE_CLOUDSEARCH";
field public static final String MANAGE_CONTENT_CAPTURE = "android.permission.MANAGE_CONTENT_CAPTURE";
field public static final String MANAGE_CONTENT_SUGGESTIONS = "android.permission.MANAGE_CONTENT_SUGGESTIONS";
@@ -842,6 +843,7 @@
method public int getPendingIntentBackgroundActivityStartMode();
method public boolean isDeferUntilActive();
method @Deprecated public boolean isPendingIntentBackgroundActivityLaunchAllowed();
+ method @Deprecated @NonNull public static android.app.BroadcastOptions makeBasic();
method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RESPONSE_STATS) public void recordResponseEventWhileInBackground(@IntRange(from=0) long);
method @RequiresPermission(android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND) public void setBackgroundActivityStartsAllowed(boolean);
method @NonNull public android.app.BroadcastOptions setDeferUntilActive(boolean);
@@ -3399,6 +3401,8 @@
}
public class ClipboardManager extends android.text.ClipboardManager {
+ method @RequiresPermission(android.Manifest.permission.MANAGE_CLIPBOARD_ACCESS_NOTIFICATION) public boolean areClipboardAccessNotificationsEnabled();
+ method @RequiresPermission(android.Manifest.permission.MANAGE_CLIPBOARD_ACCESS_NOTIFICATION) public void setClipboardAccessNotificationsEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.SET_CLIP_SOURCE) public void setPrimaryClipAsPackage(@NonNull android.content.ClipData, @NonNull String);
}
@@ -10202,6 +10206,14 @@
public class BatteryManager {
method @RequiresPermission(android.Manifest.permission.POWER_SAVER) public boolean setChargingStateUpdateDelayMillis(int);
+ field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_CHARGING_POLICY = 9; // 0x9
+ field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_FIRST_USAGE_DATE = 8; // 0x8
+ field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_MANUFACTURING_DATE = 7; // 0x7
+ field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_STATE_OF_HEALTH = 10; // 0xa
+ field public static final int CHARGING_POLICY_ADAPTIVE_AC = 3; // 0x3
+ field public static final int CHARGING_POLICY_ADAPTIVE_AON = 2; // 0x2
+ field public static final int CHARGING_POLICY_ADAPTIVE_LONGLIFE = 4; // 0x4
+ field public static final int CHARGING_POLICY_DEFAULT = 1; // 0x1
field public static final String EXTRA_EVENTS = "android.os.extra.EVENTS";
field public static final String EXTRA_EVENT_TIMESTAMP = "android.os.extra.EVENT_TIMESTAMP";
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index a53fa34..640506d 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -725,6 +725,14 @@
}
+package android.app.job {
+
+ public class JobParameters implements android.os.Parcelable {
+ field public static final int INTERNAL_STOP_REASON_USER_UI_STOP = 11; // 0xb
+ }
+
+}
+
package android.app.prediction {
public final class AppPredictor {
diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java
index c628ec4..d859f3f 100644
--- a/core/java/android/app/ApplicationExitInfo.java
+++ b/core/java/android/app/ApplicationExitInfo.java
@@ -451,6 +451,15 @@
*/
public static final int SUBREASON_SDK_SANDBOX_DIED = 27;
+ /**
+ * The process was killed because it was an SDK sandbox process that was either not usable or
+ * was no longer being used; this would be set only when the reason is {@link #REASON_OTHER}.
+ *
+ * For internal use only.
+ * @hide
+ */
+ public static final int SUBREASON_SDK_SANDBOX_NOT_NEEDED = 28;
+
// If there is any OEM code which involves additional app kill reasons, it should
// be categorized in {@link #REASON_OTHER}, with subreason code starting from 1000.
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 6301ad7..a99815c 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -556,7 +556,12 @@
@Override
public ActivityInfo getActivityInfo(ComponentName className, ComponentInfoFlags flags)
throws NameNotFoundException {
- final int userId = getUserId();
+ return getActivityInfoAsUser(className, flags, getUserId());
+ }
+
+ @Override
+ public ActivityInfo getActivityInfoAsUser(ComponentName className,
+ ComponentInfoFlags flags, @UserIdInt int userId) throws NameNotFoundException {
try {
ActivityInfo ai = mPM.getActivityInfo(className,
updateFlagsForComponent(flags.getValue(), userId, null), userId);
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index f35bdfb..fe40a4c 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -262,7 +262,12 @@
* Creates a basic {@link BroadcastOptions} with no options initially set.
*
* @return an instance of {@code BroadcastOptions} against which options can be set
+ *
+ * @deprecated Use {@link BroadcastOptions#BroadcastOptions()} instead.
+ * @hide
*/
+ @Deprecated
+ @SystemApi
public static @NonNull BroadcastOptions makeBasic() {
BroadcastOptions opts = new BroadcastOptions();
return opts;
@@ -280,7 +285,10 @@
return opts;
}
- private BroadcastOptions() {
+ /**
+ * Creates a new {@code BroadcastOptions} with no options initially set.
+ */
+ public BroadcastOptions() {
super();
resetTemporaryAppAllowlist();
}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index a203ae2..1c0be68 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1923,6 +1923,7 @@
private ComponentName startServiceCommon(Intent service, boolean requireForeground,
UserHandle user) {
+ // Keep this in sync with ActivityManagerLocal.startSdkSandboxService
try {
validateServiceIntent(service);
service.prepareToLeaveProcess(this);
@@ -1964,6 +1965,7 @@
}
private boolean stopServiceCommon(Intent service, UserHandle user) {
+ // // Keep this in sync with ActivityManagerLocal.stopSdkSandboxService
try {
validateServiceIntent(service);
service.prepareToLeaveProcess(this);
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index e97e711..d62e15a 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -65,12 +65,12 @@
import android.os.IBinder;
import android.os.IProgressListener;
import android.os.ParcelFileDescriptor;
+import android.os.RemoteCallback;
import android.os.StrictMode;
import android.os.WorkSource;
import android.service.voice.IVoiceInteractionSession;
import android.view.IRecentsAnimationRunner;
import android.view.IRemoteAnimationRunner;
-import android.view.IWindowFocusObserver;
import android.view.RemoteAnimationDefinition;
import android.view.RemoteAnimationAdapter;
import android.window.IWindowOrganizerController;
@@ -349,12 +349,13 @@
/**
* Prepare the back navigation in the server. This setups the leashed for sysui to animate
* the back gesture and returns the data needed for the animation.
- * @param focusObserver a remote callback to nofify shell when the focused window lost focus.
+ * @param navigationObserver a remote callback to nofify shell when the focused window is gone,
+ or an unexpected transition has happened on the navigation target.
* @param adaptor a remote animation to be run for the back navigation plays the animation.
* @return Returns the back navigation info.
*/
android.window.BackNavigationInfo startBackNavigation(
- in IWindowFocusObserver focusObserver, in BackAnimationAdapter adaptor);
+ in RemoteCallback navigationObserver, in BackAnimationAdapter adaptor);
/**
* registers a callback to be invoked when the screen is captured.
diff --git a/core/java/android/app/LocaleManager.java b/core/java/android/app/LocaleManager.java
index b62f766..bb9a95c 100644
--- a/core/java/android/app/LocaleManager.java
+++ b/core/java/android/app/LocaleManager.java
@@ -198,13 +198,14 @@
* the override config, and stored in a system file for future access.
*
* <p><b>Note:</b> Using this function, applications can update their list of supported
- * locales while running, without an update of the application’s software. For more
- * information, see TODO(b/261528306): add link to guide.
+ * locales while running, without an update of the application’s software.
*
* <p>Applications can remove the override LocaleConfig with a {@code null} object.
*
* @param localeConfig the desired {@link LocaleConfig} for the calling app.
*/
+ // Add following to last Note: when guide is written:
+ // For more information, see TODO(b/261528306): add link to guide.
@UserHandleAware
public void setOverrideLocaleConfig(@Nullable LocaleConfig localeConfig) {
try {
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index ef5cd93..440ee20 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5758,7 +5758,7 @@
List<Notification.Action> nonContextualActions = getNonContextualActions();
int numActions = Math.min(nonContextualActions.size(), MAX_ACTION_BUTTONS);
- boolean emphazisedMode = mN.fullScreenIntent != null
+ boolean emphasizedMode = mN.fullScreenIntent != null
|| p.mCallStyleActions
|| ((mN.flags & FLAG_FSI_REQUESTED_BUT_DENIED) != 0);
@@ -5771,7 +5771,7 @@
big.setInt(R.id.actions, "setCollapsibleIndentDimen",
R.dimen.call_notification_collapsible_indent);
}
- big.setBoolean(R.id.actions, "setEmphasizedMode", emphazisedMode);
+ big.setBoolean(R.id.actions, "setEmphasizedMode", emphasizedMode);
if (numActions > 0 && !p.mHideActions) {
big.setViewVisibility(R.id.actions_container, View.VISIBLE);
big.setViewVisibility(R.id.actions, View.VISIBLE);
@@ -5783,12 +5783,12 @@
boolean actionHasValidInput = hasValidRemoteInput(action);
validRemoteInput |= actionHasValidInput;
- final RemoteViews button = generateActionButton(action, emphazisedMode, p);
- if (actionHasValidInput && !emphazisedMode) {
+ final RemoteViews button = generateActionButton(action, emphasizedMode, p);
+ if (actionHasValidInput && !emphasizedMode) {
// Clear the drawable
button.setInt(R.id.action0, "setBackgroundResource", 0);
}
- if (emphazisedMode && i > 0) {
+ if (emphasizedMode && i > 0) {
// Clear start margin from non-first buttons to reduce the gap between them.
// (8dp remaining gap is from all buttons' standard 4dp inset).
button.setViewLayoutMarginDimen(R.id.action0, RemoteViews.MARGIN_START, 0);
@@ -7475,10 +7475,10 @@
Resources resources = context.getResources();
boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
if (mPictureIcon != null) {
- int maxPictureWidth = resources.getDimensionPixelSize(isLowRam
+ int maxPictureHeight = resources.getDimensionPixelSize(isLowRam
? R.dimen.notification_big_picture_max_height_low_ram
: R.dimen.notification_big_picture_max_height);
- int maxPictureHeight = resources.getDimensionPixelSize(isLowRam
+ int maxPictureWidth = resources.getDimensionPixelSize(isLowRam
? R.dimen.notification_big_picture_max_width_low_ram
: R.dimen.notification_big_picture_max_width);
mPictureIcon.scaleDownIfNecessary(maxPictureWidth, maxPictureHeight);
diff --git a/core/java/android/app/timedetector/TimeDetector.java b/core/java/android/app/timedetector/TimeDetector.java
index 50a7da1..5ee10a5 100644
--- a/core/java/android/app/timedetector/TimeDetector.java
+++ b/core/java/android/app/timedetector/TimeDetector.java
@@ -115,6 +115,20 @@
String SHELL_COMMAND_CONFIRM_TIME = "confirm_time";
/**
+ * A shell command that clears the network time signal used by {@link
+ * SystemClock#currentNetworkTimeClock()}.
+ * @hide
+ */
+ String SHELL_COMMAND_CLEAR_SYSTEM_CLOCK_NETWORK_TIME = "clear_system_clock_network_time";
+
+ /**
+ * A shell command that sets the network time signal used by {@link
+ * SystemClock#currentNetworkTimeClock()}.
+ * @hide
+ */
+ String SHELL_COMMAND_SET_SYSTEM_CLOCK_NETWORK_TIME = "set_system_clock_network_time";
+
+ /**
* A shared utility method to create a {@link ManualTimeSuggestion}.
*
* @hide
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index e981581..2b400c1f 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -155,6 +155,11 @@
AttributionSource(@NonNull Parcel in) {
this(AttributionSourceState.CREATOR.createFromParcel(in));
+ if (!Binder.isDirectlyHandlingTransaction()) {
+ throw new SecurityException("AttributionSource should be unparceled during a binder "
+ + "transaction for proper verification.");
+ }
+
// Since we just unpacked this object as part of it transiting a Binder
// call, this is the perfect time to enforce that its UID and PID can be trusted
enforceCallingUidAndPid();
diff --git a/core/java/android/content/ClipboardManager.java b/core/java/android/content/ClipboardManager.java
index 06a3b1e..8a22ce3 100644
--- a/core/java/android/content/ClipboardManager.java
+++ b/core/java/android/content/ClipboardManager.java
@@ -135,6 +135,39 @@
}
/**
+ * Determine if the Clipboard Access Notifications are enabled
+ *
+ * @return true if notifications are enabled, false otherwise.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.MANAGE_CLIPBOARD_ACCESS_NOTIFICATION)
+ public boolean areClipboardAccessNotificationsEnabled() {
+ try {
+ return mService.areClipboardAccessNotificationsEnabledForUser(mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ *
+ * Set the enable state of the Clipboard Access Notifications
+ * @param enable Whether to enable notifications
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.MANAGE_CLIPBOARD_ACCESS_NOTIFICATION)
+ public void setClipboardAccessNotificationsEnabled(boolean enable) {
+ try {
+ mService.setClipboardAccessNotificationsEnabledForUser(enable, mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Sets the current primary clip on the clipboard. This is the clip that
* is involved in normal cut and paste operations.
*
diff --git a/core/java/android/content/IClipboard.aidl b/core/java/android/content/IClipboard.aidl
index b2216d4..fe7798f 100644
--- a/core/java/android/content/IClipboard.aidl
+++ b/core/java/android/content/IClipboard.aidl
@@ -48,4 +48,8 @@
String getPrimaryClipSource(String callingPackage, String attributionTag, int userId,
int deviceId);
+
+ boolean areClipboardAccessNotificationsEnabledForUser(int userId);
+
+ void setClipboardAccessNotificationsEnabledForUser(boolean enable, int userId);
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 5818ed7..85daf15 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3234,8 +3234,9 @@
/**
* Broadcast Action: The receiver's effective locale has changed.
*
- * This happens when the device locale, or the receiving app's locale
- * (set via {@link android.app.LocaleManager#setApplicationLocales}) changed.
+ * This happens when the device locale, the receiving app's locale
+ * (set via {@link android.app.LocaleManager#setApplicationLocales}) or language tags
+ * of Regional preferences changed.
*
* Can be received by manifest-declared receivers.
*
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index afc2285..bd3cf5f 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -20,6 +20,9 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.compat.annotation.UnsupportedAppUsage;
import android.net.Uri;
import android.os.Build;
@@ -182,6 +185,28 @@
private static final boolean[] EMPTY_BOOLEAN_ARRAY = new boolean[0];
/**
+ * An intent with action set as null used to always pass the action test during intent
+ * filter matching. This causes a lot of confusion and unexpected intent matches.
+ * Null action intents should be blocked when either the intent sender or receiver
+ * application targets U or higher.
+ *
+ * mBlockNullAction indicates whether the intent filter owner (intent receiver) is
+ * targeting U+. This value will be properly set by package manager when IntentFilters are
+ * passed to an application, so that when an application is trying to perform intent filter
+ * matching locally, the correct matching algorithm will be chosen.
+ *
+ * When an IntentFilter is sent to system server (e.g. for registering runtime receivers),
+ * the value set in mBlockNullAction will be ignored and overwritten with the correct
+ * value evaluated based on the Binder calling identity. This makes sure that the
+ * security enforcement cannot be bypassed by crafting a malicious IntentFilter.
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ public static final long BLOCK_NULL_ACTION_INTENTS = 264497795;
+
+ /**
* The filter {@link #setPriority} value at which system high-priority
* receivers are placed; that is, receivers that should execute before
* application code. Applications should never use filters with this or
@@ -2276,6 +2301,7 @@
String type = resolve ? intent.resolveType(resolver) : intent.getType();
return match(intent.getAction(), type, intent.getScheme(),
intent.getData(), intent.getCategories(), logTag,
+ CompatChanges.isChangeEnabled(BLOCK_NULL_ACTION_INTENTS),
false /* supportWildcards */, null /* ignoreActions */,
intent.getExtras());
}
@@ -2328,6 +2354,7 @@
Uri data, Set<String> categories, String logTag, boolean supportWildcards,
@Nullable Collection<String> ignoreActions) {
return match(action, type, scheme, data, categories, logTag, supportWildcards,
+ CompatChanges.isChangeEnabled(BLOCK_NULL_ACTION_INTENTS),
ignoreActions, null /* extras */);
}
@@ -2339,8 +2366,10 @@
*/
public final int match(String action, String type, String scheme,
Uri data, Set<String> categories, String logTag, boolean supportWildcards,
- @Nullable Collection<String> ignoreActions, @Nullable Bundle extras) {
- if (action != null && !matchAction(action, supportWildcards, ignoreActions)) {
+ boolean blockNullAction, @Nullable Collection<String> ignoreActions,
+ @Nullable Bundle extras) {
+ if ((action == null && blockNullAction)
+ || !matchAction(action, supportWildcards, ignoreActions)) {
if (false) Log.v(
logTag, "No matching action " + action + " for " + this);
return NO_MATCH_ACTION;
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 8acdf51..a6a6215 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -1711,8 +1711,8 @@
* performed on the session. In case of device reboot or data loader transient failure
* before the session has been finalized, you may commit the session again.
* <p>
- * If the installer is the device owner or the affiliated profile owner, there will be no
- * user intervention.
+ * If the installer is the device owner, the affiliated profile owner, or has received
+ * user pre-approval of this session, there will be no user intervention.
*
* @param statusReceiver Called when the state of the session changes. Intents
* sent to this receiver contain {@link #EXTRA_STATUS}. Refer to the
@@ -1722,6 +1722,7 @@
* {@link #openWrite(String, long, long)} are still open.
*
* @see android.app.admin.DevicePolicyManager
+ * @see #requestUserPreapproval
*/
public void commit(@NonNull IntentSender statusReceiver) {
try {
@@ -1987,14 +1988,22 @@
* {@link android.Manifest.permission#REQUEST_INSTALL_PACKAGES REQUEST_INSTALL_PACKAGES}
* permission, they can request the approval from users before
* {@link Session#commit(IntentSender)} is called. This may require user intervention as
- * well. The result of the request will be reported through the given callback.
+ * well. When user intervention is required, installers will receive a
+ * {@link #STATUS_PENDING_USER_ACTION} callback, and {@link #STATUS_SUCCESS} otherwise.
+ * In case that requesting user pre-approval is not available, installers will receive
+ * {@link #STATUS_FAILURE_BLOCKED} instead. Note that if the users decline the request,
+ * this session will be abandoned.
+ *
+ * If user intervention is required but never resolved, or requesting user
+ * pre-approval is not available, you may still call {@link Session#commit(IntentSender)}
+ * as the typical installation.
*
* @param details the adequate context to this session for requesting the approval from
* users prior to commit.
* @param statusReceiver called when the state of the session changes.
- * Intents sent to this receiver contain
- * {@link #EXTRA_STATUS}. Refer to the individual
- * status codes on how to handle them.
+ * Intents sent to this receiver contain {@link #EXTRA_STATUS}
+ * and the {@link #EXTRA_PRE_APPROVAL} would be {@code true}.
+ * Refer to the individual status codes on how to handle them.
*
* @throws IllegalArgumentException when {@link PreapprovalDetails} is {@code null}.
* @throws IllegalArgumentException if {@link IntentSender} is {@code null}.
@@ -2003,6 +2012,7 @@
* @throws IllegalStateException if called again after this method has been called on
* this session.
* @throws SecurityException when the caller does not own this session.
+ * @throws SecurityException if called after the session has been committed or abandoned.
*/
public void requestUserPreapproval(@NonNull PreapprovalDetails details,
@NonNull IntentSender statusReceiver) {
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index db05b95..a0c620a 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -5720,6 +5720,17 @@
}
/**
+ * @hide
+ */
+ @NonNull
+ public ActivityInfo getActivityInfoAsUser(@NonNull ComponentName component,
+ @NonNull ComponentInfoFlags flags, @UserIdInt int userId)
+ throws NameNotFoundException {
+ throw new UnsupportedOperationException(
+ "getActivityInfoAsUser not implemented in subclass");
+ }
+
+ /**
* Retrieve all of the information we know about a particular receiver
* class.
*
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index f3ad9d1..02b9308 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -361,7 +361,7 @@
/**
* Constant corresponding to {@code systemExempted} in
* the {@link android.R.attr#foregroundServiceType} attribute.
- * The system exmpted foreground service use cases.
+ * The system exempted foreground service use cases.
*
* <p class="note">Note, apps are allowed to use this type only in the following cases:
* <ul>
diff --git a/core/java/android/credentials/CreateCredentialRequest.java b/core/java/android/credentials/CreateCredentialRequest.java
index 1e2a86f..b756a43 100644
--- a/core/java/android/credentials/CreateCredentialRequest.java
+++ b/core/java/android/credentials/CreateCredentialRequest.java
@@ -16,9 +16,14 @@
package android.credentials;
+import static android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN;
+
import static java.util.Objects.requireNonNull;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -69,6 +74,14 @@
private final boolean mIsSystemProviderRequired;
/**
+ * The origin of the calling app. Callers of this special API (e.g. browsers)
+ * can set this origin for an app different from their own, to be able to get credentials
+ * on behalf of that app.
+ */
+ @Nullable
+ private final String mOrigin;
+
+ /**
* Returns the requested credential type.
*/
@NonNull
@@ -123,6 +136,14 @@
return mAlwaysSendAppInfoToProvider;
}
+ /**
+ * Returns the origin of the calling app if set otherwise returns null.
+ */
+ @Nullable
+ public String getOrigin() {
+ return mOrigin;
+ }
+
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString8(mType);
@@ -130,6 +151,7 @@
dest.writeBundle(mCandidateQueryData);
dest.writeBoolean(mIsSystemProviderRequired);
dest.writeBoolean(mAlwaysSendAppInfoToProvider);
+ dest.writeString8(mOrigin);
}
@Override
@@ -146,6 +168,7 @@
+ ", isSystemProviderRequired=" + mIsSystemProviderRequired
+ ", alwaysSendAppInfoToProvider="
+ mAlwaysSendAppInfoToProvider
+ + ", origin=" + mOrigin
+ "}";
}
@@ -165,21 +188,26 @@
* the query phase, and will only be sent along
* with the final request, after the user has selected
* an entry on the UI.
+ * @param origin the origin of the calling app. Callers of this special setter (e.g. browsers)
+ * can set this origin for an app different from their own, to be able to get
+ * credentials on behalf of that app.
*
* @throws IllegalArgumentException If type is empty.
*/
- public CreateCredentialRequest(
+ private CreateCredentialRequest(
@NonNull String type,
@NonNull Bundle credentialData,
@NonNull Bundle candidateQueryData,
boolean isSystemProviderRequired,
- boolean alwaysSendAppInfoToProvider) {
+ boolean alwaysSendAppInfoToProvider,
+ @NonNull String origin) {
mType = Preconditions.checkStringNotEmpty(type, "type must not be empty");
mCredentialData = requireNonNull(credentialData, "credentialData must not be null");
mCandidateQueryData = requireNonNull(candidateQueryData,
"candidateQueryData must not be null");
mIsSystemProviderRequired = isSystemProviderRequired;
mAlwaysSendAppInfoToProvider = alwaysSendAppInfoToProvider;
+ mOrigin = origin;
}
private CreateCredentialRequest(@NonNull Parcel in) {
@@ -188,6 +216,7 @@
Bundle candidateQueryData = in.readBundle();
boolean isSystemProviderRequired = in.readBoolean();
boolean alwaysSendAppInfoToProvider = in.readBoolean();
+ mOrigin = in.readString8();
mType = type;
AnnotationValidations.validate(NonNull.class, null, mType);
@@ -211,4 +240,104 @@
return new CreateCredentialRequest(in);
}
};
+
+ /** A builder for {@link CreateCredentialRequest}. */
+ public static final class Builder {
+
+ private boolean mAlwaysSendAppInfoToProvider;
+
+ @NonNull
+ private String mType;
+
+ @NonNull
+ private final Bundle mCredentialData;
+
+ @NonNull
+ private final Bundle mCandidateQueryData;
+
+ private boolean mIsSystemProviderRequired;
+
+ private String mOrigin;
+
+ /**
+ * @param credentialData the full credential creation request data
+ * @param candidateQueryData the partial request data that will be sent to the provider
+ * during the initial creation candidate query stage
+ */
+ public Builder(@NonNull Bundle credentialData, @NonNull Bundle candidateQueryData) {
+ mCredentialData = requireNonNull(credentialData,
+ "credentialData must not be null");
+ mCandidateQueryData = requireNonNull(candidateQueryData,
+ "candidateQueryData must not be null");
+ }
+
+ /**
+ * Sets a true/false value to determine if the calling app info should be
+ * removed from the request that is sent to the providers.
+ *
+ * Developers must set this to false if they wish to remove the
+ * {@link android.service.credentials.CallingAppInfo} from the query phases requests that
+ * providers receive. Note that the calling app info will still be sent in the
+ * final phase after the user has made a selection on the UI.
+ *
+ * If not set, the default value will be true and the calling app info will be
+ * propagated to the providers in every phase.
+ */
+ @SuppressLint("MissingGetterMatchingBuilder")
+ @NonNull
+ public CreateCredentialRequest.Builder setAlwaysSendAppInfoToProvider(boolean value) {
+ mAlwaysSendAppInfoToProvider = value;
+ return this;
+ }
+
+ /**
+ * Sets the requested credential type.
+ */
+ @SuppressLint("MissingGetterMatchingBuilder")
+ @NonNull
+ public CreateCredentialRequest.Builder setType(@NonNull String type) {
+ mType = type;
+ return this;
+ }
+
+ /**
+ * Sets whether the request must only be fulfilled by a system provider.
+ * This defaults to false
+ */
+ @SuppressLint("MissingGetterMatchingBuilder")
+ @NonNull
+ public CreateCredentialRequest.Builder setIsSystemProviderRequired(boolean value) {
+ mIsSystemProviderRequired = value;
+ return this;
+ }
+
+ /**
+ * Sets the origin of the calling app. Callers of this special setter (e.g. browsers)
+ * can set this origin for an app different from their own, to be able to get
+ * credentials on behalf of that app. The permission check only happens later when this
+ * instance is passed and processed by the Credential Manager.
+ */
+ @SuppressLint({"MissingGetterMatchingBuilder", "AndroidFrameworkRequiresPermission"})
+ @RequiresPermission(CREDENTIAL_MANAGER_SET_ORIGIN)
+ @NonNull
+ public CreateCredentialRequest.Builder setOrigin(@NonNull String origin) {
+ mOrigin = origin;
+ return this;
+ }
+
+ /**
+ * Builds a {@link GetCredentialRequest}.
+ *
+ * @throws IllegalArgumentException If credentialOptions is empty.
+ */
+ @NonNull
+ public CreateCredentialRequest build() {
+ Preconditions.checkStringNotEmpty(
+ mType,
+ "type must not be empty");
+
+ return new CreateCredentialRequest(mType, mCredentialData, mCandidateQueryData,
+ mIsSystemProviderRequired, mAlwaysSendAppInfoToProvider, mOrigin);
+ }
+ }
}
diff --git a/core/java/android/credentials/CredentialDescription.java b/core/java/android/credentials/CredentialDescription.java
index f9db5e8..bf34c1c 100644
--- a/core/java/android/credentials/CredentialDescription.java
+++ b/core/java/android/credentials/CredentialDescription.java
@@ -123,7 +123,7 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeString(mType);
+ dest.writeString8(mType);
dest.writeString(mFlattenedRequestString);
dest.writeTypedList(mCredentialEntries, flags);
}
@@ -151,16 +151,4 @@
public List<CredentialEntry> getCredentialEntries() {
return mCredentialEntries;
}
-
- @Override
- public int hashCode() {
- return Objects.hash(mType, mFlattenedRequestString);
- }
-
- @Override
- public boolean equals(Object obj) {
- return Objects.equals(mType, ((CredentialDescription) obj).getType())
- && Objects.equals(mFlattenedRequestString, ((CredentialDescription) obj)
- .getFlattenedRequestString());
- }
}
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index ff7fc36..f0230e7 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -123,6 +123,10 @@
*
* <p>The execution can potentially launch UI flows to collect user consent to using a
* credential, display a picker when multiple credentials exist, etc.
+ * Callers (e.g. browsers) may optionally set origin in {@link GetCredentialRequest} for an
+ * app different from their own, to be able to get credentials on behalf of that app. They would
+ * need additional permission {@link CREDENTIAL_MANAGER_SET_ORIGIN}
+ * to use this functionality
*
* @param request the request specifying type(s) of credentials to get from the user
* @param activity the activity used to launch any UI needed
@@ -163,61 +167,14 @@
}
/**
- * Launches the necessary flows to retrieve an app credential from the user, for the given
- * origin.
- *
- * <p>The execution can potentially launch UI flows to collect user consent to using a
- * credential, display a picker when multiple credentials exist, etc.
- *
- * @param request the request specifying type(s) of credentials to get from the user
- * @param origin the origin of the calling app. Callers of this special API (e.g. browsers)
- * can set this origin for an app different from their own, to be able to get credentials
- * on behalf of that app.
- * @param activity the activity used to launch any UI needed
- * @param cancellationSignal an optional signal that allows for cancelling this call
- * @param executor the callback will take place on this {@link Executor}
- * @param callback the callback invoked when the request succeeds or fails
- */
- @RequiresPermission(CREDENTIAL_MANAGER_SET_ORIGIN)
- public void getCredentialWithOrigin(
- @NonNull GetCredentialRequest request,
- @Nullable String origin,
- @NonNull Activity activity,
- @Nullable CancellationSignal cancellationSignal,
- @CallbackExecutor @NonNull Executor executor,
- @NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
- requireNonNull(request, "request must not be null");
- requireNonNull(activity, "activity must not be null");
- requireNonNull(executor, "executor must not be null");
- requireNonNull(callback, "callback must not be null");
-
- if (cancellationSignal != null && cancellationSignal.isCanceled()) {
- Log.w(TAG, "getCredential already canceled");
- return;
- }
-
- ICancellationSignal cancelRemote = null;
- try {
- cancelRemote =
- mService.executeGetCredentialWithOrigin(
- request,
- new GetCredentialTransport(activity, executor, callback),
- mContext.getOpPackageName(),
- origin);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
-
- if (cancellationSignal != null && cancelRemote != null) {
- cancellationSignal.setRemote(cancelRemote);
- }
- }
-
- /**
* Launches the necessary flows to register an app credential for the user.
*
* <p>The execution can potentially launch UI flows to collect user consent to creating or
* storing the new credential, etc.
+ * Callers (e.g. browsers) may optionally set origin in {@link CreateCredentialRequest} for an
+ * app different from their own, to be able to get credentials on behalf of that app. They would
+ * need additional permission {@link CREDENTIAL_MANAGER_SET_ORIGIN}
+ * to use this functionality
*
* @param request the request specifying type(s) of credentials to get from the user
* @param activity the activity used to launch any UI needed
@@ -259,58 +216,6 @@
}
/**
- * Launches the necessary flows to register an app credential for the user.
- *
- * <p>The execution can potentially launch UI flows to collect user consent to creating or
- * storing the new credential, etc.
- *
- * @param request the request specifying type(s) of credentials to get from the user, for the
- * given origin
- * @param origin the origin of the calling app. Callers of this special API (e.g. browsers)
- * can set this origin for an app different from their own, to be able to get credentials
- * on behalf of that app.
- * @param activity the activity used to launch any UI needed
- * @param cancellationSignal an optional signal that allows for cancelling this call
- * @param executor the callback will take place on this {@link Executor}
- * @param callback the callback invoked when the request succeeds or fails
- */
- @RequiresPermission(CREDENTIAL_MANAGER_SET_ORIGIN)
- public void createCredentialWithOrigin(
- @NonNull CreateCredentialRequest request,
- @Nullable String origin,
- @NonNull Activity activity,
- @Nullable CancellationSignal cancellationSignal,
- @CallbackExecutor @NonNull Executor executor,
- @NonNull
- OutcomeReceiver<CreateCredentialResponse, CreateCredentialException> callback) {
- requireNonNull(request, "request must not be null");
- requireNonNull(activity, "activity must not be null");
- requireNonNull(executor, "executor must not be null");
- requireNonNull(callback, "callback must not be null");
-
- if (cancellationSignal != null && cancellationSignal.isCanceled()) {
- Log.w(TAG, "createCredential already canceled");
- return;
- }
-
- ICancellationSignal cancelRemote = null;
- try {
- cancelRemote =
- mService.executeCreateCredentialWithOrigin(
- request,
- new CreateCredentialTransport(activity, executor, callback),
- mContext.getOpPackageName(),
- origin);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
-
- if (cancellationSignal != null && cancelRemote != null) {
- cancellationSignal.setRemote(cancelRemote);
- }
- }
-
- /**
* Clears the current user credential state from all credential providers.
*
* <p>You should invoked this api after your user signs out of your app to notify all credential
@@ -505,7 +410,19 @@
*
* @hide
*/
- public static boolean isCredentialDescriptionApiEnabled() {
+ public static boolean isCredentialDescriptionApiEnabled(Context context) {
+ if (context == null) {
+ return false;
+ }
+ CredentialManager credentialManager =
+ (CredentialManager) context.getSystemService(Context.CREDENTIAL_SERVICE);
+ if (credentialManager != null) {
+ return credentialManager.isCredentialDescriptionApiEnabled();
+ }
+ return false;
+ }
+
+ private boolean isCredentialDescriptionApiEnabled() {
return DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API, false);
}
@@ -527,11 +444,6 @@
*/
public void registerCredentialDescription(
@NonNull RegisterCredentialDescriptionRequest request) {
-
- if (!isCredentialDescriptionApiEnabled()) {
- throw new UnsupportedOperationException("This API is not currently supported.");
- }
-
requireNonNull(request, "request must not be null");
try {
@@ -550,11 +462,6 @@
*/
public void unregisterCredentialDescription(
@NonNull UnregisterCredentialDescriptionRequest request) {
-
- if (!isCredentialDescriptionApiEnabled()) {
- throw new UnsupportedOperationException("This API is not currently supported.");
- }
-
requireNonNull(request, "request must not be null");
try {
diff --git a/core/java/android/credentials/GetCredentialRequest.java b/core/java/android/credentials/GetCredentialRequest.java
index bc92c7c..951cbe4 100644
--- a/core/java/android/credentials/GetCredentialRequest.java
+++ b/core/java/android/credentials/GetCredentialRequest.java
@@ -16,9 +16,13 @@
package android.credentials;
+import static android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN;
+
import static java.util.Objects.requireNonNull;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.os.Parcel;
@@ -49,6 +53,14 @@
private final Bundle mData;
/**
+ * The origin of the calling app. Callers of this special API (e.g. browsers)
+ * can set this origin for an app different from their own, to be able to get credentials
+ * on behalf of that app.
+ */
+ @Nullable
+ private String mOrigin;
+
+ /**
* True/False value to determine if the calling app info should be
* removed from the request that is sent to the providers.
* Developers must set this to false if they wish to remove the
@@ -76,6 +88,14 @@
}
/**
+ * Returns the origin of the calling app if set otherwise returns null.
+ */
+ @Nullable
+ public String getOrigin() {
+ return mOrigin;
+ }
+
+ /**
* Returns a value to determine if the calling app info should be always
* sent to the provider in every phase (if true), or should be removed
* from the query phase, and only sent as part of the request in the final phase,
@@ -90,6 +110,7 @@
dest.writeTypedList(mCredentialOptions, flags);
dest.writeBundle(mData);
dest.writeBoolean(mAlwaysSendAppInfoToProvider);
+ dest.writeString8(mOrigin);
}
@Override
@@ -103,11 +124,12 @@
+ ", data=" + mData
+ ", alwaysSendAppInfoToProvider="
+ mAlwaysSendAppInfoToProvider
+ + ", origin=" + mOrigin
+ "}";
}
private GetCredentialRequest(@NonNull List<CredentialOption> credentialOptions,
- @NonNull Bundle data, @NonNull boolean alwaysSendAppInfoToProvider) {
+ @NonNull Bundle data, @NonNull boolean alwaysSendAppInfoToProvider, String origin) {
Preconditions.checkCollectionNotEmpty(
credentialOptions,
/*valueName=*/ "credentialOptions");
@@ -118,6 +140,7 @@
mData = requireNonNull(data,
"data must not be null");
mAlwaysSendAppInfoToProvider = alwaysSendAppInfoToProvider;
+ mOrigin = origin;
}
private GetCredentialRequest(@NonNull Parcel in) {
@@ -132,6 +155,7 @@
AnnotationValidations.validate(NonNull.class, null, mData);
mAlwaysSendAppInfoToProvider = in.readBoolean();
+ mOrigin = in.readString8();
}
@NonNull public static final Parcelable.Creator<GetCredentialRequest> CREATOR =
@@ -159,6 +183,8 @@
@NonNull
private boolean mAlwaysSendAppInfoToProvider = true;
+ private String mOrigin;
+
/**
* @param data the top request level data
*/
@@ -209,6 +235,20 @@
}
/**
+ * Sets the origin of the calling app. Callers of this special setter (e.g. browsers)
+ * can set this origin for an app different from their own, to be able to get
+ * credentials on behalf of that app. The permission check only happens later when this
+ * instance is passed and processed by the Credential Manager.
+ */
+ @SuppressLint({"MissingGetterMatchingBuilder", "AndroidFrameworkRequiresPermission"})
+ @RequiresPermission(CREDENTIAL_MANAGER_SET_ORIGIN)
+ @NonNull
+ public Builder setOrigin(@NonNull String origin) {
+ mOrigin = origin;
+ return this;
+ }
+
+ /**
* Builds a {@link GetCredentialRequest}.
*
* @throws IllegalArgumentException If credentialOptions is empty.
@@ -222,7 +262,7 @@
mCredentialOptions,
/*valueName=*/ "credentialOptions");
return new GetCredentialRequest(mCredentialOptions, mData,
- mAlwaysSendAppInfoToProvider);
+ mAlwaysSendAppInfoToProvider, mOrigin);
}
}
}
diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl
index 6d81d80..625fc8a 100644
--- a/core/java/android/credentials/ICredentialManager.aidl
+++ b/core/java/android/credentials/ICredentialManager.aidl
@@ -41,12 +41,8 @@
@nullable ICancellationSignal executeGetCredential(in GetCredentialRequest request, in IGetCredentialCallback callback, String callingPackage);
- @nullable ICancellationSignal executeGetCredentialWithOrigin(in GetCredentialRequest request, in IGetCredentialCallback callback, String callingPackage, String origin);
-
@nullable ICancellationSignal executeCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback, String callingPackage);
- @nullable ICancellationSignal executeCreateCredentialWithOrigin(in CreateCredentialRequest request, in ICreateCredentialCallback callback, String callingPackage, String origin);
-
@nullable ICancellationSignal clearCredentialState(in ClearCredentialStateRequest request, in IClearCredentialStateCallback callback, String callingPackage);
@nullable ICancellationSignal listEnabledProviders(in IListEnabledProvidersCallback callback);
diff --git a/core/java/android/credentials/ui/IntentFactory.java b/core/java/android/credentials/ui/IntentFactory.java
index 67634dc..3c10e81 100644
--- a/core/java/android/credentials/ui/IntentFactory.java
+++ b/core/java/android/credentials/ui/IntentFactory.java
@@ -78,7 +78,7 @@
Resources.getSystem()
.getString(
com.android.internal.R.string
- .config_credentialManagerDialogComponent));
+ .config_credentialManagerReceiverComponent));
intent.setComponent(componentName);
intent.setAction(Constants.CREDMAN_ENABLED_PROVIDERS_UPDATED);
return intent;
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 8e6576a..b9b310f 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -3660,8 +3660,8 @@
/**
* <p>An array of mandatory stream combinations which are applicable when device lists
* {@code PREVIEW_STABILIZATION} in {@link CameraCharacteristics#CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES android.control.availableVideoStabilizationModes}.
- * This is an app-readable conversion of the preview stabilization mandatory stream combination
- * {@link android.hardware.camera2.CameraDevice#createCaptureSession tables}.</p>
+ * This is an app-readable conversion of the preview stabilization mandatory stream
+ * combination {@link android.hardware.camera2.CameraDevice#createCaptureSession tables}.</p>
* <p>The array of
* {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is
* generated according to the documented
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index cca900a..1bf004a 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -541,7 +541,8 @@
private static <T> boolean isOutputSupportedFor(Class<T> klass) {
Objects.requireNonNull(klass, "klass must not be null");
- if (klass == android.graphics.SurfaceTexture.class) {
+ if ((klass == android.graphics.SurfaceTexture.class) ||
+ (klass == android.view.SurfaceView.class)) {
return true;
}
@@ -725,6 +726,12 @@
* backward compatible cameras whereas other output classes are not guaranteed to be supported.
* </p>
*
+ * <p>Starting with Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}
+ * {@link android.view.SurfaceView} classes are also guaranteed to be supported and include
+ * the same resolutions as {@link android.graphics.SurfaceTexture}.
+ * Clients must set the desired SurfaceView resolution by calling
+ * {@link android.view.SurfaceHolder#setFixedSize}.</p>
+ *
* @param extension the extension type
* @param klass a non-{@code null} {@link Class} object reference
* @return non-modifiable list of available sizes or an empty list if the Surface output is not
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 19719a8..fbc0184 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -142,24 +142,18 @@
PackageManager.PERMISSION_GRANTED;
}
- mHandlerThread = new HandlerThread(TAG);
- mHandlerThread.start();
- mHandler = new Handler(mHandlerThread.getLooper());
mFoldStateListener = new FoldStateListener(context);
try {
- context.getSystemService(DeviceStateManager.class)
- .registerCallback(new HandlerExecutor(mHandler), mFoldStateListener);
+ context.getSystemService(DeviceStateManager.class).registerCallback(
+ new HandlerExecutor(CameraManagerGlobal.get().getDeviceStateHandler()),
+ mFoldStateListener);
} catch (IllegalStateException e) {
Log.v(TAG, "Failed to register device state listener!");
Log.v(TAG, "Device state dependent characteristics updates will not be functional!");
- mHandlerThread.quitSafely();
- mHandler = null;
mFoldStateListener = null;
}
}
- private HandlerThread mHandlerThread;
- private Handler mHandler;
private FoldStateListener mFoldStateListener;
/**
@@ -1645,6 +1639,9 @@
private ICameraService mCameraService;
private boolean mHasOpenCloseListenerPermission = false;
+ private HandlerThread mDeviceStateHandlerThread;
+ private Handler mDeviceStateHandler;
+
// Singleton, don't allow construction
private CameraManagerGlobal() { }
@@ -1658,6 +1655,18 @@
return gCameraManager;
}
+ public Handler getDeviceStateHandler() {
+ synchronized(mLock) {
+ if (mDeviceStateHandlerThread == null) {
+ mDeviceStateHandlerThread = new HandlerThread(TAG);
+ mDeviceStateHandlerThread.start();
+ mDeviceStateHandler = new Handler(mDeviceStateHandlerThread.getLooper());
+ }
+
+ return mDeviceStateHandler;
+ }
+ }
+
@Override
public IBinder asBinder() {
return this;
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 788302b..705afc5 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -1000,24 +1000,25 @@
* camera's crop region is set to maximum size, the FOV of the physical streams for the
* ultrawide lens will be the same as the logical stream, by making the crop region
* smaller than its active array size to compensate for the smaller focal length.</p>
- * <p>There are two ways for the application to capture RAW images from a logical camera
- * with RAW capability:</p>
+ * <p>For a logical camera, typically the underlying physical cameras have different RAW
+ * capabilities (such as resolution or CFA pattern). There are two ways for the
+ * application to capture RAW images from the logical camera:</p>
* <ul>
- * <li>Because the underlying physical cameras may have different RAW capabilities (such
- * as resolution or CFA pattern), to maintain backward compatibility, when a RAW stream
- * is configured, the camera device makes sure the default active physical camera remains
- * active and does not switch to other physical cameras. (One exception is that, if the
- * logical camera consists of identical image sensors and advertises multiple focalLength
- * due to different lenses, the camera device may generate RAW images from different
- * physical cameras based on the focalLength being set by the application.) This
- * backward-compatible approach usually results in loss of optical zoom, to telephoto
- * lens or to ultrawide lens.</li>
- * <li>Alternatively, to take advantage of the full zoomRatio range of the logical camera,
- * the application should use {@link android.hardware.camera2.MultiResolutionImageReader }
- * to capture RAW images from the currently active physical camera. Because different
- * physical camera may have different RAW characteristics, the application needs to use
- * the characteristics and result metadata of the active physical camera for the
- * relevant RAW metadata.</li>
+ * <li>If the logical camera has RAW capability, the application can create and use RAW
+ * streams in the same way as before. In case a RAW stream is configured, to maintain
+ * backward compatibility, the camera device makes sure the default active physical
+ * camera remains active and does not switch to other physical cameras. (One exception
+ * is that, if the logical camera consists of identical image sensors and advertises
+ * multiple focalLength due to different lenses, the camera device may generate RAW
+ * images from different physical cameras based on the focalLength being set by the
+ * application.) This backward-compatible approach usually results in loss of optical
+ * zoom, to telephoto lens or to ultrawide lens.</li>
+ * <li>Alternatively, if supported by the device,
+ * {@link android.hardware.camera2.MultiResolutionImageReader }
+ * can be used to capture RAW images from one of the underlying physical cameras (
+ * depending on current zoom level). Because different physical cameras may have
+ * different RAW characteristics, the application needs to use the characteristics
+ * and result metadata of the active physical camera for the relevant RAW metadata.</li>
* </ul>
* <p>The capture request and result metadata tags required for backward compatible camera
* functionalities will be solely based on the logical camera capability. On the other
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index 41c406d..db83e62 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -62,8 +62,6 @@
import android.os.HandlerThread;
import android.os.RemoteException;
import android.util.Log;
-import android.util.Pair;
-import android.util.Range;
import android.util.Size;
import android.view.Surface;
@@ -270,6 +268,10 @@
}
}
+ // The extension processing logic needs to be able to match images to capture results via
+ // image and result timestamps.
+ cameraOutput.setTimestampBase(OutputConfiguration.TIMESTAMP_BASE_SENSOR);
+ cameraOutput.setReadoutTimestampEnabled(false);
cameraOutput.setPhysicalCameraId(output.physicalCameraId);
outputList.add(cameraOutput);
mCameraConfigMap.put(cameraOutput.getSurface(), output);
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 3f85d44..c2b3656 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -60,7 +60,6 @@
import android.util.Log;
import android.util.LongSparseArray;
import android.util.Pair;
-import android.util.Range;
import android.util.Size;
import android.view.Surface;
@@ -478,13 +477,21 @@
ArrayList<CaptureStageImpl> sessionParamsList = new ArrayList<>();
ArrayList<OutputConfiguration> outputList = new ArrayList<>();
initializeRepeatingRequestPipeline();
- outputList.add(new OutputConfiguration(mCameraRepeatingSurface));
+ OutputConfiguration previewOutput = new OutputConfiguration(mCameraRepeatingSurface);
+ // The extension processing logic needs to be able to match images to capture results via
+ // image and result timestamps.
+ previewOutput.setTimestampBase(OutputConfiguration.TIMESTAMP_BASE_SENSOR);
+ previewOutput.setReadoutTimestampEnabled(false);
+ outputList.add(previewOutput);
CaptureStageImpl previewSessionParams = mPreviewExtender.onPresetSession();
if (previewSessionParams != null) {
sessionParamsList.add(previewSessionParams);
}
initializeBurstCapturePipeline();
- outputList.add(new OutputConfiguration(mCameraBurstSurface));
+ OutputConfiguration captureOutput = new OutputConfiguration(mCameraBurstSurface);
+ captureOutput.setTimestampBase(OutputConfiguration.TIMESTAMP_BASE_SENSOR);
+ captureOutput.setReadoutTimestampEnabled(false);
+ outputList.add(captureOutput);
CaptureStageImpl stillCaptureSessionParams = mImageExtender.onPresetSession();
if (stillCaptureSessionParams != null) {
sessionParamsList.add(stillCaptureSessionParams);
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
index 5222408..08111c5 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.graphics.ImageFormat;
import android.graphics.PixelFormat;
+import android.hardware.HardwareBuffer;
import android.hardware.camera2.CameraExtensionCharacteristics;
import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.params.StreamConfigurationMap;
@@ -149,6 +150,7 @@
SurfaceInfo surfaceInfo = querySurface(config.getSurface());
if ((surfaceInfo.mFormat ==
CameraExtensionCharacteristics.NON_PROCESSING_INPUT_FORMAT) ||
+ ((surfaceInfo.mUsage & HardwareBuffer.USAGE_COMPOSER_OVERLAY) != 0) ||
// The default RGBA_8888 is also implicitly supported because camera will
// internally override it to
// 'CameraExtensionCharacteristics.NON_PROCESSING_INPUT_FORMAT'
diff --git a/core/java/android/hardware/face/FaceAuthenticateOptions.java b/core/java/android/hardware/face/FaceAuthenticateOptions.java
index 4009fa7..1c6de04 100644
--- a/core/java/android/hardware/face/FaceAuthenticateOptions.java
+++ b/core/java/android/hardware/face/FaceAuthenticateOptions.java
@@ -52,7 +52,7 @@
}
/** The sensor id for this operation. */
- private final int mSensorId;
+ private int mSensorId;
private static int defaultSensorId() {
return -1;
}
@@ -299,6 +299,15 @@
}
/**
+ * The sensor id for this operation.
+ */
+ @DataClass.Generated.Member
+ public @NonNull FaceAuthenticateOptions setSensorId( int value) {
+ mSensorId = value;
+ return this;
+ }
+
+ /**
* The package name for that operation that should be used for
* {@link android.app.AppOpsManager} verification.
*
@@ -610,10 +619,10 @@
}
@DataClass.Generated(
- time = 1676508211385L,
+ time = 1677119626034L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/hardware/face/FaceAuthenticateOptions.java",
- inputSignatures = "private final int mUserId\nprivate final int mSensorId\nprivate final @android.hardware.biometrics.AuthenticateOptions.DisplayState int mDisplayState\npublic static final int AUTHENTICATE_REASON_UNKNOWN\npublic static final int AUTHENTICATE_REASON_STARTED_WAKING_UP\npublic static final int AUTHENTICATE_REASON_PRIMARY_BOUNCER_SHOWN\npublic static final int AUTHENTICATE_REASON_ASSISTANT_VISIBLE\npublic static final int AUTHENTICATE_REASON_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN\npublic static final int AUTHENTICATE_REASON_NOTIFICATION_PANEL_CLICKED\npublic static final int AUTHENTICATE_REASON_OCCLUDING_APP_REQUESTED\npublic static final int AUTHENTICATE_REASON_PICK_UP_GESTURE_TRIGGERED\npublic static final int AUTHENTICATE_REASON_QS_EXPANDED\npublic static final int AUTHENTICATE_REASON_SWIPE_UP_ON_BOUNCER\npublic static final int AUTHENTICATE_REASON_UDFPS_POINTER_DOWN\nprivate final @android.hardware.face.FaceAuthenticateOptions.AuthenticateReason int mAuthenticateReason\nprivate final @android.os.PowerManager.WakeReason int mWakeReason\nprivate @android.annotation.NonNull java.lang.String mOpPackageName\nprivate @android.annotation.Nullable java.lang.String mAttributionTag\nprivate static int defaultUserId()\nprivate static int defaultSensorId()\nprivate static int defaultDisplayState()\nprivate static int defaultAuthenticateReason()\nprivate static int defaultWakeReason()\nprivate static java.lang.String defaultOpPackageName()\nprivate static java.lang.String defaultAttributionTag()\nclass FaceAuthenticateOptions extends java.lang.Object implements [android.hardware.biometrics.AuthenticateOptions, android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true, genSetters=true, genEqualsHashCode=true)")
+ inputSignatures = "private final int mUserId\nprivate int mSensorId\nprivate final @android.hardware.biometrics.AuthenticateOptions.DisplayState int mDisplayState\npublic static final int AUTHENTICATE_REASON_UNKNOWN\npublic static final int AUTHENTICATE_REASON_STARTED_WAKING_UP\npublic static final int AUTHENTICATE_REASON_PRIMARY_BOUNCER_SHOWN\npublic static final int AUTHENTICATE_REASON_ASSISTANT_VISIBLE\npublic static final int AUTHENTICATE_REASON_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN\npublic static final int AUTHENTICATE_REASON_NOTIFICATION_PANEL_CLICKED\npublic static final int AUTHENTICATE_REASON_OCCLUDING_APP_REQUESTED\npublic static final int AUTHENTICATE_REASON_PICK_UP_GESTURE_TRIGGERED\npublic static final int AUTHENTICATE_REASON_QS_EXPANDED\npublic static final int AUTHENTICATE_REASON_SWIPE_UP_ON_BOUNCER\npublic static final int AUTHENTICATE_REASON_UDFPS_POINTER_DOWN\nprivate final @android.hardware.face.FaceAuthenticateOptions.AuthenticateReason int mAuthenticateReason\nprivate final @android.os.PowerManager.WakeReason int mWakeReason\nprivate @android.annotation.NonNull java.lang.String mOpPackageName\nprivate @android.annotation.Nullable java.lang.String mAttributionTag\nprivate static int defaultUserId()\nprivate static int defaultSensorId()\nprivate static int defaultDisplayState()\nprivate static int defaultAuthenticateReason()\nprivate static int defaultWakeReason()\nprivate static java.lang.String defaultOpPackageName()\nprivate static java.lang.String defaultAttributionTag()\nclass FaceAuthenticateOptions extends java.lang.Object implements [android.hardware.biometrics.AuthenticateOptions, android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true, genSetters=true, genEqualsHashCode=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 2857627..9d5073e 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -68,7 +68,7 @@
// by BiometricService. To start authentication after the clients are ready, use
// startPreparedClient().
@EnforcePermission("USE_BIOMETRIC_INTERNAL")
- void prepareForAuthentication(int sensorId, boolean requireConfirmation, IBinder token,
+ void prepareForAuthentication(boolean requireConfirmation, IBinder token,
long operationId, IBiometricSensorReceiver sensorReceiver,
in FaceAuthenticateOptions options, long requestId, int cookie,
boolean allowBackgroundAuthentication);
diff --git a/core/java/android/hardware/fingerprint/FingerprintAuthenticateOptions.java b/core/java/android/hardware/fingerprint/FingerprintAuthenticateOptions.java
index cecb317..763246e 100644
--- a/core/java/android/hardware/fingerprint/FingerprintAuthenticateOptions.java
+++ b/core/java/android/hardware/fingerprint/FingerprintAuthenticateOptions.java
@@ -46,7 +46,7 @@
}
/** The sensor id for this operation. */
- private final int mSensorId;
+ private int mSensorId;
private static int defaultSensorId() {
return SENSOR_ID_ANY;
}
@@ -176,6 +176,15 @@
}
/**
+ * The sensor id for this operation.
+ */
+ @DataClass.Generated.Member
+ public @NonNull FingerprintAuthenticateOptions setSensorId( int value) {
+ mSensorId = value;
+ return this;
+ }
+
+ /**
* The package name for that operation that should be used for
* {@link android.app.AppOpsManager} verification.
*
@@ -433,10 +442,10 @@
}
@DataClass.Generated(
- time = 1676508212083L,
+ time = 1677119626721L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/hardware/fingerprint/FingerprintAuthenticateOptions.java",
- inputSignatures = "private final int mUserId\nprivate final int mSensorId\nprivate final boolean mIgnoreEnrollmentState\nprivate final @android.hardware.biometrics.AuthenticateOptions.DisplayState int mDisplayState\nprivate @android.annotation.NonNull java.lang.String mOpPackageName\nprivate @android.annotation.Nullable java.lang.String mAttributionTag\nprivate static int defaultUserId()\nprivate static int defaultSensorId()\nprivate static boolean defaultIgnoreEnrollmentState()\nprivate static int defaultDisplayState()\nprivate static java.lang.String defaultOpPackageName()\nprivate static java.lang.String defaultAttributionTag()\nclass FingerprintAuthenticateOptions extends java.lang.Object implements [android.hardware.biometrics.AuthenticateOptions, android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true, genSetters=true, genEqualsHashCode=true)")
+ inputSignatures = "private final int mUserId\nprivate int mSensorId\nprivate final boolean mIgnoreEnrollmentState\nprivate final @android.hardware.biometrics.AuthenticateOptions.DisplayState int mDisplayState\nprivate @android.annotation.NonNull java.lang.String mOpPackageName\nprivate @android.annotation.Nullable java.lang.String mAttributionTag\nprivate static int defaultUserId()\nprivate static int defaultSensorId()\nprivate static boolean defaultIgnoreEnrollmentState()\nprivate static int defaultDisplayState()\nprivate static java.lang.String defaultOpPackageName()\nprivate static java.lang.String defaultAttributionTag()\nclass FingerprintAuthenticateOptions extends java.lang.Object implements [android.hardware.biometrics.AuthenticateOptions, android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true, genSetters=true, genEqualsHashCode=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index e3ae299..ec5749e 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -73,8 +73,8 @@
// by BiometricService. To start authentication after the clients are ready, use
// startPreparedClient().
@EnforcePermission("MANAGE_BIOMETRIC")
- void prepareForAuthentication(int sensorId, IBinder token, long operationId, int userId,
- IBiometricSensorReceiver sensorReceiver, String opPackageName, long requestId,
+ void prepareForAuthentication(IBinder token, long operationId,
+ IBiometricSensorReceiver sensorReceiver, in FingerprintAuthenticateOptions options, long requestId,
int cookie, boolean allowBackgroundAuthentication);
// Starts authentication with the previously prepared client.
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
index 76f857b..6bc0f6e 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -147,6 +147,18 @@
public static final String EXTRA_SEQUENCE = "seq";
/**
+ * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
+ * Int value representing the battery charging cycle count.
+ */
+ public static final String EXTRA_CYCLE_COUNT = "android.os.extra.CYCLE_COUNT";
+
+ /**
+ * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
+ * Int value representing the battery charging status.
+ */
+ public static final String EXTRA_CHARGING_STATUS = "android.os.extra.CHARGING_STATUS";
+
+ /**
* Extra for {@link android.content.Intent#ACTION_BATTERY_LEVEL_CHANGED}:
* Contains list of Bundles representing battery events
* @hide
@@ -190,6 +202,35 @@
/** Power source is dock. */
public static final int BATTERY_PLUGGED_DOCK = OsProtoEnums.BATTERY_PLUGGED_DOCK; // = 8
+ // values for "charge policy" property
+ /**
+ * Default policy (e.g. normal).
+ * @hide
+ */
+ @SystemApi
+ public static final int CHARGING_POLICY_DEFAULT = OsProtoEnums.CHARGING_POLICY_DEFAULT; // = 1
+ /**
+ * Optimized for battery health using static thresholds (e.g stop at 80%).
+ * @hide
+ */
+ @SystemApi
+ public static final int CHARGING_POLICY_ADAPTIVE_AON =
+ OsProtoEnums.CHARGING_POLICY_ADAPTIVE_AON; // = 2
+ /**
+ * Optimized for battery health using adaptive thresholds.
+ * @hide
+ */
+ @SystemApi
+ public static final int CHARGING_POLICY_ADAPTIVE_AC =
+ OsProtoEnums.CHARGING_POLICY_ADAPTIVE_AC; // = 3
+ /**
+ * Optimized for battery health, devices always connected to power.
+ * @hide
+ */
+ @SystemApi
+ public static final int CHARGING_POLICY_ADAPTIVE_LONGLIFE =
+ OsProtoEnums.CHARGING_POLICY_ADAPTIVE_LONGLIFE; // = 4
+
/** @hide */
public static final int BATTERY_PLUGGED_ANY =
BATTERY_PLUGGED_AC | BATTERY_PLUGGED_USB | BATTERY_PLUGGED_WIRELESS
@@ -254,6 +295,76 @@
*/
public static final int BATTERY_PROPERTY_STATUS = 6;
+ /**
+ * Battery manufacturing date is reported in epoch. The 0 timepoint
+ * begins at midnight Coordinated Universal Time (UTC) on January 1, 1970.
+ * It is a long integer in seconds.
+ *
+ * <p class="note">
+ * The sender must hold the {@link android.Manifest.permission#BATTERY_STATS} permission.
+ *
+ * Example: <code>
+ * // The value returned from the API can be used to create a Date, used
+ * // to set the time on a calendar and coverted to a string.
+ * import java.util.Date;
+ *
+ * mBatteryManager = mContext.getSystemService(BatteryManager.class);
+ * final long manufacturingDate =
+ * mBatteryManager.getLongProperty(BatteryManager.BATTERY_PROPERTY_MANUFACTURING_DATE);
+ * Date date = new Date(manufacturingDate);
+ * Calendar calendar = Calendar.getInstance();
+ * calendar.setTime(date);
+ * // Convert to yyyy-MM-dd HH:mm:ss format string
+ * SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ * String dateString = sdf.format(date);
+ * </code>
+ * @hide
+ */
+ @RequiresPermission(permission.BATTERY_STATS)
+ @SystemApi
+ public static final int BATTERY_PROPERTY_MANUFACTURING_DATE = 7;
+
+ /**
+ * The date of first usage is reported in epoch. The 0 timepoint
+ * begins at midnight Coordinated Universal Time (UTC) on January 1, 1970.
+ * It is a long integer in seconds.
+ *
+ * <p class="note">
+ * The sender must hold the {@link android.Manifest.permission#BATTERY_STATS} permission.
+ *
+ * {@link BATTERY_PROPERTY_MANUFACTURING_DATE for sample code}
+ * @hide
+ */
+ @RequiresPermission(permission.BATTERY_STATS)
+ @SystemApi
+ public static final int BATTERY_PROPERTY_FIRST_USAGE_DATE = 8;
+
+ /**
+ * Battery charging policy from a CHARGING_POLICY_* value..
+ *
+ * <p class="note">
+ * The sender must hold the {@link android.Manifest.permission#BATTERY_STATS} permission.
+ *
+ * @hide
+ */
+ @RequiresPermission(permission.BATTERY_STATS)
+ @SystemApi
+ public static final int BATTERY_PROPERTY_CHARGING_POLICY = 9;
+
+ /**
+ *
+ * Percentage representing the measured battery state of health (remaining
+ * estimated full charge capacity relative to the rated capacity in %).
+ *
+ * <p class="note">
+ * The sender must hold the {@link android.Manifest.permission#BATTERY_STATS} permission.
+ *
+ * @hide
+ */
+ @RequiresPermission(permission.BATTERY_STATS)
+ @SystemApi
+ public static final int BATTERY_PROPERTY_STATE_OF_HEALTH = 10;
+
private final Context mContext;
private final IBatteryStats mBatteryStats;
private final IBatteryPropertiesRegistrar mBatteryPropertiesRegistrar;
@@ -307,7 +418,6 @@
try {
BatteryProperty prop = new BatteryProperty();
-
if (mBatteryPropertiesRegistrar.getProperty(id, prop) == 0)
ret = prop.getLong();
else
diff --git a/core/java/android/os/CancellationSignalBeamer.java b/core/java/android/os/CancellationSignalBeamer.java
new file mode 100644
index 0000000..afb5ff7
--- /dev/null
+++ b/core/java/android/os/CancellationSignalBeamer.java
@@ -0,0 +1,325 @@
+/*
+ * 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.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.system.SystemCleaner;
+
+import java.lang.ref.Cleaner;
+import java.lang.ref.Reference;
+import java.util.HashMap;
+
+/**
+ * A transport for {@link CancellationSignal}, but unlike
+ * {@link CancellationSignal#createTransport()} doesn't require pre-creating the transport in the
+ * target process. Instead, cancellation is forwarded over the same IPC surface as the cancellable
+ * request.
+ *
+ * <p><strong>Important:</strong> For this to work, the following invariants must be held up:
+ * <ul>
+ * <li>A call to beam() <strong>MUST</strong> result in a call to close() on the result
+ * (otherwise, the token will be leaked and cancellation isn't propagated), and that call
+ * must happen after the call using the
+ * token is sent (otherwise, any concurrent cancellation may be lost). It is strongly
+ * recommended to use try-with-resources on the token.
+ * <li>The cancel(), forget() and cancellable operations transporting the token must either
+ * all be oneway on the same binder, or all be non-oneway to guarantee proper ordering.
+ * <li>A {@link CancellationSignal} <strong>SHOULD</strong> be used only once, as there
+ * can only be a single {@link android.os.CancellationSignal.OnCancelListener OnCancelListener}.
+ *
+ * </ul>
+ * <p>Caveats:
+ * <ul>
+ * <li>Cancellation is only ever dispatched after the token is closed, and thus after the
+ * call performing the cancellable operation (if the invariants are followed). The operation
+ * must therefore not block the incoming binder thread, or cancellation won't be possible.
+ * <li>Consequently, in the unlikely event that the sender dies right after beaming an already
+ * cancelled {@link CancellationSignal}, the cancellation may be lost (unlike with
+ * {@link CancellationSignal#createTransport()}).
+ * <li>The forwarding OnCancelListener is set in the implied finally phase of try-with-resources
+ * / when closing the token. If the receiver is in the same process, and the signal is
+ * already cancelled, this may invoke the target's OnCancelListener during that phase.
+ * </ul>
+ *
+ *
+ * <p>Usage:
+ * <pre>
+ * // Sender:
+ *
+ * class FooManager {
+ * var mCancellationSignalSender = new CancellationSignalBeamer.Sender() {
+ * @Override
+ * public void onCancel(IBinder token) { remoteIFooService.onCancelToken(token); }
+ *
+ * @Override
+ * public void onForget(IBinder token) { remoteIFooService.onForgetToken(token); }
+ * };
+ *
+ * public void doCancellableOperation(..., CancellationSignal cs) {
+ * try (var csToken = mCancellationSignalSender.beam(cs)) {
+ * remoteIFooService.doCancellableOperation(..., csToken);
+ * }
+ * }
+ * }
+ *
+ * // Receiver:
+ *
+ * class FooManagerService extends IFooService.Stub {
+ * var mCancellationSignalReceiver = new CancellationSignalBeamer.Receiver();
+ *
+ * @Override
+ * public void doCancellableOperation(..., IBinder csToken) {
+ * CancellationSignal cs = mCancellationSignalReceiver.unbeam(csToken))
+ * // ...
+ * }
+ *
+ * @Override
+ * public void onCancelToken(..., IBinder csToken) {
+ * mCancellationSignalReceiver.cancelToken(csToken))
+ * }
+ *
+ * @Override
+ * public void onForgetToken(..., IBinder csToken) {
+ * mCancellationSignalReceiver.forgetToken(csToken))
+ * }
+ * }
+ *
+ * </pre>
+ *
+ * @hide
+ */
+public class CancellationSignalBeamer {
+
+ static final Cleaner sCleaner = SystemCleaner.cleaner();
+
+ /** The sending side of an {@link CancellationSignalBeamer} */
+ public abstract static class Sender {
+
+ /**
+ * Beams a {@link CancellationSignal} through an existing Binder interface.
+ *
+ * @param cs the {@code CancellationSignal} to beam, or {@code null}.
+ * @return an {@link IBinder} token. MUST be {@link CloseableToken#close}d <em>after</em>
+ * the binder call transporting it to the remote process, best with
+ * try-with-resources. {@code null} if {@code cs} was {@code null}.
+ */
+ // TODO(b/254888024): @MustBeClosed
+ @Nullable
+ public CloseableToken beam(@Nullable CancellationSignal cs) {
+ if (cs == null) {
+ return null;
+ }
+ return new Token(this, cs);
+ }
+
+ /**
+ * A {@link #beam}ed {@link CancellationSignal} was closed.
+ *
+ * MUST be forwarded to {@link Receiver#cancel} with proper ordering. See
+ * {@link CancellationSignalBeamer} for details.
+ */
+ public abstract void onCancel(IBinder token);
+
+ /**
+ * A {@link #beam}ed {@link CancellationSignal} was GC'd.
+ *
+ * MUST be forwarded to {@link Receiver#forget} with proper ordering. See
+ * {@link CancellationSignalBeamer} for details.
+ */
+ public abstract void onForget(IBinder token);
+
+ private static class Token extends Binder implements CloseableToken, Runnable {
+
+ private final Sender mSender;
+ private Preparer mPreparer;
+
+ private Token(Sender sender, CancellationSignal signal) {
+ mSender = sender;
+ mPreparer = new Preparer(sender, signal, this);
+ }
+
+ @Override
+ public void close() {
+ Preparer preparer = mPreparer;
+ mPreparer = null;
+ if (preparer != null) {
+ preparer.setup();
+ }
+ }
+
+ @Override
+ public void run() {
+ mSender.onForget(this);
+ }
+
+ private static class Preparer implements CancellationSignal.OnCancelListener {
+ private final Sender mSender;
+ private final CancellationSignal mSignal;
+ private final Token mToken;
+
+ private Preparer(Sender sender, CancellationSignal signal, Token token) {
+ mSender = sender;
+ mSignal = signal;
+ mToken = token;
+ }
+
+ void setup() {
+ sCleaner.register(this, mToken);
+ mSignal.setOnCancelListener(this);
+ }
+
+ @Override
+ public void onCancel() {
+ try {
+ mSender.onCancel(mToken);
+ } finally {
+ // Make sure we dispatch onCancel before the cleaner can run.
+ Reference.reachabilityFence(this);
+ }
+ }
+ }
+ }
+
+ /**
+ * A {@link #beam}ed {@link CancellationSignal} ready for sending over Binder.
+ *
+ * MUST be closed <em>after</em> it is sent over binder, ideally through try-with-resources.
+ */
+ public interface CloseableToken extends IBinder, AutoCloseable {
+ @Override
+ void close(); // No throws
+ }
+ }
+
+ /** The receiving side of a {@link CancellationSignalBeamer}. */
+ public static class Receiver implements IBinder.DeathRecipient {
+ private final HashMap<IBinder, CancellationSignal> mTokenMap = new HashMap<>();
+ private final boolean mCancelOnSenderDeath;
+
+ /**
+ * Constructs a new {@code Receiver}.
+ *
+ * @param cancelOnSenderDeath if true, {@link CancellationSignal}s obtained from
+ * {@link #unbeam} are automatically {@link #cancel}led if the sender token
+ * {@link Binder#linkToDeath dies}; otherwise they are simnply dropped. Note: if the
+ * sending process drops all references to the {@link CancellationSignal} before
+ * process death, the cancellation is not guaranteed.
+ */
+ public Receiver(boolean cancelOnSenderDeath) {
+ mCancelOnSenderDeath = cancelOnSenderDeath;
+ }
+
+ /**
+ * Unbeams a token that was obtained via {@link Sender#beam} and turns it back into a
+ * {@link CancellationSignal}.
+ *
+ * A subsequent call to {@link #cancel} with the same token will cancel the returned
+ * {@code CancellationSignal}.
+ *
+ * @param token a token that was obtained from {@link Sender}, possibly in a remote process.
+ * @return a {@link CancellationSignal} linked to the given token.
+ */
+ @Nullable
+ public CancellationSignal unbeam(@Nullable IBinder token) {
+ if (token == null) {
+ return null;
+ }
+ synchronized (this) {
+ CancellationSignal cs = mTokenMap.get(token);
+ if (cs != null) {
+ return cs;
+ }
+
+ cs = new CancellationSignal();
+ mTokenMap.put(token, cs);
+ try {
+ token.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ dead(token);
+ }
+ return cs;
+ }
+ }
+
+ /**
+ * Forgets state associated with the given token (if any).
+ *
+ * Subsequent calls to {@link #cancel} or binder death notifications on the token will not
+ * have any effect.
+ *
+ * This MUST be invoked when forwarding {@link Sender#onForget}, otherwise the token and
+ * {@link CancellationSignal} will leak if the token was ever {@link #unbeam}ed.
+ *
+ * Optionally, the receiving service logic may also invoke this if it can guarantee that
+ * the unbeamed CancellationSignal isn't needed anymore (i.e. the cancellable operation
+ * using the CancellationSignal has been fully completed).
+ *
+ * @param token the token to forget. No-op if {@code null}.
+ */
+ public void forget(@Nullable IBinder token) {
+ synchronized (this) {
+ if (mTokenMap.remove(token) != null) {
+ token.unlinkToDeath(this, 0);
+ }
+ }
+ }
+
+ /**
+ * Cancels the {@link CancellationSignal} associated with the given token (if any).
+ *
+ * This MUST be invoked when forwarding {@link Sender#onCancel}, otherwise the token and
+ * {@link CancellationSignal} will leak if the token was ever {@link #unbeam}ed.
+ *
+ * Optionally, the receiving service logic may also invoke this if it can guarantee that
+ * the unbeamed CancellationSignal isn't needed anymore (i.e. the cancellable operation
+ * using the CancellationSignal has been fully completed).
+ *
+ * @param token the token to forget. No-op if {@code null}.
+ */
+ public void cancel(@Nullable IBinder token) {
+ CancellationSignal cs;
+ synchronized (this) {
+ cs = mTokenMap.get(token);
+ if (cs != null) {
+ forget(token);
+ } else {
+ return;
+ }
+ }
+ cs.cancel();
+ }
+
+ private void dead(@NonNull IBinder token) {
+ if (mCancelOnSenderDeath) {
+ cancel(token);
+ } else {
+ forget(token);
+ }
+ }
+
+ @Override
+ public void binderDied(@NonNull IBinder who) {
+ dead(who);
+ }
+
+ @Override
+ public void binderDied() {
+ throw new RuntimeException("unreachable");
+ }
+ }
+}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index bbf7f81..de94b86 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1641,6 +1641,7 @@
DISALLOW_ADD_WIFI_CONFIG,
DISALLOW_CELLULAR_2G,
DISALLOW_ULTRA_WIDEBAND_RADIO,
+ DISALLOW_GRANT_ADMIN,
})
@Retention(RetentionPolicy.SOURCE)
public @interface UserRestrictionKey {}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 54e4909..acb1f5b 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -116,6 +116,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
* The Settings provider contains global system-level device preferences.
@@ -2978,19 +2979,22 @@
}
private static final class GenerationTracker {
- private final MemoryIntArray mArray;
- private final Runnable mErrorHandler;
+ @NonNull private final String mName;
+ @NonNull private final MemoryIntArray mArray;
+ @NonNull private final Consumer<String> mErrorHandler;
private final int mIndex;
private int mCurrentGeneration;
- public GenerationTracker(@NonNull MemoryIntArray array, int index,
- int generation, Runnable errorHandler) {
+ GenerationTracker(@NonNull String name, @NonNull MemoryIntArray array, int index,
+ int generation, Consumer<String> errorHandler) {
+ mName = name;
mArray = array;
mIndex = index;
mErrorHandler = errorHandler;
mCurrentGeneration = generation;
}
+ // This method also updates the obsolete generation code stored locally
public boolean isGenerationChanged() {
final int currentGeneration = readCurrentGeneration();
if (currentGeneration >= 0) {
@@ -3011,9 +3015,7 @@
return mArray.get(mIndex);
} catch (IOException e) {
Log.e(TAG, "Error getting current generation", e);
- if (mErrorHandler != null) {
- mErrorHandler.run();
- }
+ mErrorHandler.accept(mName);
}
return -1;
}
@@ -3023,9 +3025,6 @@
mArray.close();
} catch (IOException e) {
Log.e(TAG, "Error closing backing array", e);
- if (mErrorHandler != null) {
- mErrorHandler.run();
- }
}
}
}
@@ -3088,8 +3087,21 @@
private final ArraySet<String> mAllFields;
private final ArrayMap<String, Integer> mReadableFieldsWithMaxTargetSdk;
+ // Mapping from the name of a setting (or the prefix of a namespace) to a generation tracker
@GuardedBy("this")
- private GenerationTracker mGenerationTracker;
+ private ArrayMap<String, GenerationTracker> mGenerationTrackers = new ArrayMap<>();
+
+ private Consumer<String> mGenerationTrackerErrorHandler = (String name) -> {
+ synchronized (NameValueCache.this) {
+ Log.e(TAG, "Error accessing generation tracker - removing");
+ final GenerationTracker tracker = mGenerationTrackers.get(name);
+ if (tracker != null) {
+ tracker.destroy();
+ mGenerationTrackers.remove(name);
+ }
+ mValues.remove(name);
+ }
+ };
<T extends NameValueTable> NameValueCache(Uri uri, String getCommand,
String setCommand, String deleteCommand, ContentProviderHolder providerHolder,
@@ -3178,6 +3190,43 @@
@UnsupportedAppUsage
public String getStringForUser(ContentResolver cr, String name, final int userHandle) {
+ final boolean isSelf = (userHandle == UserHandle.myUserId());
+ int currentGeneration = -1;
+ boolean needsGenerationTracker = false;
+
+ if (isSelf) {
+ synchronized (NameValueCache.this) {
+ final GenerationTracker generationTracker = mGenerationTrackers.get(name);
+ if (generationTracker != null) {
+ if (generationTracker.isGenerationChanged()) {
+ if (DEBUG) {
+ Log.i(TAG, "Generation changed for setting:" + name
+ + " type:" + mUri.getPath()
+ + " in package:" + cr.getPackageName()
+ + " and user:" + userHandle);
+ }
+ mValues.remove(name);
+ } else if (mValues.containsKey(name)) {
+ if (DEBUG) {
+ Log.i(TAG, "Cache hit for setting:" + name);
+ }
+ return mValues.get(name);
+ }
+ currentGeneration = generationTracker.getCurrentGeneration();
+ } else {
+ needsGenerationTracker = true;
+ }
+ }
+ } else {
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "get setting for user " + userHandle
+ + " by user " + UserHandle.myUserId() + " so skipping cache");
+ }
+ }
+ if (DEBUG) {
+ Log.i(TAG, "Cache miss for setting:" + name);
+ }
+
// Check if the target settings key is readable. Reject if the caller is not system and
// is trying to access a settings key defined in the Settings.Secure, Settings.System or
// Settings.Global and is not annotated as @Readable.
@@ -3211,31 +3260,6 @@
}
}
- final boolean isSelf = (userHandle == UserHandle.myUserId());
- int currentGeneration = -1;
- if (isSelf) {
- synchronized (NameValueCache.this) {
- if (mGenerationTracker != null) {
- if (mGenerationTracker.isGenerationChanged()) {
- if (DEBUG) {
- Log.i(TAG, "Generation changed for type:"
- + mUri.getPath() + " in package:"
- + cr.getPackageName() +" and user:" + userHandle);
- }
- mValues.clear();
- } else if (mValues.containsKey(name)) {
- return mValues.get(name);
- }
- if (mGenerationTracker != null) {
- currentGeneration = mGenerationTracker.getCurrentGeneration();
- }
- }
- }
- } else {
- if (LOCAL_LOGV) Log.v(TAG, "get setting for user " + userHandle
- + " by user " + UserHandle.myUserId() + " so skipping cache");
- }
-
IContentProvider cp = mProviderHolder.getProvider(cr);
// Try the fast path first, not using query(). If this
@@ -3244,24 +3268,17 @@
// interface.
if (mCallGetCommand != null) {
try {
- Bundle args = null;
+ Bundle args = new Bundle();
if (!isSelf) {
- args = new Bundle();
args.putInt(CALL_METHOD_USER_KEY, userHandle);
}
- boolean needsGenerationTracker = false;
- synchronized (NameValueCache.this) {
- if (isSelf && mGenerationTracker == null) {
- needsGenerationTracker = true;
- if (args == null) {
- args = new Bundle();
- }
- args.putString(CALL_METHOD_TRACK_GENERATION_KEY, null);
- if (DEBUG) {
- Log.i(TAG, "Requested generation tracker for type: "+ mUri.getPath()
- + " in package:" + cr.getPackageName() +" and user:"
- + userHandle);
- }
+ if (needsGenerationTracker) {
+ args.putString(CALL_METHOD_TRACK_GENERATION_KEY, null);
+ if (DEBUG) {
+ Log.i(TAG, "Requested generation tracker for setting:" + name
+ + " type:" + mUri.getPath()
+ + " in package:" + cr.getPackageName()
+ + " and user:" + userHandle);
}
}
Bundle b;
@@ -3298,33 +3315,24 @@
final int generation = b.getInt(
CALL_METHOD_GENERATION_KEY, 0);
if (DEBUG) {
- Log.i(TAG, "Received generation tracker for type:"
- + mUri.getPath() + " in package:"
- + cr.getPackageName() + " and user:"
- + userHandle + " with index:" + index);
+ Log.i(TAG, "Received generation tracker for setting:"
+ + name
+ + " type:" + mUri.getPath()
+ + " in package:" + cr.getPackageName()
+ + " and user:" + userHandle
+ + " with index:" + index);
}
- if (mGenerationTracker != null) {
- mGenerationTracker.destroy();
- }
- mGenerationTracker = new GenerationTracker(array, index,
- generation, () -> {
- synchronized (NameValueCache.this) {
- Log.e(TAG, "Error accessing generation"
- + " tracker - removing");
- if (mGenerationTracker != null) {
- GenerationTracker generationTracker =
- mGenerationTracker;
- mGenerationTracker = null;
- generationTracker.destroy();
- mValues.clear();
- }
- }
- });
+ mGenerationTrackers.put(name, new GenerationTracker(name,
+ array, index, generation,
+ mGenerationTrackerErrorHandler));
currentGeneration = generation;
}
}
- if (mGenerationTracker != null && currentGeneration ==
- mGenerationTracker.getCurrentGeneration()) {
+ if (mGenerationTrackers.get(name) != null && currentGeneration
+ == mGenerationTrackers.get(name).getCurrentGeneration()) {
+ if (DEBUG) {
+ Log.i(TAG, "Updating cache for setting:" + name);
+ }
mValues.put(name, value);
}
}
@@ -3367,15 +3375,14 @@
String value = c.moveToNext() ? c.getString(0) : null;
synchronized (NameValueCache.this) {
- if (mGenerationTracker != null
- && currentGeneration == mGenerationTracker.getCurrentGeneration()) {
+ if (mGenerationTrackers.get(name) != null && currentGeneration
+ == mGenerationTrackers.get(name).getCurrentGeneration()) {
+ if (DEBUG) {
+ Log.i(TAG, "Updating cache for setting:" + name + " using query");
+ }
mValues.put(name, value);
}
}
- if (LOCAL_LOGV) {
- Log.v(TAG, "cache miss [" + mUri.getLastPathSegment() + "]: " +
- name + " = " + (value == null ? "(null)" : value));
- }
return value;
} catch (RemoteException e) {
Log.w(TAG, "Can't get key " + name + " from " + mUri, e);
@@ -3409,18 +3416,29 @@
Config.enforceReadPermission(namespace);
ArrayMap<String, String> keyValues = new ArrayMap<>();
int currentGeneration = -1;
+ boolean needsGenerationTracker = false;
synchronized (NameValueCache.this) {
- if (mGenerationTracker != null) {
- if (mGenerationTracker.isGenerationChanged()) {
+ final GenerationTracker generationTracker = mGenerationTrackers.get(prefix);
+ if (generationTracker != null) {
+ if (generationTracker.isGenerationChanged()) {
if (DEBUG) {
- Log.i(TAG, "Generation changed for type:" + mUri.getPath()
+ Log.i(TAG, "Generation changed for prefix:" + prefix
+ + " type:" + mUri.getPath()
+ " in package:" + cr.getPackageName());
}
- mValues.clear();
+ for (int i = 0; i < mValues.size(); ++i) {
+ String key = mValues.keyAt(i);
+ if (key.startsWith(prefix)) {
+ mValues.remove(key);
+ }
+ }
} else {
boolean prefixCached = mValues.containsKey(prefix);
if (prefixCached) {
+ if (DEBUG) {
+ Log.i(TAG, "Cache hit for prefix:" + prefix);
+ }
if (!names.isEmpty()) {
for (String name : names) {
if (mValues.containsKey(name)) {
@@ -3440,9 +3458,9 @@
return keyValues;
}
}
- if (mGenerationTracker != null) {
- currentGeneration = mGenerationTracker.getCurrentGeneration();
- }
+ currentGeneration = generationTracker.getCurrentGeneration();
+ } else {
+ needsGenerationTracker = true;
}
}
@@ -3450,20 +3468,20 @@
// No list command specified, return empty map
return keyValues;
}
+ if (DEBUG) {
+ Log.i(TAG, "Cache miss for prefix:" + prefix);
+ }
IContentProvider cp = mProviderHolder.getProvider(cr);
try {
Bundle args = new Bundle();
args.putString(Settings.CALL_METHOD_PREFIX_KEY, prefix);
- boolean needsGenerationTracker = false;
- synchronized (NameValueCache.this) {
- if (mGenerationTracker == null) {
- needsGenerationTracker = true;
- args.putString(CALL_METHOD_TRACK_GENERATION_KEY, null);
- if (DEBUG) {
- Log.i(TAG, "Requested generation tracker for type: "
- + mUri.getPath() + " in package:" + cr.getPackageName());
- }
+ if (needsGenerationTracker) {
+ args.putString(CALL_METHOD_TRACK_GENERATION_KEY, null);
+ if (DEBUG) {
+ Log.i(TAG, "Requested generation tracker for prefix:" + prefix
+ + " type: " + mUri.getPath()
+ + " in package:" + cr.getPackageName());
}
}
@@ -3516,32 +3534,22 @@
final int generation = b.getInt(
CALL_METHOD_GENERATION_KEY, 0);
if (DEBUG) {
- Log.i(TAG, "Received generation tracker for type:"
- + mUri.getPath() + " in package:"
- + cr.getPackageName() + " with index:" + index);
+ Log.i(TAG, "Received generation tracker for prefix:" + prefix
+ + " type:" + mUri.getPath()
+ + " in package:" + cr.getPackageName()
+ + " with index:" + index);
}
- if (mGenerationTracker != null) {
- mGenerationTracker.destroy();
- }
- mGenerationTracker = new GenerationTracker(array, index,
- generation, () -> {
- synchronized (NameValueCache.this) {
- Log.e(TAG, "Error accessing generation tracker"
- + " - removing");
- if (mGenerationTracker != null) {
- GenerationTracker generationTracker =
- mGenerationTracker;
- mGenerationTracker = null;
- generationTracker.destroy();
- mValues.clear();
- }
- }
- });
+ mGenerationTrackers.put(prefix,
+ new GenerationTracker(prefix, array, index, generation,
+ mGenerationTrackerErrorHandler));
currentGeneration = generation;
}
}
- if (mGenerationTracker != null && currentGeneration
- == mGenerationTracker.getCurrentGeneration()) {
+ if (mGenerationTrackers.get(prefix) != null && currentGeneration
+ == mGenerationTrackers.get(prefix).getCurrentGeneration()) {
+ if (DEBUG) {
+ Log.i(TAG, "Updating cache for prefix:" + prefix);
+ }
// cache the complete list of flags for the namespace
mValues.putAll(flagsToValues);
// Adding the prefix as a signal that the prefix is cached.
@@ -3557,11 +3565,11 @@
public void clearGenerationTrackerForTest() {
synchronized (NameValueCache.this) {
- if (mGenerationTracker != null) {
- mGenerationTracker.destroy();
+ for (int i = 0; i < mGenerationTrackers.size(); i++) {
+ mGenerationTrackers.valueAt(i).destroy();
}
+ mGenerationTrackers.clear();
mValues.clear();
- mGenerationTracker = null;
}
}
}
diff --git a/core/java/android/service/credentials/BeginGetCredentialOption.java b/core/java/android/service/credentials/BeginGetCredentialOption.java
index 81b9f22..1ad0424 100644
--- a/core/java/android/service/credentials/BeginGetCredentialOption.java
+++ b/core/java/android/service/credentials/BeginGetCredentialOption.java
@@ -116,12 +116,12 @@
* @param id the unique id associated with this option
* @param type the requested credential type
* @param candidateQueryData the request candidateQueryData
- * @throws IllegalArgumentException If type is empty.
+ * @throws IllegalArgumentException If id or type is empty.
*/
public BeginGetCredentialOption(
@NonNull String id, @NonNull String type,
@NonNull Bundle candidateQueryData) {
- mId = id;
+ mId = Preconditions.checkStringNotEmpty(id, "id must not be empty");
mType = Preconditions.checkStringNotEmpty(type, "type must not be empty");
Bundle bundle = new Bundle();
bundle.putAll(candidateQueryData);
diff --git a/core/java/android/service/credentials/CallingAppInfo.java b/core/java/android/service/credentials/CallingAppInfo.java
index fba91d4..e755581 100644
--- a/core/java/android/service/credentials/CallingAppInfo.java
+++ b/core/java/android/service/credentials/CallingAppInfo.java
@@ -17,6 +17,7 @@
package android.service.credentials;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.pm.SigningInfo;
import android.os.Parcel;
import android.os.Parcelable;
@@ -32,6 +33,8 @@
public final class CallingAppInfo implements Parcelable {
@NonNull private final String mPackageName;
@NonNull private final SigningInfo mSigningInfo;
+ @Nullable
+ private final String mOrigin;
/**
* Constructs a new instance.
@@ -41,14 +44,31 @@
*/
public CallingAppInfo(@NonNull String packageName,
@NonNull SigningInfo signingInfo) {
+ this(packageName, signingInfo, /*origin=*/ null);
+ }
+
+ /**
+ * Constructs a new instance.
+ *
+ * @param packageName - the package name of the calling app
+ * @param signingInfo - the signing info on the calling app
+ * @param origin - the origin that the calling app wants to use when making request on behalf of
+ * other
+ * @throws IllegalArgumentException If {@code packageName} is null or empty.
+ * @throws NullPointerException If {@code signingInfo} is null.
+ */
+ public CallingAppInfo(@NonNull String packageName,
+ @NonNull SigningInfo signingInfo, @Nullable String origin) {
mPackageName = Preconditions.checkStringNotEmpty(packageName, "package name"
+ "must not be null or empty");
mSigningInfo = Objects.requireNonNull(signingInfo);
+ mOrigin = origin;
}
private CallingAppInfo(@NonNull Parcel in) {
mPackageName = in.readString8();
mSigningInfo = in.readTypedObject(SigningInfo.CREATOR);
+ mOrigin = in.readString8();
}
public static final @NonNull Creator<CallingAppInfo> CREATOR = new Creator<CallingAppInfo>() {
@@ -76,6 +96,22 @@
return mSigningInfo;
}
+ /**
+ * Returns the origin of the calling app if set otherwise returns null.
+ * This value is set only if the origin is different than that of the calling app,
+ * and should be expected from privileged callers(browsers) only when making request on behalf
+ * of other applications.
+ *
+ * Android system makes sure that only applications that poses the permission
+ * {@link android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN} can set the origin on
+ * the incoming {@link android.credentials.GetCredentialRequest} or
+ * {@link android.credentials.CreateCredentialRequest}.
+ */
+ @Nullable
+ public String getOrigin() {
+ return mOrigin;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -85,6 +121,7 @@
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString8(mPackageName);
dest.writeTypedObject(mSigningInfo, flags);
+ dest.writeString8(mOrigin);
}
@Override
@@ -97,6 +134,7 @@
} else {
builder.append(", mSigningInfo: null");
}
+ builder.append(",mOrigin: " + mOrigin);
builder.append(" }");
return builder.toString();
}
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index eb9901a..5c2b389 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -953,7 +953,7 @@
private static Uri safeUri(TypedXmlPullParser parser, String att) {
final String val = parser.getAttributeValue(null, att);
- if (TextUtils.isEmpty(val)) return null;
+ if (val == null) return null;
return Uri.parse(val);
}
diff --git a/core/java/android/service/voice/AbstractDetector.java b/core/java/android/service/voice/AbstractDetector.java
index 466bc05..644a2bf 100644
--- a/core/java/android/service/voice/AbstractDetector.java
+++ b/core/java/android/service/voice/AbstractDetector.java
@@ -77,6 +77,13 @@
mIsDetectorActive = new AtomicBoolean(true);
}
+ boolean isSameToken(IBinder token) {
+ if (token == null) {
+ return false;
+ }
+ return mToken == token;
+ }
+
/**
* Method to be called for the detector to ready/register itself with underlying system
* services.
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 50ac1f3..b1dc686 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -1707,6 +1707,13 @@
}
}
+ void onDetectorRemoteException() {
+ Message.obtain(mHandler, MSG_DETECTION_ERROR,
+ new HotwordDetectionServiceFailure(
+ HotwordDetectionServiceFailure.ERROR_CODE_REMOTE_EXCEPTION,
+ "Detector remote exception occurs")).sendToTarget();
+ }
+
class MyHandler extends Handler {
MyHandler(@NonNull Looper looper) {
super(looper);
diff --git a/core/java/android/service/voice/IVoiceInteractionService.aidl b/core/java/android/service/voice/IVoiceInteractionService.aidl
index 6a54606..491056e 100644
--- a/core/java/android/service/voice/IVoiceInteractionService.aidl
+++ b/core/java/android/service/voice/IVoiceInteractionService.aidl
@@ -32,4 +32,5 @@
in IVoiceActionCheckCallback callback);
void prepareToShowSession(in Bundle args, int flags);
void showSessionFailed(in Bundle args);
+ void detectorRemoteExceptionOccurred(in IBinder token, int detectorType);
}
diff --git a/core/java/android/service/voice/SoftwareHotwordDetector.java b/core/java/android/service/voice/SoftwareHotwordDetector.java
index d4b6f3b..767fe37 100644
--- a/core/java/android/service/voice/SoftwareHotwordDetector.java
+++ b/core/java/android/service/voice/SoftwareHotwordDetector.java
@@ -77,6 +77,13 @@
DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE);
}
+ void onDetectorRemoteException() {
+ Binder.withCleanCallingIdentity(() -> mExecutor.execute(() ->
+ mCallback.onFailure(new HotwordDetectionServiceFailure(
+ HotwordDetectionServiceFailure.ERROR_CODE_REMOTE_EXCEPTION,
+ "Detector remote exception occurs"))));
+ }
+
@RequiresPermission(RECORD_AUDIO)
@Override
public boolean startRecognition() throws IllegalDetectorStateException {
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index a684e41..fcc64b0 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -36,8 +36,8 @@
import android.content.Intent;
import android.hardware.soundtrigger.KeyphraseEnrollmentInfo;
import android.hardware.soundtrigger.SoundTrigger;
-import android.media.voice.KeyphraseModelManager;
import android.media.permission.Identity;
+import android.media.voice.KeyphraseModelManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@@ -180,6 +180,14 @@
VoiceInteractionService::onShowSessionFailed,
VoiceInteractionService.this, args));
}
+
+ @Override
+ public void detectorRemoteExceptionOccurred(@NonNull IBinder token, int detectorType) {
+ Log.d(TAG, "detectorRemoteExceptionOccurred");
+ Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage(
+ VoiceInteractionService::onDetectorRemoteException,
+ VoiceInteractionService.this, token, detectorType));
+ }
};
IVoiceInteractionManagerService mSystemService;
@@ -192,6 +200,28 @@
private final Set<HotwordDetector> mActiveDetectors = new ArraySet<>();
+
+ private void onDetectorRemoteException(@NonNull IBinder token, int detectorType) {
+ Log.d(TAG, "onDetectorRemoteException for " + HotwordDetector.detectorTypeToString(
+ detectorType));
+ mActiveDetectors.forEach(detector -> {
+ // TODO: handle normal detector, VQD
+ if (detectorType == HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP
+ && detector instanceof AlwaysOnHotwordDetector) {
+ AlwaysOnHotwordDetector alwaysOnDetector = (AlwaysOnHotwordDetector) detector;
+ if (alwaysOnDetector.isSameToken(token)) {
+ alwaysOnDetector.onDetectorRemoteException();
+ }
+ } else if (detectorType == HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE
+ && detector instanceof SoftwareHotwordDetector) {
+ SoftwareHotwordDetector softwareDetector = (SoftwareHotwordDetector) detector;
+ if (softwareDetector.isSameToken(token)) {
+ softwareDetector.onDetectorRemoteException();
+ }
+ }
+ });
+ }
+
/**
* Called when a user has activated an affordance to launch voice assist from the Keyguard.
*
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 12cd523..f53abce 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -2198,6 +2198,11 @@
}
mCreated = false;
}
+
+ if (mSurfaceControl != null) {
+ mSurfaceControl.release();
+ mSurfaceControl = null;
+ }
}
private final DisplayListener mDisplayListener = new DisplayListener() {
diff --git a/core/java/android/telephony/CellBroadcastIntents.java b/core/java/android/telephony/CellBroadcastIntents.java
index c3ca286..b9ad773 100644
--- a/core/java/android/telephony/CellBroadcastIntents.java
+++ b/core/java/android/telephony/CellBroadcastIntents.java
@@ -104,7 +104,7 @@
* Put the phone ID and sub ID into an intent as extras.
*/
private static void putPhoneIdAndSubIdExtra(Context context, Intent intent, int phoneId) {
- int subId = getSubIdForPhone(context, phoneId);
+ int subId = SubscriptionManager.getSubscriptionId(phoneId);
if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
intent.putExtra("subscription", subId);
intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
@@ -112,22 +112,4 @@
intent.putExtra("phone", phoneId);
intent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, phoneId);
}
-
- /**
- * Get the subscription ID for a phone ID, or INVALID_SUBSCRIPTION_ID if the phone does not
- * have an active sub
- * @param phoneId the phoneId to use
- * @return the associated sub id
- */
- private static int getSubIdForPhone(Context context, int phoneId) {
- SubscriptionManager subMan =
- (SubscriptionManager) context.getSystemService(
- Context.TELEPHONY_SUBSCRIPTION_SERVICE);
- int[] subIds = subMan.getSubscriptionIds(phoneId);
- if (subIds != null) {
- return subIds[0];
- } else {
- return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
- }
- }
}
diff --git a/core/java/android/util/NtpTrustedTime.java b/core/java/android/util/NtpTrustedTime.java
index 98c0d7f..5aa0f59 100644
--- a/core/java/android/util/NtpTrustedTime.java
+++ b/core/java/android/util/NtpTrustedTime.java
@@ -477,7 +477,14 @@
return mTimeResult;
}
- /** Clears the last received NTP. Intended for use during tests. */
+ /** Sets the last received NTP time. Intended for use during tests. */
+ public void setCachedTimeResult(TimeResult timeResult) {
+ synchronized (this) {
+ mTimeResult = timeResult;
+ }
+ }
+
+ /** Clears the last received NTP time. Intended for use during tests. */
public void clearCachedTimeResult() {
synchronized (this) {
mTimeResult = null;
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 20be9d6..5476088 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -1261,20 +1261,17 @@
/**
* @hide
- * Returns the display's HDR supported types.
+ * Returns the current mode's supported HDR types.
*
* @see #isHdr()
- * @see HdrCapabilities#getSupportedHdrTypes()
+ * @see Mode#getSupportedHdrTypes()
*/
@TestApi
@NonNull
public int[] getReportedHdrTypes() {
synchronized (mLock) {
updateDisplayInfoLocked();
- if (mDisplayInfo.hdrCapabilities == null) {
- return new int[0];
- }
- return mDisplayInfo.hdrCapabilities.getSupportedHdrTypes();
+ return mDisplayInfo.getMode().getSupportedHdrTypes();
}
}
diff --git a/core/java/android/view/HandwritingDelegateConfiguration.java b/core/java/android/view/HandwritingDelegateConfiguration.java
deleted file mode 100644
index 719c614..0000000
--- a/core/java/android/view/HandwritingDelegateConfiguration.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import android.annotation.IdRes;
-import android.annotation.NonNull;
-
-/**
- * Configuration for a view to act as a handwriting initiation delegate. This allows handwriting
- * mode for a delegator editor view to be initiated by stylus movement on the delegate view.
- *
- * <p>If a stylus {@link MotionEvent} occurs within the delegate view's bounds, the callback
- * returned by {@link #getInitiationCallback()} will be called. The callback implementation is
- * expected to show and focus the delegator editor view. If a view with identifier matching {@link
- * #getDelegatorViewId()} creates an input connection while the same stylus {@link MotionEvent}
- * sequence is ongoing, handwriting mode will be initiated for that view.
- *
- * <p>A common use case is a custom view which looks like a text editor but does not actually
- * support text editing itself, and clicking on the custom view causes an EditText to be shown. To
- * support handwriting initiation in this case, {@link View#setHandwritingDelegateConfiguration} can
- * be called on the custom view to configure it as a delegate, and set the EditText as the delegator
- * by passing the EditText's identifier as the {@code delegatorViewId}. The {@code
- * initiationCallback} implementation is typically the same as the click listener implementation
- * which shows the EditText.
- */
-public class HandwritingDelegateConfiguration {
- @IdRes private final int mDelegatorViewId;
- @NonNull private final Runnable mInitiationCallback;
-
- /**
- * Constructs a HandwritingDelegateConfiguration instance.
- *
- * @param delegatorViewId identifier of the delegator editor view for which handwriting mode
- * should be initiated
- * @param initiationCallback callback called when a stylus {@link MotionEvent} occurs within
- * this view's bounds. This will be called from the UI thread.
- */
- public HandwritingDelegateConfiguration(
- @IdRes int delegatorViewId, @NonNull Runnable initiationCallback) {
- mDelegatorViewId = delegatorViewId;
- mInitiationCallback = initiationCallback;
- }
-
- /**
- * Returns the identifier of the delegator editor view for which handwriting mode should be
- * initiated.
- */
- public int getDelegatorViewId() {
- return mDelegatorViewId;
- }
-
- /**
- * Returns the callback which should be called when a stylus {@link MotionEvent} occurs within
- * the delegate view's bounds. The callback should only be called from the UI thread.
- */
- @NonNull
- public Runnable getInitiationCallback() {
- return mInitiationCallback;
- }
-}
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index 8d221ab..98f61a3 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -16,7 +16,6 @@
package android.view;
-import android.annotation.IdRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -172,15 +171,11 @@
if (candidateView != null) {
if (candidateView == getConnectedView()) {
startHandwriting(candidateView);
- } else if (candidateView.getHandwritingDelegateConfiguration() != null) {
- mState.mDelegatorViewId =
- candidateView
- .getHandwritingDelegateConfiguration()
- .getDelegatorViewId();
- candidateView
- .getHandwritingDelegateConfiguration()
- .getInitiationCallback()
- .run();
+ } else if (candidateView.getHandwritingDelegatorCallback() != null) {
+ mImm.prepareStylusHandwritingDelegation(
+ candidateView,
+ candidateView.getAllowedHandwritingDelegatePackageName());
+ candidateView.getHandwritingDelegatorCallback().run();
} else {
if (candidateView.getRevealOnFocusHint()) {
candidateView.setRevealOnFocusHint(false);
@@ -227,6 +222,9 @@
} else {
mConnectedView = new WeakReference<>(view);
mConnectionCount = 1;
+ if (view.isHandwritingDelegate() && tryAcceptStylusHandwritingDelegation(view)) {
+ return;
+ }
if (mState != null && mState.mShouldInitHandwriting) {
tryStartHandwriting();
}
@@ -279,17 +277,15 @@
}
final Rect handwritingArea = getViewHandwritingArea(connectedView);
- if ((mState.mDelegatorViewId != View.NO_ID
- && mState.mDelegatorViewId == connectedView.getId())
- || isInHandwritingArea(
- handwritingArea, mState.mStylusDownX, mState.mStylusDownY, connectedView)) {
+ if (isInHandwritingArea(
+ handwritingArea, mState.mStylusDownX, mState.mStylusDownY, connectedView)) {
startHandwriting(connectedView);
} else {
mState.mShouldInitHandwriting = false;
}
}
- /** For test only. */
+ /** Starts a stylus handwriting session for the view. */
@VisibleForTesting
public void startHandwriting(@NonNull View view) {
mImm.startStylusHandwriting(view);
@@ -298,6 +294,23 @@
}
/**
+ * Starts a stylus handwriting session for the delegate view, if {@link
+ * InputMethodManager#prepareStylusHandwritingDelegation} was previously called.
+ */
+ @VisibleForTesting
+ public boolean tryAcceptStylusHandwritingDelegation(@NonNull View view) {
+ if (mImm.acceptStylusHandwritingDelegation(
+ view, view.getAllowedHandwritingDelegatorPackageName())) {
+ if (mState != null) {
+ mState.mHasInitiatedHandwriting = true;
+ mState.mShouldInitHandwriting = false;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
* Notify that the handwriting area for the given view might be updated.
* @param view the view whose handwriting area might be updated.
*/
@@ -542,13 +555,6 @@
* built InputConnection.
*/
private boolean mExceedHandwritingSlop;
- /**
- * If the current ongoing stylus MotionEvent sequence started over a handwriting initiation
- * delegate view, then this is the view identifier of the corresponding delegator view. If
- * the delegator view creates an input connection while the MotionEvent sequence is still
- * ongoing, then handwriting mode will be initiated for the delegator view.
- */
- @IdRes private int mDelegatorViewId = View.NO_ID;
/** The pointer id of the stylus pointer that is being tracked. */
private final int mStylusPointerId;
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index 17ab83c..e02e600 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -133,7 +133,7 @@
return mVisibleFrame == null || !mVisibleFrame.isEmpty();
}
- public boolean getInsetsRoundedCornerFrame() {
+ public boolean insetsRoundedCornerFrame() {
return mInsetsRoundedCornerFrame;
}
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index ba7d823..997e4a5 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -275,7 +275,7 @@
final Rect roundedCornerFrame = new Rect(mRoundedCornerFrame);
for (int i = mSources.size() - 1; i >= 0; i--) {
final InsetsSource source = mSources.valueAt(i);
- if (source.getInsetsRoundedCornerFrame()) {
+ if (source.insetsRoundedCornerFrame()) {
final Insets insets = source.calculateInsets(roundedCornerFrame, false);
roundedCornerFrame.inset(insets);
}
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 1a5613e..ab81345 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -1352,6 +1352,7 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private int mSource;
private int mDisplayId = INVALID_DISPLAY;
+ // NOTE: mHmac is private and not used in this class, but it's used on native side / parcel.
private @Nullable byte[] mHmac;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private int mMetaState;
@@ -1377,7 +1378,7 @@
*/
private long mEventTime;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- private String mCharacters;
+ private @Nullable String mCharacters;
public interface Callback {
/**
@@ -1441,7 +1442,11 @@
private static native int nativeKeyCodeFromString(String keyCode);
private static native int nativeNextId();
- private KeyEvent() {}
+ private KeyEvent() {
+ this(/* downTime= */ 0, /* eventTime= */ 0, /* action= */ 0, /* code= */0, /* repeat= */ 0,
+ /* metaState= */ 0, /* deviceId= */ 0, /* scancode= */ 0, /* flags= */ 0,
+ /* source= */ 0, /* characters= */ null);
+ }
/**
* Create a new key event.
@@ -1451,11 +1456,9 @@
* @param code The key code.
*/
public KeyEvent(int action, int code) {
- mId = nativeNextId();
- mAction = action;
- mKeyCode = code;
- mRepeatCount = 0;
- mDeviceId = KeyCharacterMap.VIRTUAL_KEYBOARD;
+ this(/* downTime= */ 0, /* eventTime= */ 0, action, code, /* repeat= */ 0,
+ /* metaState= */ 0, /* deviceId= */ KeyCharacterMap.VIRTUAL_KEYBOARD,
+ /* scancode= */ 0, /* flags= */ 0, /* source= */ 0, /* characters= */ null);
}
/**
@@ -1473,13 +1476,9 @@
*/
public KeyEvent(long downTime, long eventTime, int action,
int code, int repeat) {
- mId = nativeNextId();
- mDownTime = TimeUnit.NANOSECONDS.convert(downTime, TimeUnit.MILLISECONDS);
- mEventTime = TimeUnit.NANOSECONDS.convert(eventTime, TimeUnit.MILLISECONDS);
- mAction = action;
- mKeyCode = code;
- mRepeatCount = repeat;
- mDeviceId = KeyCharacterMap.VIRTUAL_KEYBOARD;
+ this(downTime, eventTime, action, code, repeat, /* metaState= */ 0,
+ KeyCharacterMap.VIRTUAL_KEYBOARD, /* scancode= */ 0, /* flags= */ 0,
+ /* source= */ 0, /* characters= */ null);
}
/**
@@ -1498,14 +1497,8 @@
*/
public KeyEvent(long downTime, long eventTime, int action,
int code, int repeat, int metaState) {
- mId = nativeNextId();
- mDownTime = TimeUnit.NANOSECONDS.convert(downTime, TimeUnit.MILLISECONDS);
- mEventTime = TimeUnit.NANOSECONDS.convert(eventTime, TimeUnit.MILLISECONDS);
- mAction = action;
- mKeyCode = code;
- mRepeatCount = repeat;
- mMetaState = metaState;
- mDeviceId = KeyCharacterMap.VIRTUAL_KEYBOARD;
+ this(downTime, eventTime, action, code, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD,
+ /* scancode= */ 0, /* flags= */ 0, /* source= */ 0, /* characters= */ null);
}
/**
@@ -1527,15 +1520,8 @@
public KeyEvent(long downTime, long eventTime, int action,
int code, int repeat, int metaState,
int deviceId, int scancode) {
- mId = nativeNextId();
- mDownTime = TimeUnit.NANOSECONDS.convert(downTime, TimeUnit.MILLISECONDS);
- mEventTime = TimeUnit.NANOSECONDS.convert(eventTime, TimeUnit.MILLISECONDS);
- mAction = action;
- mKeyCode = code;
- mRepeatCount = repeat;
- mMetaState = metaState;
- mDeviceId = deviceId;
- mScanCode = scancode;
+ this(downTime, eventTime, action, code, repeat, metaState, deviceId, scancode,
+ /* flags= */ 0, /* source= */ 0, /* characters= */ null);
}
/**
@@ -1558,16 +1544,8 @@
public KeyEvent(long downTime, long eventTime, int action,
int code, int repeat, int metaState,
int deviceId, int scancode, int flags) {
- mId = nativeNextId();
- mDownTime = TimeUnit.NANOSECONDS.convert(downTime, TimeUnit.MILLISECONDS);
- mEventTime = TimeUnit.NANOSECONDS.convert(eventTime, TimeUnit.MILLISECONDS);
- mAction = action;
- mKeyCode = code;
- mRepeatCount = repeat;
- mMetaState = metaState;
- mDeviceId = deviceId;
- mScanCode = scancode;
- mFlags = flags;
+ this(downTime, eventTime, action, code, repeat, metaState, deviceId, scancode, flags,
+ /* source= */ 0, /* characters= */ null);
}
/**
@@ -1591,6 +1569,14 @@
public KeyEvent(long downTime, long eventTime, int action,
int code, int repeat, int metaState,
int deviceId, int scancode, int flags, int source) {
+ this(downTime, eventTime, action, code, repeat, metaState, deviceId, scancode, flags,
+ source, /* characters= */ null);
+ }
+
+ private KeyEvent(long downTime, long eventTime, int action, int code, int repeat, int metaState,
+ int deviceId, int scancode, int flags, int source, @Nullable String characters) {
+ // NOTE: this is the canonical constructor, other constructors that takes KeyEvent
+ // attributes should call it
mId = nativeNextId();
mDownTime = TimeUnit.NANOSECONDS.convert(downTime, TimeUnit.MILLISECONDS);
mEventTime = TimeUnit.NANOSECONDS.convert(eventTime, TimeUnit.MILLISECONDS);
@@ -1602,6 +1588,7 @@
mScanCode = scancode;
mFlags = flags;
mSource = source;
+ mCharacters = characters;
}
/**
@@ -1617,36 +1604,18 @@
* @param flags The flags for this key event
*/
public KeyEvent(long time, String characters, int deviceId, int flags) {
- mId = nativeNextId();
- mDownTime = TimeUnit.NANOSECONDS.convert(time, TimeUnit.MILLISECONDS);
- mEventTime = TimeUnit.NANOSECONDS.convert(time, TimeUnit.MILLISECONDS);
- mCharacters = characters;
- mAction = ACTION_MULTIPLE;
- mKeyCode = KEYCODE_UNKNOWN;
- mRepeatCount = 0;
- mDeviceId = deviceId;
- mFlags = flags;
- mSource = InputDevice.SOURCE_KEYBOARD;
+ this(/* downTime= */ time, /* eventTime= */ time, ACTION_MULTIPLE, KEYCODE_UNKNOWN,
+ /* repeat= */ 0, /* metaState= */ 0, deviceId, /* scancode= */ 0, flags,
+ /* source= */ InputDevice.SOURCE_KEYBOARD, characters);
}
/**
* Make an exact copy of an existing key event.
*/
public KeyEvent(KeyEvent origEvent) {
- mId = origEvent.mId;
- mDownTime = origEvent.mDownTime;
- mEventTime = origEvent.mEventTime;
- mAction = origEvent.mAction;
- mKeyCode = origEvent.mKeyCode;
- mRepeatCount = origEvent.mRepeatCount;
- mMetaState = origEvent.mMetaState;
- mDeviceId = origEvent.mDeviceId;
- mSource = origEvent.mSource;
- mDisplayId = origEvent.mDisplayId;
- mHmac = origEvent.mHmac == null ? null : origEvent.mHmac.clone();
- mScanCode = origEvent.mScanCode;
- mFlags = origEvent.mFlags;
- mCharacters = origEvent.mCharacters;
+ this(origEvent, origEvent.mId, origEvent.mEventTime, origEvent.mAction,
+ origEvent.mRepeatCount, origEvent.mHmac == null ? null : origEvent.mHmac.clone(),
+ origEvent.mCharacters);
}
/**
@@ -1662,20 +1631,30 @@
*/
@Deprecated
public KeyEvent(KeyEvent origEvent, long eventTime, int newRepeat) {
- mId = nativeNextId(); // Not an exact copy so assign a new ID.
+ // Not an exact copy so assign a new ID.
+ // Don't copy HMAC, it will be invalid because eventTime is changing
+ this(origEvent, nativeNextId(),
+ TimeUnit.NANOSECONDS.convert(eventTime, TimeUnit.MILLISECONDS), origEvent.mAction,
+ newRepeat, /* hmac= */ null, origEvent.mCharacters);
+ }
+
+ // This is the canonical constructor that should be called for constructors that take a KeyEvent
+ private KeyEvent(KeyEvent origEvent, int id, long eventTime, int action, int newRepeat,
+ @Nullable byte[] hmac, @Nullable String characters) {
+ mId = id;
mDownTime = origEvent.mDownTime;
- mEventTime = TimeUnit.NANOSECONDS.convert(eventTime, TimeUnit.MILLISECONDS);
- mAction = origEvent.mAction;
+ mEventTime = eventTime;
+ mAction = action;
mKeyCode = origEvent.mKeyCode;
mRepeatCount = newRepeat;
mMetaState = origEvent.mMetaState;
mDeviceId = origEvent.mDeviceId;
mSource = origEvent.mSource;
mDisplayId = origEvent.mDisplayId;
- mHmac = null; // Don't copy HMAC, it will be invalid because eventTime is changing
+ mHmac = hmac;
mScanCode = origEvent.mScanCode;
mFlags = origEvent.mFlags;
- mCharacters = origEvent.mCharacters;
+ mCharacters = characters;
}
private static KeyEvent obtain() {
@@ -1857,21 +1836,11 @@
* @param action The new action code of the event.
*/
private KeyEvent(KeyEvent origEvent, int action) {
- mId = nativeNextId(); // Not an exact copy so assign a new ID.
- mDownTime = origEvent.mDownTime;
- mEventTime = origEvent.mEventTime;
- mAction = action;
- mKeyCode = origEvent.mKeyCode;
- mRepeatCount = origEvent.mRepeatCount;
- mMetaState = origEvent.mMetaState;
- mDeviceId = origEvent.mDeviceId;
- mSource = origEvent.mSource;
- mDisplayId = origEvent.mDisplayId;
- mHmac = null; // Don't copy the hmac, it will be invalid since action is changing
- mScanCode = origEvent.mScanCode;
- mFlags = origEvent.mFlags;
- // Don't copy mCharacters, since one way or the other we'll lose it
- // when changing the action.
+ // Not an exact copy so assign a new ID
+ // Don't copy the hmac, it will be invalid since action is changing
+ // Don't copy mCharacters, since one way or the other we'll lose it when changing action.
+ this(origEvent, nativeNextId(), origEvent.mEventTime, action, origEvent.mRepeatCount,
+ /* hmac= */ null, /* characters= */ null);
}
/**
@@ -3219,6 +3188,8 @@
}
private KeyEvent(Parcel in) {
+ // NOTE: ideally this constructor should call the canonical one, but that would require
+ // changing the order the fields are written to the parcel, which could break native code
mId = in.readInt();
mDeviceId = in.readInt();
mSource = in.readInt();
diff --git a/core/java/android/view/TaskTransitionSpec.java b/core/java/android/view/TaskTransitionSpec.java
index 5f498a1..9a2d3ba 100644
--- a/core/java/android/view/TaskTransitionSpec.java
+++ b/core/java/android/view/TaskTransitionSpec.java
@@ -18,9 +18,6 @@
import android.os.Parcel;
import android.os.Parcelable;
-import android.util.ArraySet;
-
-import java.util.Set;
/**
* Holds information about how to execute task transition animations.
@@ -36,32 +33,12 @@
*/
public final int backgroundColor;
- /**
- * TEMPORARY FIELD (b/202383002)
- * TODO: Remove once we use surfaceflinger rounded corners on tasks rather than taskbar overlays
- * or when shell transitions are fully enabled
- *
- * A set of {@InsetsState.InternalInsetsType}s we want to use as the source to set the bounds
- * of the task during the animation. Used to make sure that task animate above the taskbar.
- * Will also be used to crop to the frame size of the inset source to the inset size to prevent
- * the taskbar rounded corners overlay from being invisible during task transition animation.
- */
- public final Set<Integer> animationBoundInsets;
-
- public TaskTransitionSpec(
- int backgroundColor, Set<Integer> animationBoundInsets) {
+ public TaskTransitionSpec(int backgroundColor) {
this.backgroundColor = backgroundColor;
- this.animationBoundInsets = animationBoundInsets;
}
public TaskTransitionSpec(Parcel in) {
this.backgroundColor = in.readInt();
-
- int animationBoundInsetsSize = in.readInt();
- this.animationBoundInsets = new ArraySet<>(animationBoundInsetsSize);
- for (int i = 0; i < animationBoundInsetsSize; i++) {
- this.animationBoundInsets.add(in.readInt());
- }
}
@Override
@@ -72,11 +49,6 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(backgroundColor);
-
- dest.writeInt(animationBoundInsets.size());
- for (int insetType : animationBoundInsets) {
- dest.writeInt(insetType);
- }
}
public static final @android.annotation.NonNull Parcelable.Creator<TaskTransitionSpec>
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 7d18bf0..fee2051 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -5101,12 +5101,13 @@
*/
private boolean mHoveringTouchDelegate = false;
- /**
- * Configuration for this view to act as a handwriting initiation delegate. This allows
- * handwriting mode for a delegator editor view to be initiated by stylus movement on this
- * delegate view.
- */
- private HandwritingDelegateConfiguration mHandwritingDelegateConfiguration;
+ // These two fields are set if the view is a handwriting delegator.
+ private Runnable mHandwritingDelegatorCallback;
+ private String mAllowedHandwritingDelegatePackageName;
+
+ // These two fields are set if the view is a handwriting delegate.
+ private boolean mIsHandwritingDelegate;
+ private String mAllowedHandwritingDelegatorPackageName;
/**
* Solid color to use as a background when creating the drawing cache. Enables
@@ -12410,27 +12411,168 @@
}
/**
- * Configures this view to act as a handwriting initiation delegate. This allows handwriting
- * mode for a delegator editor view to be initiated by stylus movement on this delegate view.
+ * Sets a callback which should be called when a stylus {@link MotionEvent} occurs within this
+ * view's bounds. The callback will be called from the UI thread.
+ *
+ * <p>Setting a callback allows this view to act as a handwriting delegator, so that handwriting
+ * mode for a delegate editor view can be initiated by stylus movement on this delegator view.
+ * The callback implementation is expected to show and focus the delegate editor view. If a view
+ * which returns {@code true} for {@link #isHandwritingDelegate()} creates an input connection
+ * while the same stylus {@link MotionEvent} sequence is ongoing, handwriting mode will be
+ * initiated for that view.
+ *
+ * <p>A common use case is a custom view which looks like a text editor but does not actually
+ * support text editing itself, and clicking on the custom view causes an EditText to be shown.
+ * To support handwriting initiation in this case, this method can be called on the custom view
+ * to configure it as a delegator. The EditText should call {@link #setIsHandwritingDelegate} to
+ * set it as a delegate. The {@code callback} implementation is typically the same as the click
+ * listener implementation which shows the EditText.
*
* <p>If {@code null} is passed, this view will no longer act as a handwriting initiation
- * delegate.
+ * delegator.
+ *
+ * @param callback a callback which should be called when a stylus {@link MotionEvent} occurs
+ * within this view's bounds
*/
- public void setHandwritingDelegateConfiguration(
- @Nullable HandwritingDelegateConfiguration configuration) {
- mHandwritingDelegateConfiguration = configuration;
- if (configuration != null) {
+ public void setHandwritingDelegatorCallback(@Nullable Runnable callback) {
+ mHandwritingDelegatorCallback = callback;
+ if (callback != null) {
+ // By default, the delegate must be from the same package as the delegator view.
+ mAllowedHandwritingDelegatePackageName = mContext.getOpPackageName();
setHandwritingArea(new Rect(0, 0, getWidth(), getHeight()));
+ } else {
+ mAllowedHandwritingDelegatePackageName = null;
}
}
/**
- * If this view has been configured as a handwriting initiation delegate, returns the delegate
- * configuration.
+ * Returns the callback set by {@link #setHandwritingDelegatorCallback} which should be called
+ * when a stylus {@link MotionEvent} occurs within this view's bounds. The callback should only
+ * be called from the UI thread.
*/
@Nullable
- public HandwritingDelegateConfiguration getHandwritingDelegateConfiguration() {
- return mHandwritingDelegateConfiguration;
+ public Runnable getHandwritingDelegatorCallback() {
+ return mHandwritingDelegatorCallback;
+ }
+
+ /**
+ * Specifies that this view may act as a handwriting initiation delegator for a delegate editor
+ * view from the specified package. If this method is not called, delegators may only be used to
+ * initiate handwriting mode for a delegate editor view from the same package as the delegator
+ * view. This method allows specifying a different trusted package which may contain a delegate
+ * editor view linked to this delegator view. This should be called after {@link
+ * #setHandwritingDelegatorCallback}.
+ *
+ * <p>If this method is called on the delegator view, then {@link
+ * #setAllowedHandwritingDelegatorPackage} should also be called on the delegate editor view.
+ *
+ * <p>For example, to configure a delegator view in package 1:
+ *
+ * <pre>
+ * delegatorView.setHandwritingDelegatorCallback(callback);
+ * delegatorView.setAllowedHandwritingDelegatePackage(package2);</pre>
+ *
+ * Then to configure the corresponding delegate editor view in package 2:
+ *
+ * <pre>
+ * delegateEditorView.setIsHandwritingDelegate(true);
+ * delegateEditorView.setAllowedHandwritingDelegatorPackage(package1);</pre>
+ *
+ * @param allowedPackageName the package name of a delegate editor view linked to this delegator
+ * view
+ * @throws IllegalStateException If the view has not been configured as a handwriting delegator
+ * using {@link #setHandwritingDelegatorCallback}.
+ */
+ public void setAllowedHandwritingDelegatePackage(@NonNull String allowedPackageName) {
+ if (mHandwritingDelegatorCallback == null) {
+ throw new IllegalStateException("This view is not a handwriting delegator.");
+ }
+ mAllowedHandwritingDelegatePackageName = allowedPackageName;
+ }
+
+ /**
+ * Returns the allowed package for delegate editor views for which this view may act as a
+ * handwriting delegator. If {@link #setAllowedHandwritingDelegatePackage} has not been called,
+ * this will return this view's package name, since by default delegators may only be used to
+ * initiate handwriting mode for a delegate editor view from the same package as the delegator
+ * view. This will return a different allowed package if set by {@link
+ * #setAllowedHandwritingDelegatePackage}.
+ *
+ * @throws IllegalStateException If the view has not been configured as a handwriting delegator
+ * using {@link #setHandwritingDelegatorCallback}.
+ */
+ @NonNull
+ public String getAllowedHandwritingDelegatePackageName() {
+ if (mHandwritingDelegatorCallback == null) {
+ throw new IllegalStateException("This view is not a handwriting delegator.");
+ }
+ return mAllowedHandwritingDelegatePackageName;
+ }
+
+ /**
+ * Sets this view to be a handwriting delegate. If a delegate view creates an input connection
+ * while a stylus {@link MotionEvent} sequence from a delegator view is ongoing, handwriting
+ * mode will be initiated for the delegate view.
+ *
+ * @param isHandwritingDelegate whether this view is a handwriting initiation delegate
+ * @see #setHandwritingDelegatorCallback(Runnable)
+ */
+ public void setIsHandwritingDelegate(boolean isHandwritingDelegate) {
+ mIsHandwritingDelegate = isHandwritingDelegate;
+ if (mIsHandwritingDelegate) {
+ // By default, the delegator must be from the same package as the delegate view.
+ mAllowedHandwritingDelegatorPackageName = mContext.getOpPackageName();
+ } else {
+ mAllowedHandwritingDelegatePackageName = null;
+ }
+ }
+
+ /**
+ * Returns whether this view has been set as a handwriting delegate by {@link
+ * #setIsHandwritingDelegate}.
+ */
+ public boolean isHandwritingDelegate() {
+ return mIsHandwritingDelegate;
+ }
+
+ /**
+ * Specifies that a view from the specified package may act as a handwriting delegator for this
+ * delegate editor view. If this method is not called, only views from the same package as the
+ * delegate editor view may act as a handwriting delegator. This method allows specifying a
+ * different trusted package which may contain a delegator view linked to this delegate editor
+ * view. This should be called after {@link #setIsHandwritingDelegate}.
+ *
+ * <p>If this method is called on the delegate editor view, then {@link
+ * #setAllowedHandwritingDelegatePackage} should also be called on the delegator view.
+ *
+ * @param allowedPackageName the package name of a delegator view linked to this delegate editor
+ * view
+ * @throws IllegalStateException If the view has not been configured as a handwriting delegate
+ * using {@link #setIsHandwritingDelegate}.
+ */
+ public void setAllowedHandwritingDelegatorPackage(@NonNull String allowedPackageName) {
+ if (!mIsHandwritingDelegate) {
+ throw new IllegalStateException("This view is not a handwriting delegate.");
+ }
+ mAllowedHandwritingDelegatorPackageName = allowedPackageName;
+ }
+
+ /**
+ * Returns the allowed package for views which may act as a handwriting delegator for this
+ * delegate editor view. If {@link #setAllowedHandwritingDelegatorPackage} has not been called,
+ * this will return this view's package name, since by default only views from the same package
+ * as the delegator editor view may act as a handwriting delegator. This will return a different
+ * allowed package if set by {@link #setAllowedHandwritingDelegatorPackage}.
+ *
+ * @throws IllegalStateException If the view has not been configured as a handwriting delegate
+ * using {@link #setIsHandwritingDelegate}.
+ */
+ @NonNull
+ public String getAllowedHandwritingDelegatorPackageName() {
+ if (!mIsHandwritingDelegate) {
+ throw new IllegalStateException("This view is not a handwriting delegate.");
+ }
+ return mAllowedHandwritingDelegatorPackageName;
}
/**
@@ -24474,7 +24616,7 @@
}
}
rebuildOutline();
- if (onCheckIsTextEditor() || mHandwritingDelegateConfiguration != null) {
+ if (onCheckIsTextEditor() || mHandwritingDelegatorCallback != null) {
setHandwritingArea(new Rect(0, 0, newWidth, newHeight));
}
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e9b3e28..f430ec3 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1136,7 +1136,7 @@
// Make sure to report the completion of draw for relaunch with preserved window.
reportNextDraw("rebuilt");
// Make sure to resume this root view when relaunching its host activity which was stopped.
- if (mStopped && getHostVisibility() != View.GONE) {
+ if (mStopped) {
setWindowStopped(false);
}
}
diff --git a/core/java/android/view/accessibility/DirectAccessibilityConnection.java b/core/java/android/view/accessibility/DirectAccessibilityConnection.java
index 8a3bb6f..25f5b8c 100644
--- a/core/java/android/view/accessibility/DirectAccessibilityConnection.java
+++ b/core/java/android/view/accessibility/DirectAccessibilityConnection.java
@@ -50,13 +50,13 @@
class DirectAccessibilityConnection extends IAccessibilityServiceConnection.Default {
private final IAccessibilityInteractionConnection mAccessibilityInteractionConnection;
private final AccessibilityManager mAccessibilityManager;
+ private final int mMyProcessId;
// Fetch all views, but do not use prefetching/cache since this "connection" does not
// receive cache invalidation events (as it is not linked to an AccessibilityService).
private static final int FETCH_FLAGS =
AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_REPORT_VIEW_IDS
| AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS;
- private static final int PID = Process.myPid();
private static final Region INTERACTIVE_REGION = null;
DirectAccessibilityConnection(
@@ -64,6 +64,7 @@
AccessibilityManager accessibilityManager) {
mAccessibilityInteractionConnection = accessibilityInteractionConnection;
mAccessibilityManager = accessibilityManager;
+ mMyProcessId = Process.myPid();
}
@Override
@@ -74,8 +75,9 @@
IAccessibilityManager.WindowTransformationSpec spec =
mAccessibilityManager.getWindowTransformationSpec(accessibilityWindowId);
mAccessibilityInteractionConnection.findAccessibilityNodeInfoByAccessibilityId(
- accessibilityNodeId, INTERACTIVE_REGION, interactionId, callback, FETCH_FLAGS, PID,
- threadId, spec.magnificationSpec, spec.transformationMatrix, arguments);
+ accessibilityNodeId, INTERACTIVE_REGION, interactionId, callback, FETCH_FLAGS,
+ mMyProcessId, threadId, spec.magnificationSpec, spec.transformationMatrix,
+ arguments);
return new String[0];
}
@@ -87,8 +89,8 @@
IAccessibilityManager.WindowTransformationSpec spec =
mAccessibilityManager.getWindowTransformationSpec(accessibilityWindowId);
mAccessibilityInteractionConnection.findAccessibilityNodeInfosByText(accessibilityNodeId,
- text, INTERACTIVE_REGION, interactionId, callback, FETCH_FLAGS, PID, threadId,
- spec.magnificationSpec, spec.transformationMatrix);
+ text, INTERACTIVE_REGION, interactionId, callback, FETCH_FLAGS, mMyProcessId,
+ threadId, spec.magnificationSpec, spec.transformationMatrix);
return new String[0];
}
@@ -100,8 +102,8 @@
IAccessibilityManager.WindowTransformationSpec spec =
mAccessibilityManager.getWindowTransformationSpec(accessibilityWindowId);
mAccessibilityInteractionConnection.findAccessibilityNodeInfosByViewId(accessibilityNodeId,
- viewId, INTERACTIVE_REGION, interactionId, callback, FETCH_FLAGS, PID, threadId,
- spec.magnificationSpec, spec.transformationMatrix);
+ viewId, INTERACTIVE_REGION, interactionId, callback, FETCH_FLAGS, mMyProcessId,
+ threadId, spec.magnificationSpec, spec.transformationMatrix);
return new String[0];
}
@@ -112,7 +114,7 @@
IAccessibilityManager.WindowTransformationSpec spec =
mAccessibilityManager.getWindowTransformationSpec(accessibilityWindowId);
mAccessibilityInteractionConnection.findFocus(accessibilityNodeId, focusType,
- INTERACTIVE_REGION, interactionId, callback, FETCH_FLAGS, PID, threadId,
+ INTERACTIVE_REGION, interactionId, callback, FETCH_FLAGS, mMyProcessId, threadId,
spec.magnificationSpec, spec.transformationMatrix);
return new String[0];
}
@@ -124,7 +126,7 @@
IAccessibilityManager.WindowTransformationSpec spec =
mAccessibilityManager.getWindowTransformationSpec(accessibilityWindowId);
mAccessibilityInteractionConnection.focusSearch(accessibilityNodeId, direction,
- INTERACTIVE_REGION, interactionId, callback, FETCH_FLAGS, PID, threadId,
+ INTERACTIVE_REGION, interactionId, callback, FETCH_FLAGS, mMyProcessId, threadId,
spec.magnificationSpec, spec.transformationMatrix);
return new String[0];
}
@@ -135,7 +137,7 @@
IAccessibilityInteractionConnectionCallback callback, long threadId)
throws RemoteException {
mAccessibilityInteractionConnection.performAccessibilityAction(accessibilityNodeId, action,
- arguments, interactionId, callback, FETCH_FLAGS, PID, threadId);
+ arguments, interactionId, callback, FETCH_FLAGS, mMyProcessId, threadId);
return true;
}
}
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index bdc7333..aef0e65 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -899,9 +899,10 @@
// 3. Get the activity names substring between the indexes
final int activityStringStartIndex = packageInStringIndex + packageName.length() + 1;
- if (activityStringStartIndex < firstNextSemicolonIndex) {
+ if (activityStringStartIndex >= firstNextSemicolonIndex) {
Log.e(TAG, "Failed to get denied activity names from denylist because it's wrongly "
+ "formatted");
+ return;
}
final String activitySubstring =
denyListString.substring(activityStringStartIndex, firstNextSemicolonIndex);
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index 96602619..d84acc0 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -25,6 +25,7 @@
import android.annotation.RequiresPermission;
import android.annotation.UserIdInt;
import android.content.Context;
+import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
@@ -33,6 +34,7 @@
import android.window.ImeOnBackInvokedDispatcher;
import com.android.internal.inputmethod.DirectBootAwareness;
+import com.android.internal.inputmethod.IImeTracker;
import com.android.internal.inputmethod.IInputMethodClient;
import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
import com.android.internal.inputmethod.IRemoteInputConnection;
@@ -40,7 +42,6 @@
import com.android.internal.inputmethod.SoftInputShowHideReason;
import com.android.internal.inputmethod.StartInputFlags;
import com.android.internal.inputmethod.StartInputReason;
-import com.android.internal.view.IImeTracker;
import com.android.internal.view.IInputMethodManager;
import java.util.ArrayList;
@@ -505,6 +506,40 @@
}
@AnyThread
+ static void prepareStylusHandwritingDelegation(
+ @NonNull IInputMethodClient client,
+ @NonNull String delegatePackageName,
+ @NonNull String delegatorPackageName) {
+ final IInputMethodManager service = getService();
+ if (service == null) {
+ return;
+ }
+ try {
+ service.prepareStylusHandwritingDelegation(
+ client, delegatePackageName, delegatorPackageName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @AnyThread
+ static boolean acceptStylusHandwritingDelegation(
+ @NonNull IInputMethodClient client,
+ @NonNull String delegatePackageName,
+ @NonNull String delegatorPackageName) {
+ final IInputMethodManager service = getService();
+ if (service == null) {
+ return false;
+ }
+ try {
+ return service.acceptStylusHandwritingDelegation(
+ client, delegatePackageName, delegatorPackageName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @AnyThread
@RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
static boolean isStylusHandwritingAvailableAsUser(@UserIdInt int userId) {
final IInputMethodManager service = getService();
@@ -547,51 +582,57 @@
}
}
+ /** @see com.android.server.inputmethod.ImeTrackerService#onRequestShow */
@AnyThread
- @Nullable
- static IBinder onRequestShow(int uid, @ImeTracker.Origin int origin,
- @SoftInputShowHideReason int reason) {
+ @NonNull
+ static ImeTracker.Token onRequestShow(@NonNull String tag, int uid,
+ @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
final IImeTracker service = getImeTrackerService();
if (service == null) {
- return null;
+ // Create token with "fake" binder if the service was not found.
+ return new ImeTracker.Token(new Binder(), tag);
}
try {
- return service.onRequestShow(uid, origin, reason);
+ return service.onRequestShow(tag, uid, origin, reason);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
+ /** @see com.android.server.inputmethod.ImeTrackerService#onRequestHide */
@AnyThread
- @Nullable
- static IBinder onRequestHide(int uid, @ImeTracker.Origin int origin,
- @SoftInputShowHideReason int reason) {
+ @NonNull
+ static ImeTracker.Token onRequestHide(@NonNull String tag, int uid,
+ @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
final IImeTracker service = getImeTrackerService();
if (service == null) {
- return null;
+ // Create token with "fake" binder if the service was not found.
+ return new ImeTracker.Token(new Binder(), tag);
}
try {
- return service.onRequestHide(uid, origin, reason);
+ return service.onRequestHide(tag, uid, origin, reason);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
+ /** @see com.android.server.inputmethod.ImeTrackerService#onProgress */
@AnyThread
- static void onProgress(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+ static void onProgress(@NonNull IBinder binder, @ImeTracker.Phase int phase) {
final IImeTracker service = getImeTrackerService();
if (service == null) {
return;
}
try {
- service.onProgress(statsToken, phase);
+ service.onProgress(binder, phase);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
+ /** @see com.android.server.inputmethod.ImeTrackerService#onFailed */
@AnyThread
- static void onFailed(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+ static void onFailed(@NonNull ImeTracker.Token statsToken, @ImeTracker.Phase int phase) {
final IImeTracker service = getImeTrackerService();
if (service == null) {
return;
@@ -603,8 +644,9 @@
}
}
+ /** @see com.android.server.inputmethod.ImeTrackerService#onCancelled */
@AnyThread
- static void onCancelled(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+ static void onCancelled(@NonNull ImeTracker.Token statsToken, @ImeTracker.Phase int phase) {
final IImeTracker service = getImeTrackerService();
if (service == null) {
return;
@@ -616,8 +658,9 @@
}
}
+ /** @see com.android.server.inputmethod.ImeTrackerService#onShown */
@AnyThread
- static void onShown(@NonNull IBinder statsToken) {
+ static void onShown(@NonNull ImeTracker.Token statsToken) {
final IImeTracker service = getImeTrackerService();
if (service == null) {
return;
@@ -629,8 +672,9 @@
}
}
+ /** @see com.android.server.inputmethod.ImeTrackerService#onHidden */
@AnyThread
- static void onHidden(@NonNull IBinder statsToken) {
+ static void onHidden(@NonNull ImeTracker.Token statsToken) {
final IImeTracker service = getImeTrackerService();
if (service == null) {
return;
@@ -642,6 +686,7 @@
}
}
+ /** @see com.android.server.inputmethod.ImeTrackerService#hasPendingImeVisibilityRequests */
@AnyThread
@RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
static boolean hasPendingImeVisibilityRequests() {
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index e5a99ff..f0d1019 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -26,7 +26,6 @@
import android.annotation.Nullable;
import android.app.ActivityThread;
import android.content.Context;
-import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
@@ -47,7 +46,7 @@
import java.util.Arrays;
import java.util.Locale;
import java.util.Map;
-import java.util.Random;
+import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
/** @hide */
@@ -321,9 +320,8 @@
/**
* Creates an IME show request tracking token.
*
- * @param component the component name where the IME show request was created,
- * or {@code null} otherwise
- * (defaulting to {@link ActivityThread#currentProcessName()}).
+ * @param component the name of the component that created the IME request, or {@code null}
+ * otherwise (defaulting to {@link ActivityThread#currentProcessName()}).
* @param uid the uid of the client that requested the IME.
* @param origin the origin of the IME show request.
* @param reason the reason why the IME show request was created.
@@ -337,9 +335,8 @@
/**
* Creates an IME hide request tracking token.
*
- * @param component the component name where the IME hide request was created,
- * or {@code null} otherwise
- * (defaulting to {@link ActivityThread#currentProcessName()}).
+ * @param component the name of the component that created the IME request, or {@code null}
+ * otherwise (defaulting to {@link ActivityThread#currentProcessName()}).
* @param uid the uid of the client that requested the IME.
* @param origin the origin of the IME hide request.
* @param reason the reason why the IME hide request was created.
@@ -435,8 +432,7 @@
mLogProgress = SystemProperties.getBoolean("persist.debug.imetracker", false);
// Update logging flag dynamically.
SystemProperties.addChangeCallback(() ->
- mLogProgress =
- SystemProperties.getBoolean("persist.debug.imetracker", false));
+ mLogProgress = SystemProperties.getBoolean("persist.debug.imetracker", false));
}
/** Whether progress should be logged. */
@@ -446,10 +442,9 @@
@Override
public Token onRequestShow(@Nullable String component, int uid, @Origin int origin,
@SoftInputShowHideReason int reason) {
- IBinder binder = IInputMethodManagerGlobalInvoker.onRequestShow(uid, origin, reason);
- if (binder == null) binder = new Binder();
-
- final Token token = Token.build(binder, component);
+ final var tag = getTag(component);
+ final var token = IInputMethodManagerGlobalInvoker.onRequestShow(tag, uid, origin,
+ reason);
Log.i(TAG, token.mTag + ": onRequestShow at " + Debug.originToString(origin)
+ " reason " + InputMethodDebug.softInputDisplayReasonToString(reason));
@@ -461,10 +456,9 @@
@Override
public Token onRequestHide(@Nullable String component, int uid, @Origin int origin,
@SoftInputShowHideReason int reason) {
- IBinder binder = IInputMethodManagerGlobalInvoker.onRequestHide(uid, origin, reason);
- if (binder == null) binder = new Binder();
-
- final Token token = Token.build(binder, component);
+ final var tag = getTag(component);
+ final var token = IInputMethodManagerGlobalInvoker.onRequestHide(tag, uid, origin,
+ reason);
Log.i(TAG, token.mTag + ": onRequestHide at " + Debug.originToString(origin)
+ " reason " + InputMethodDebug.softInputDisplayReasonToString(reason));
@@ -485,7 +479,7 @@
@Override
public void onFailed(@Nullable Token token, @Phase int phase) {
if (token == null) return;
- IInputMethodManagerGlobalInvoker.onFailed(token.mBinder, phase);
+ IInputMethodManagerGlobalInvoker.onFailed(token, phase);
Log.i(TAG, token.mTag + ": onFailed at " + Debug.phaseToString(phase));
}
@@ -499,7 +493,7 @@
@Override
public void onCancelled(@Nullable Token token, @Phase int phase) {
if (token == null) return;
- IInputMethodManagerGlobalInvoker.onCancelled(token.mBinder, phase);
+ IInputMethodManagerGlobalInvoker.onCancelled(token, phase);
Log.i(TAG, token.mTag + ": onCancelled at " + Debug.phaseToString(phase));
}
@@ -507,7 +501,7 @@
@Override
public void onShown(@Nullable Token token) {
if (token == null) return;
- IInputMethodManagerGlobalInvoker.onShown(token.mBinder);
+ IInputMethodManagerGlobalInvoker.onShown(token);
Log.i(TAG, token.mTag + ": onShown");
}
@@ -515,10 +509,24 @@
@Override
public void onHidden(@Nullable Token token) {
if (token == null) return;
- IInputMethodManagerGlobalInvoker.onHidden(token.mBinder);
+ IInputMethodManagerGlobalInvoker.onHidden(token);
Log.i(TAG, token.mTag + ": onHidden");
}
+
+ /**
+ * Returns a logging tag using the given component name.
+ *
+ * @param component the name of the component that created the IME request, or {@code null}
+ * otherwise (defaulting to {@link ActivityThread#currentProcessName()}).
+ */
+ @NonNull
+ private String getTag(@Nullable String component) {
+ if (component == null) {
+ component = ActivityThread.currentProcessName();
+ }
+ return component + ":" + Integer.toHexString(ThreadLocalRandom.current().nextInt());
+ }
};
/** The singleton IME tracker instance for instrumenting jank metrics. */
@@ -528,28 +536,31 @@
ImeLatencyTracker LATENCY_TRACKER = new ImeLatencyTracker();
/** A token that tracks the progress of an IME request. */
- class Token implements Parcelable {
+ final class Token implements Parcelable {
+ /** The binder used to identify this token. */
@NonNull
- public final IBinder mBinder;
+ private final IBinder mBinder;
+ /** Logging tag, of the shape "component:random_hexadecimal". */
@NonNull
private final String mTag;
- @NonNull
- private static Token build(@NonNull IBinder binder, @Nullable String component) {
- if (component == null) component = ActivityThread.currentProcessName();
- final String tag = component + ":" + Integer.toHexString((new Random().nextInt()));
-
- return new Token(binder, tag);
- }
-
- private Token(@NonNull IBinder binder, @NonNull String tag) {
+ public Token(@NonNull IBinder binder, @NonNull String tag) {
mBinder = binder;
mTag = tag;
}
- /** Returns the {@link Token#mTag} */
+ private Token(@NonNull Parcel in) {
+ mBinder = in.readStrongBinder();
+ mTag = in.readString8();
+ }
+
+ @NonNull
+ public IBinder getBinder() {
+ return mBinder;
+ }
+
@NonNull
public String getTag() {
return mTag;
@@ -562,7 +573,7 @@
}
@Override
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeStrongBinder(mBinder);
dest.writeString8(mTag);
}
@@ -571,12 +582,11 @@
public static final Creator<Token> CREATOR = new Creator<>() {
@NonNull
@Override
- public Token createFromParcel(Parcel source) {
- final IBinder binder = source.readStrongBinder();
- final String tag = source.readString8();
- return new Token(binder, tag);
+ public Token createFromParcel(@NonNull Parcel in) {
+ return new Token(in);
}
+ @NonNull
@Override
public Token[] newArray(int size) {
return new Token[size];
@@ -589,40 +599,50 @@
*
* Note: This is held in a separate class so that it only gets initialized when actually needed.
*/
- class Debug {
+ final class Debug {
+ @NonNull
private static final Map<Integer, String> sTypes =
getFieldMapping(ImeTracker.class, "TYPE_");
+ @NonNull
private static final Map<Integer, String> sStatus =
getFieldMapping(ImeTracker.class, "STATUS_");
+ @NonNull
private static final Map<Integer, String> sOrigins =
getFieldMapping(ImeTracker.class, "ORIGIN_");
+ @NonNull
private static final Map<Integer, String> sPhases =
getFieldMapping(ImeTracker.class, "PHASE_");
+ @NonNull
public static String typeToString(@Type int type) {
return sTypes.getOrDefault(type, "TYPE_" + type);
}
+ @NonNull
public static String statusToString(@Status int status) {
return sStatus.getOrDefault(status, "STATUS_" + status);
}
+ @NonNull
public static String originToString(@Origin int origin) {
return sOrigins.getOrDefault(origin, "ORIGIN_" + origin);
}
+ @NonNull
public static String phaseToString(@Phase int phase) {
return sPhases.getOrDefault(phase, "PHASE_" + phase);
}
- private static Map<Integer, String> getFieldMapping(Class<?> cls, String fieldPrefix) {
+ @NonNull
+ private static Map<Integer, String> getFieldMapping(Class<?> cls,
+ @NonNull String fieldPrefix) {
return Arrays.stream(cls.getDeclaredFields())
.filter(field -> field.getName().startsWith(fieldPrefix))
.collect(Collectors.toMap(Debug::getFieldValue, Field::getName));
}
- private static int getFieldValue(Field field) {
+ private static int getFieldValue(@NonNull Field field) {
try {
return field.getInt(null);
} catch (IllegalAccessException e) {
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 642182b..36d2b8a 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -99,6 +99,7 @@
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
+import android.widget.Editor;
import android.window.ImeOnBackInvokedDispatcher;
import android.window.WindowOnBackInvokedDispatcher;
@@ -1552,11 +1553,7 @@
if (fallbackContext == null) {
return false;
}
- if (Settings.Global.getInt(fallbackContext.getContentResolver(),
- Settings.Global.STYLUS_HANDWRITING_ENABLED, 0) == 0) {
- if (DEBUG) {
- Log.d(TAG, "Stylus handwriting is not enabled in settings.");
- }
+ if (!isStylusHandwritingEnabled(fallbackContext)) {
return false;
}
return IInputMethodManagerGlobalInvoker.isStylusHandwritingAvailableAsUser(userId);
@@ -2233,37 +2230,175 @@
* @see #isStylusHandwritingAvailable()
*/
public void startStylusHandwriting(@NonNull View view) {
+ startStylusHandwritingInternal(view, null /* delegatorPackageName */);
+ }
+
+ private boolean startStylusHandwritingInternal(
+ @NonNull View view, @Nullable String delegatorPackageName) {
+ Objects.requireNonNull(view);
+
// Re-dispatch if there is a context mismatch.
final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
if (fallbackImm != null) {
- fallbackImm.startStylusHandwriting(view);
+ fallbackImm.startStylusHandwritingInternal(view, delegatorPackageName);
}
- Objects.requireNonNull(view);
- if (Settings.Global.getInt(view.getContext().getContentResolver(),
- Settings.Global.STYLUS_HANDWRITING_ENABLED, 0) == 0) {
- Log.d(TAG, "Ignoring startStylusHandwriting(view) as stylus handwriting is disabled");
- return;
+ boolean useDelegation = !TextUtils.isEmpty(delegatorPackageName);
+ if (!isStylusHandwritingEnabled(view.getContext())) {
+ Log.w(TAG, "Stylus handwriting pref is disabled. "
+ + "Ignoring calls to start stylus handwriting.");
+ return false;
}
checkFocus();
synchronized (mH) {
if (!hasServedByInputMethodLocked(view)) {
Log.w(TAG,
- "Ignoring startStylusHandwriting() as view=" + view + " is not served.");
- return;
+ "Ignoring startStylusHandwriting as view=" + view + " is not served.");
+ return false;
}
if (view.getViewRootImpl() != mCurRootView) {
- Log.w(TAG, "Ignoring startStylusHandwriting: View's window does not have focus.");
- return;
+ Log.w(TAG,
+ "Ignoring startStylusHandwriting: View's window does not have focus.");
+ return false;
}
-
- IInputMethodManagerGlobalInvoker.startStylusHandwriting(mClient);
- // TODO(b/210039666): do we need any extra work for supporting non-native
- // UI toolkits?
+ if (useDelegation) {
+ return IInputMethodManagerGlobalInvoker.acceptStylusHandwritingDelegation(
+ mClient, view.getContext().getOpPackageName(), delegatorPackageName);
+ } else {
+ IInputMethodManagerGlobalInvoker.startStylusHandwriting(mClient);
+ }
+ return false;
}
}
+ private boolean isStylusHandwritingEnabled(@NonNull Context context) {
+ if (Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.STYLUS_HANDWRITING_ENABLED, 0) == 0) {
+ Log.d(TAG, "Stylus handwriting pref is disabled.");
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Prepares delegation of starting stylus handwriting session to a different editor in same
+ * or different window than the view on which initial handwriting stroke was detected.
+ *
+ * Delegation can be used to start stylus handwriting session before the {@link Editor} view or
+ * its {@link InputConnection} is started. Calling this method starts buffering of stylus
+ * motion events until {@link #acceptStylusHandwritingDelegation(View)} is called, at which
+ * point the handwriting session can be started and the buffered stylus motion events will be
+ * delivered to the IME.
+ * e.g. Delegation can be used when initial handwriting stroke is
+ * on a pseudo {@link Editor} like widget (with no {@link InputConnection}) but actual
+ * {@link Editor} is on a different window.
+ *
+ * <p> Note: If an actual {@link Editor} capable of {@link InputConnection} is being scribbled
+ * upon using stylus, use {@link #startStylusHandwriting(View)} instead.</p>
+ *
+ * @param delegatorView the view that receives initial stylus stroke and delegates it to the
+ * actual editor. Its window must {@link View#hasWindowFocus have focus}.
+ * @see #prepareStylusHandwritingDelegation(View, String)
+ * @see #acceptStylusHandwritingDelegation(View)
+ * @see #startStylusHandwriting(View)
+ */
+ public void prepareStylusHandwritingDelegation(@NonNull View delegatorView) {
+ prepareStylusHandwritingDelegation(
+ delegatorView, delegatorView.getContext().getOpPackageName());
+ }
+
+ /**
+ * Prepares delegation of starting stylus handwriting session to a different editor in same or a
+ * different window in a different package than the view on which initial handwriting stroke
+ * was detected.
+ *
+ * Delegation can be used to start stylus handwriting session before the {@link Editor} view or
+ * its {@link InputConnection} is started. Calling this method starts buffering of stylus
+ * motion events until {@link #acceptStylusHandwritingDelegation(View, String)} is called, at
+ * which point the handwriting session can be started and the buffered stylus motion events will
+ * be delivered to the IME.
+ * e.g. Delegation can be used when initial handwriting stroke is
+ * on a pseudo {@link Editor} like widget (with no {@link InputConnection}) but actual
+ * {@link Editor} is on a different window in the given package.
+ *
+ * <p>Note: If delegator and delegate are in same package use
+ * {@link #prepareStylusHandwritingDelegation(View)} instead.</p>
+ *
+ * @param delegatorView the view that receives initial stylus stroke and delegates it to the
+ * actual editor. Its window must {@link View#hasWindowFocus have focus}.
+ * @param delegatePackageName package name that contains actual {@link Editor} which should
+ * start stylus handwriting session by calling {@link #acceptStylusHandwritingDelegation}.
+ * @see #prepareStylusHandwritingDelegation(View)
+ * @see #acceptStylusHandwritingDelegation(View, String)
+ */
+ public void prepareStylusHandwritingDelegation(
+ @NonNull View delegatorView, @NonNull String delegatePackageName) {
+ Objects.requireNonNull(delegatorView);
+ Objects.requireNonNull(delegatePackageName);
+
+ // Re-dispatch if there is a context mismatch.
+ final InputMethodManager fallbackImm =
+ getFallbackInputMethodManagerIfNecessary(delegatorView);
+ if (fallbackImm != null) {
+ fallbackImm.prepareStylusHandwritingDelegation(delegatorView, delegatePackageName);
+ }
+
+ if (!isStylusHandwritingEnabled(delegatorView.getContext())) {
+ Log.w(TAG, "Stylus handwriting pref is disabled. "
+ + "Ignoring prepareStylusHandwritingDelegation().");
+ return;
+ }
+ IInputMethodManagerGlobalInvoker.prepareStylusHandwritingDelegation(
+ mClient,
+ delegatePackageName,
+ delegatorView.getContext().getOpPackageName());
+ }
+
+ /**
+ * Accepts and starts a stylus handwriting session on the delegate view, if handwriting
+ * initiation delegation was previously requested using
+ * {@link #prepareStylusHandwritingDelegation(View)} from the delegator.
+ *
+ * <p>Note: If delegator and delegate are in different application packages, use
+ * {@link #acceptStylusHandwritingDelegation(View, String)} instead.</p>
+ *
+ * @param delegateView delegate view capable of receiving input via {@link InputConnection}
+ * on which {@link #startStylusHandwriting(View)} will be called.
+ * @return {@code true} if view belongs to same application package as used in
+ * {@link #prepareStylusHandwritingDelegation(View)} and handwriting session can start.
+ * @see #acceptStylusHandwritingDelegation(View, String)
+ * @see #prepareStylusHandwritingDelegation(View)
+ */
+ public boolean acceptStylusHandwritingDelegation(@NonNull View delegateView) {
+ return startStylusHandwritingInternal(
+ delegateView, delegateView.getContext().getOpPackageName());
+ }
+
+ /**
+ * Accepts and starts a stylus handwriting session on the delegate view, if handwriting
+ * initiation delegation was previously requested using
+ * {@link #prepareStylusHandwritingDelegation(View, String)} from te delegator and the view
+ * belongs to a specified delegate package.
+ *
+ * <p>Note: If delegator and delegate are in same application package use
+ * {@link #acceptStylusHandwritingDelegation(View)} instead.</p>
+ *
+ * @param delegateView delegate view capable of receiving input via {@link InputConnection}
+ * on which {@link #startStylusHandwriting(View)} will be called.
+ * @param delegatorPackageName package name of the delegator that handled initial stylus stroke.
+ * @return {@code true} if view belongs to allowed delegate package declared in
+ * {@link #prepareStylusHandwritingDelegation(View, String)} and handwriting session can start.
+ * @see #prepareStylusHandwritingDelegation(View, String)
+ * @see #acceptStylusHandwritingDelegation(View)
+ */
+ public boolean acceptStylusHandwritingDelegation(
+ @NonNull View delegateView, @NonNull String delegatorPackageName) {
+ Objects.requireNonNull(delegatorPackageName);
+
+ return startStylusHandwritingInternal(delegateView, delegatorPackageName);
+ }
+
/**
* This method toggles the input method window display.
* If the input window is already displayed, it gets hidden.
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index b89dd31..9f9a781 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -52,6 +52,7 @@
import android.graphics.RenderNode;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
import android.os.Build;
import android.os.Bundle;
import android.os.LocaleList;
@@ -3238,6 +3239,44 @@
.setOnMenuItemClickListener(mOnContextMenuItemClickListener);
mPreserveSelection = true;
+
+ // No-op for the old context menu because it doesn't have icons.
+ adjustIconSpacing(menu);
+ }
+
+ /**
+ * Adjust icon spacing to align the texts.
+ * @hide
+ */
+ @VisibleForTesting
+ public void adjustIconSpacing(ContextMenu menu) {
+ int width = -1;
+ int height = -1;
+ for (int i = 0; i < menu.size(); ++i) {
+ final MenuItem item = menu.getItem(i);
+ final Drawable d = item.getIcon();
+ if (d == null) {
+ continue;
+ }
+
+ width = Math.max(width, d.getIntrinsicWidth());
+ height = Math.max(height, d.getIntrinsicHeight());
+ }
+
+ if (width < 0 || height < 0) {
+ return; // No menu has icon drawable.
+ }
+
+ GradientDrawable paddingDrawable = new GradientDrawable();
+ paddingDrawable.setSize(width, height);
+
+ for (int i = 0; i < menu.size(); ++i) {
+ final MenuItem item = menu.getItem(i);
+ final Drawable d = item.getIcon();
+ if (d == null) {
+ item.setIcon(paddingDrawable);
+ }
+ }
}
@Nullable
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index ef25501..b83d1d8 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -650,6 +650,11 @@
*
*/
private void setEmptyShortcutTargetIfNeeded() {
+ if (hasFeatureLeanback()) {
+ // Do not disable the default shortcut on TV.
+ return;
+ }
+
final ContentResolver contentResolver = mContext.getContentResolver();
final String shortcutTargets = Settings.Secure.getStringForUser(contentResolver,
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index 586b309..9fae211 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -64,7 +64,7 @@
/** Gating the feature which shows FSI-denied notifications as Sticky HUNs */
public static final Flag SHOW_STICKY_HUN_FOR_DENIED_FSI =
- devFlag("persist.sysui.notification.show_sticky_hun_for_denied_fsi");
+ releasedFlag("persist.sysui.notification.show_sticky_hun_for_denied_fsi");
/** Gating the ability for users to dismiss ongoing event notifications */
public static final Flag ALLOW_DISMISS_ONGOING =
diff --git a/core/java/com/android/internal/view/IImeTracker.aidl b/core/java/com/android/internal/inputmethod/IImeTracker.aidl
similarity index 71%
rename from core/java/com/android/internal/view/IImeTracker.aidl
rename to core/java/com/android/internal/inputmethod/IImeTracker.aidl
index b062ca7..c7418ee 100644
--- a/core/java/com/android/internal/view/IImeTracker.aidl
+++ b/core/java/com/android/internal/inputmethod/IImeTracker.aidl
@@ -14,43 +14,45 @@
* limitations under the License.
*/
-package com.android.internal.view;
+package com.android.internal.inputmethod;
import android.view.inputmethod.ImeTracker;
/**
- * Interface to the global Ime tracker, used by all client applications.
+ * Interface to the global IME tracker service, used by all client applications.
* {@hide}
*/
interface IImeTracker {
/**
- * Called when an IME show request is created,
- * returns a new Binder to be associated with the IME tracking token.
+ * Called when an IME show request is created.
*
+ * @param tag the logging tag.
* @param uid the uid of the client that requested the IME.
* @param origin the origin of the IME show request.
* @param reason the reason why the IME show request was created.
+ * @return A new IME tracking token.
*/
- IBinder onRequestShow(int uid, int origin, int reason);
+ ImeTracker.Token onRequestShow(String tag, int uid, int origin, int reason);
/**
- * Called when an IME hide request is created,
- * returns a new Binder to be associated with the IME tracking token.
+ * Called when an IME hide request is created.
*
+ * @param tag the logging tag.
* @param uid the uid of the client that requested the IME.
* @param origin the origin of the IME hide request.
* @param reason the reason why the IME hide request was created.
+ * @return A new IME tracking token.
*/
- IBinder onRequestHide(int uid, int origin, int reason);
+ ImeTracker.Token onRequestHide(String tag, int uid, int origin, int reason);
/**
* Called when the IME request progresses to a further phase.
*
- * @param statsToken the token tracking the current IME request.
+ * @param binder the binder of token tracking the current IME request.
* @param phase the new phase the IME request reached.
*/
- oneway void onProgress(in IBinder statsToken, int phase);
+ oneway void onProgress(in IBinder binder, int phase);
/**
* Called when the IME request fails.
@@ -58,7 +60,7 @@
* @param statsToken the token tracking the current IME request.
* @param phase the phase the IME request failed at.
*/
- oneway void onFailed(in IBinder statsToken, int phase);
+ oneway void onFailed(in ImeTracker.Token statsToken, int phase);
/**
* Called when the IME request is cancelled.
@@ -66,21 +68,21 @@
* @param statsToken the token tracking the current IME request.
* @param phase the phase the IME request was cancelled at.
*/
- oneway void onCancelled(in IBinder statsToken, int phase);
+ oneway void onCancelled(in ImeTracker.Token statsToken, int phase);
/**
* Called when the IME show request is successful.
*
* @param statsToken the token tracking the current IME request.
*/
- oneway void onShown(in IBinder statsToken);
+ oneway void onShown(in ImeTracker.Token statsToken);
/**
* Called when the IME hide request is successful.
*
* @param statsToken the token tracking the current IME request.
*/
- oneway void onHidden(in IBinder statsToken);
+ oneway void onHidden(in ImeTracker.Token statsToken);
/**
* Checks whether there are any pending IME visibility requests.
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index f7e1d57..617519b 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -330,7 +330,10 @@
}
}
- public BatteryStatsHistory(HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) {
+ public BatteryStatsHistory(int maxHistoryFiles, int maxHistoryBufferSize,
+ HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) {
+ mMaxHistoryFiles = maxHistoryFiles;
+ mMaxHistoryBufferSize = maxHistoryBufferSize;
mStepDetailsCalculator = stepDetailsCalculator;
mTracer = new TraceDelegate();
mClock = clock;
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index b529a10..f7c03cd 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -56,6 +56,7 @@
void showRecentApps(boolean triggeredFromAltTab);
void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey);
void toggleRecentApps();
+ void toggleTaskbar();
void toggleSplitScreen();
void preloadRecentApps();
void cancelPreloadRecentApps();
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 9116cb3..9a4610e 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -23,11 +23,11 @@
import android.view.inputmethod.EditorInfo;
import android.window.ImeOnBackInvokedDispatcher;
+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.view.IImeTracker;
/**
* Public interface to the global input method manager, used by all client
@@ -148,6 +148,15 @@
/** Start Stylus handwriting session **/
void startStylusHandwriting(in IInputMethodClient client);
+ /** Prepares delegation of starting stylus handwriting session to a different editor **/
+ void prepareStylusHandwritingDelegation(in IInputMethodClient client,
+ in String delegatePackageName,
+ in String delegatorPackageName);
+
+ /** Accepts and starts a stylus handwriting session for the delegate view **/
+ boolean acceptStylusHandwritingDelegation(in IInputMethodClient client,
+ in String delegatePackageName, in String delegatorPackageName);
+
/** Returns {@code true} if currently selected IME supports Stylus handwriting. */
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)")
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 16db818..85d1765 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7107,6 +7107,13 @@
@hide Not for use by third-party applications. -->
<permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND"
android:protectionLevel="signature|role" />
+
+ <!-- @SystemApi Permission that allows apps to disable the clipboard access notifications.
+ @hide
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.MANAGE_CLIPBOARD_ACCESS_NOTIFICATION"
+ android:protectionLevel="signature|installer" />
+
<!-- @hide Permission that suppresses the notification when the clipboard is accessed.
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.SUPPRESS_CLIPBOARD_ACCESS_NOTIFICATION"
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index d2ee5de..1bbe8ee 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1714,7 +1714,7 @@
this type.
-->
<flag name="remoteMessaging" value="0x200" />
- <!-- The system exmpted foreground service use cases.
+ <!-- The system exempted foreground service use cases.
<p>Requires the app to hold the permission
{@link android.Manifest.permission#FOREGROUND_SERVICE_SYSTEM_EXEMPTED} in order to use
this type. Apps are allowed to use this type only in the use cases listed in
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 12eff67..2f94ed7 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3065,6 +3065,10 @@
<string name="config_credentialManagerDialogComponent" translatable="false"
>com.android.credentialmanager/com.android.credentialmanager.CredentialSelectorActivity</string>
+ <!-- Name of the broadcast receiver that is used to receive provider change events -->
+ <string name="config_credentialManagerReceiverComponent" translatable="false"
+ >com.android.credentialmanager/com.android.credentialmanager.CredentialProviderReceiver</string>
+
<!-- Apps that are authorized to access shared accounts, overridden by product overlays -->
<string name="config_appsAuthorizedForSharedAccounts" translatable="false">;com.android.settings;</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7e89fc8..12646a0 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2210,6 +2210,7 @@
<java-symbol type="string" name="config_platformVpnConfirmDialogComponent" />
<java-symbol type="string" name="config_carrierAppInstallDialogComponent" />
<java-symbol type="string" name="config_credentialManagerDialogComponent" />
+ <java-symbol type="string" name="config_credentialManagerReceiverComponent" />
<java-symbol type="string" name="config_defaultNetworkScorerPackageName" />
<java-symbol type="string" name="config_persistentDataPackageName" />
<java-symbol type="string" name="config_deviceConfiguratorPackageName" />
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
index 55ef854..980211f 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
@@ -27,6 +27,7 @@
import android.os.Bundle;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
+import android.util.PollingCheck;
import android.view.View;
import android.widget.TextView;
@@ -91,6 +92,9 @@
var densityRef = new AtomicReference<Float>();
scenario.onActivity(activity -> {
+ assertThat(activity.getResources().getConfiguration().fontScale)
+ .isWithin(0.05f)
+ .of(2f);
densityRef.compareAndSet(null, activity.getResources().getDisplayMetrics().density);
});
var density = densityRef.get();
@@ -141,6 +145,15 @@
fontScale
);
});
+
+ PollingCheck.waitFor(/* timeout= */ 5000, () ->
+ InstrumentationRegistry
+ .getInstrumentation()
+ .getContext()
+ .getResources()
+ .getConfiguration()
+ .fontScale == fontScale
+ );
}
private Matcher<View> withTextSizeInRange(float sizeStartPx, float sizeEndPx) {
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
index 249e246..ef106bc 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
@@ -90,11 +90,7 @@
}
.forEach { (table, sp) ->
try {
- assertWithMessage(
- "convertSpToDp(%s) on table: %s",
- sp.toString(),
- table.toString()
- )
+ assertWithMessage("convertSpToDp(%s) on table: %s", sp, table)
.that(table.convertSpToDp(sp))
.isFinite()
} catch (e: Exception) {
diff --git a/core/tests/coretests/src/android/credentials/CredentialManagerTest.java b/core/tests/coretests/src/android/credentials/CredentialManagerTest.java
index 444e9f2..25664fb 100644
--- a/core/tests/coretests/src/android/credentials/CredentialManagerTest.java
+++ b/core/tests/coretests/src/android/credentials/CredentialManagerTest.java
@@ -19,7 +19,6 @@
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
-import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.same;
@@ -102,8 +101,11 @@
mGetRequest = new GetCredentialRequest.Builder(Bundle.EMPTY).addCredentialOption(
new CredentialOption(Credential.TYPE_PASSWORD_CREDENTIAL, Bundle.EMPTY,
Bundle.EMPTY, false)).build();
- mCreateRequest = new CreateCredentialRequest(Credential.TYPE_PASSWORD_CREDENTIAL,
- Bundle.EMPTY, Bundle.EMPTY, false, false);
+ mCreateRequest = new CreateCredentialRequest.Builder(Bundle.EMPTY, Bundle.EMPTY)
+ .setType(Credential.TYPE_PASSWORD_CREDENTIAL)
+ .setIsSystemProviderRequired(false)
+ .setAlwaysSendAppInfoToProvider(false)
+ .build();
mClearRequest = new ClearCredentialStateRequest(Bundle.EMPTY);
final Slice slice = new Slice.Builder(Uri.parse("foo://bar"), null).addText("some text",
@@ -593,15 +595,12 @@
@Test
public void testRegisterCredentialDescription_nullRequest() {
- assumeTrue(CredentialManager.isCredentialDescriptionApiEnabled());
assertThrows(NullPointerException.class,
() -> mCredentialManager.registerCredentialDescription(null));
}
@Test
public void testRegisterCredentialDescription_success() throws RemoteException {
- assumeTrue(CredentialManager.isCredentialDescriptionApiEnabled());
-
mCredentialManager.registerCredentialDescription(mRegisterRequest);
verify(mMockCredentialManagerService).registerCredentialDescription(same(mRegisterRequest),
eq(mPackageName));
@@ -609,16 +608,12 @@
@Test
public void testUnregisterCredentialDescription_nullRequest() {
- assumeTrue(CredentialManager.isCredentialDescriptionApiEnabled());
-
assertThrows(NullPointerException.class,
() -> mCredentialManager.unregisterCredentialDescription(null));
}
@Test
public void testUnregisterCredentialDescription_success() throws RemoteException {
- assumeTrue(CredentialManager.isCredentialDescriptionApiEnabled());
-
mCredentialManager.unregisterCredentialDescription(mUnregisterRequest);
verify(mMockCredentialManagerService).unregisterCredentialDescription(
same(mUnregisterRequest), eq(mPackageName));
diff --git a/core/tests/coretests/src/android/os/CancellationSignalBeamerTest.java b/core/tests/coretests/src/android/os/CancellationSignalBeamerTest.java
new file mode 100644
index 0000000..42c97f3
--- /dev/null
+++ b/core/tests/coretests/src/android/os/CancellationSignalBeamerTest.java
@@ -0,0 +1,224 @@
+/*
+ * 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.os;
+
+import static android.os.CancellationSignalBeamer.Sender;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.CancellationSignalBeamer.Receiver;
+import android.util.PollingCheck;
+import android.util.PollingCheck.PollingCheckCondition;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.ref.Cleaner;
+import java.lang.ref.Reference;
+import java.util.concurrent.CountDownLatch;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class CancellationSignalBeamerTest {
+
+ private CancellationSignal mSenderSignal = new CancellationSignal();
+ private CancellationSignal mReceivedSignal;
+ private Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+
+ @Test
+ public void testBeam_null() {
+ try (var token = mSender.beam(null)) {
+ assertThat(token).isNull();
+ invokeGenericService(token);
+ }
+ assertThat(mReceivedSignal).isNull();
+ }
+
+ @Test
+ public void testBeam_nonNull() {
+ try (var token = mSender.beam(mSenderSignal)) {
+ assertThat(token).isNotNull();
+ invokeGenericService(token);
+ }
+ assertThat(mReceivedSignal).isNotNull();
+ }
+
+ @Test
+ public void testBeam_async() {
+ IBinder outerToken;
+ try (var token = mSender.beam(mSenderSignal)) {
+ assertThat(token).isNotNull();
+ outerToken = token;
+ }
+ invokeGenericService(outerToken);
+ assertThat(mReceivedSignal).isNotNull();
+ }
+
+ @Test
+ public void testCancelOnSentSignal_cancelsReceivedSignal() {
+ try (var token = mSender.beam(mSenderSignal)) {
+ invokeGenericService(token);
+ }
+ mSenderSignal.cancel();
+ assertThat(mReceivedSignal.isCanceled()).isTrue();
+ }
+
+ @Test
+ public void testSendingCancelledSignal_cancelsReceivedSignal() {
+ mSenderSignal.cancel();
+ try (var token = mSender.beam(mSenderSignal)) {
+ invokeGenericService(token);
+ }
+ assertThat(mReceivedSignal.isCanceled()).isTrue();
+ }
+
+ @Test
+ public void testUnbeam_null() {
+ assertThat(mReceiver.unbeam(null)).isNull();
+ }
+
+ @Test
+ public void testForget_null() {
+ mReceiver.forget(null);
+ }
+
+ @Test
+ public void testCancel_null() {
+ mReceiver.cancel(null);
+ }
+
+ @Test
+ public void testForget_withUnknownToken() {
+ mReceiver.forget(new Binder());
+ }
+
+ @Test
+ public void testCancel_withUnknownToken() {
+ mReceiver.cancel(new Binder());
+ }
+
+ @Test
+ public void testBinderDied_withUnknownToken() {
+ mReceiver.binderDied(new Binder());
+ }
+
+ @Test
+ public void testReceiverWithCancelOnSenderDead_cancelsOnSenderDeath() {
+ var receiver = new Receiver(true /* cancelOnSenderDeath */);
+ var token = new Binder();
+ var signal = receiver.unbeam(token);
+ receiver.binderDied(token);
+ assertThat(signal.isCanceled()).isTrue();
+ }
+
+ @Test
+ public void testReceiverWithoutCancelOnSenderDead_doesntCancelOnSenderDeath() {
+ var receiver = new Receiver(false /* cancelOnSenderDeath */);
+ var token = new Binder();
+ var signal = receiver.unbeam(token);
+ receiver.binderDied(token);
+ assertThat(signal.isCanceled()).isFalse();
+ }
+
+ @Test
+ public void testDroppingSentSignal_dropsReceivedSignal() throws Exception {
+ // In a multiprocess scenario, sending token over Binder might leak the token
+ // on both ends if we create a reference cycle. Simulate that worst-case scenario
+ // here by leaking it directly, then test that cleanup of the signals still works.
+ var receivedSignalCleaned = new CountDownLatch(1);
+ var tokenRef = new Object[1];
+ // Reference the cancellation signals in a separate method scope, so we don't
+ // accidentally leak them on the stack / in a register.
+ Runnable r = () -> {
+ try (var token = mSender.beam(mSenderSignal)) {
+ tokenRef[0] = token;
+ invokeGenericService(token);
+ }
+ mSenderSignal = null;
+
+ Cleaner.create().register(mReceivedSignal, receivedSignalCleaned::countDown);
+ mReceivedSignal = null;
+ };
+ r.run();
+
+ waitForWithGc(() -> receivedSignalCleaned.getCount() == 0);
+
+ Reference.reachabilityFence(tokenRef[0]);
+ }
+
+ @Test
+ public void testRepeatedBeaming_doesntLeak() throws Exception {
+ var receivedSignalCleaned = new CountDownLatch(1);
+ var tokenRef = new Object[1];
+ // Reference the cancellation signals in a separate method scope, so we don't
+ // accidentally leak them on the stack / in a register.
+ Runnable r = () -> {
+ try (var token = mSender.beam(mSenderSignal)) {
+ tokenRef[0] = token;
+ invokeGenericService(token);
+ }
+ // Beaming again leaves mReceivedSignal dangling, so it should be collected.
+ mSender.beam(mSenderSignal).close();
+
+ Cleaner.create().register(mReceivedSignal, receivedSignalCleaned::countDown);
+ mReceivedSignal = null;
+ };
+ r.run();
+
+ waitForWithGc(() -> receivedSignalCleaned.getCount() == 0);
+
+ Reference.reachabilityFence(tokenRef[0]);
+ }
+
+ private void waitForWithGc(PollingCheckCondition condition) throws IOException {
+ try {
+ PollingCheck.waitFor(() -> {
+ Runtime.getRuntime().gc();
+ return condition.canProceed();
+ });
+ } catch (AssertionError e) {
+ File heap = new File(mContext.getExternalFilesDir(null), "dump.hprof");
+ Debug.dumpHprofData(heap.getAbsolutePath());
+ throw e;
+ }
+ }
+
+ private void invokeGenericService(IBinder cancellationSignalToken) {
+ mReceivedSignal = mReceiver.unbeam(cancellationSignalToken);
+ }
+
+ private final Sender mSender = new Sender() {
+ @Override
+ public void onCancel(IBinder token) {
+ mReceiver.cancel(token);
+ }
+
+ @Override
+ public void onForget(IBinder token) {
+ mReceiver.forget(token);
+ }
+ };
+
+ private final Receiver mReceiver = new Receiver(false);
+}
diff --git a/core/tests/coretests/src/android/provider/NameValueCacheTest.java b/core/tests/coretests/src/android/provider/NameValueCacheTest.java
index 2e31bb5..b6fc137 100644
--- a/core/tests/coretests/src/android/provider/NameValueCacheTest.java
+++ b/core/tests/coretests/src/android/provider/NameValueCacheTest.java
@@ -32,16 +32,18 @@
import android.test.mock.MockContentResolver;
import android.util.MemoryIntArray;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -59,42 +61,63 @@
private static final String NAMESPACE = "namespace";
private static final String NAMESPACE2 = "namespace2";
+ private static final String SETTING = "test_setting";
+ private static final String SETTING2 = "test_setting2";
+
@Mock
private IContentProvider mMockIContentProvider;
@Mock
private ContentProvider mMockContentProvider;
private MockContentResolver mMockContentResolver;
- private MemoryIntArray mCacheGenerationStore;
- private int mCurrentGeneration = 123;
+ private MemoryIntArray mConfigsCacheGenerationStore;
+ private MemoryIntArray mSettingsCacheGenerationStore;
- private HashMap<String, HashMap<String, String>> mStorage;
+ private HashMap<String, HashMap<String, String>> mConfigsStorage;
+ private HashMap<String, String> mSettingsStorage;
+
@Before
public void setUp() throws Exception {
Settings.Config.clearProviderForTest();
+ Settings.Secure.clearProviderForTest();
MockitoAnnotations.initMocks(this);
when(mMockContentProvider.getIContentProvider()).thenReturn(mMockIContentProvider);
- mMockContentResolver = new MockContentResolver(InstrumentationRegistry
- .getInstrumentation().getContext());
+ mMockContentResolver = new MockContentResolver(
+ InstrumentationRegistry.getInstrumentation().getContext());
mMockContentResolver.addProvider(Settings.Config.CONTENT_URI.getAuthority(),
mMockContentProvider);
- mCacheGenerationStore = new MemoryIntArray(1);
- mStorage = new HashMap<>();
+ mMockContentResolver.addProvider(Settings.Secure.CONTENT_URI.getAuthority(),
+ mMockContentProvider);
+ mConfigsCacheGenerationStore = new MemoryIntArray(2);
+ mConfigsCacheGenerationStore.set(0, 123);
+ mConfigsCacheGenerationStore.set(1, 456);
+ mSettingsCacheGenerationStore = new MemoryIntArray(2);
+ mSettingsCacheGenerationStore.set(0, 234);
+ mSettingsCacheGenerationStore.set(1, 567);
+ mConfigsStorage = new HashMap<>();
+ mSettingsStorage = new HashMap<>();
// Stores keyValues for a given prefix and increments the generation. (Note that this
// increments the generation no matter what, it doesn't pay attention to if anything
// actually changed).
when(mMockIContentProvider.call(any(), eq(Settings.Config.CONTENT_URI.getAuthority()),
- eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
- any(), any(Bundle.class))).thenAnswer(invocationOnMock -> {
+ eq(Settings.CALL_METHOD_SET_ALL_CONFIG), any(), any(Bundle.class))).thenAnswer(
+ invocationOnMock -> {
Bundle incomingBundle = invocationOnMock.getArgument(4);
HashMap<String, String> keyValues =
(HashMap<String, String>) incomingBundle.getSerializable(
- Settings.CALL_METHOD_FLAGS_KEY);
+ Settings.CALL_METHOD_FLAGS_KEY, HashMap.class);
String prefix = incomingBundle.getString(Settings.CALL_METHOD_PREFIX_KEY);
- mStorage.put(prefix, keyValues);
- mCacheGenerationStore.set(0, ++mCurrentGeneration);
-
+ mConfigsStorage.put(prefix, keyValues);
+ int currentGeneration;
+ // Different prefixes have different generation codes
+ if (prefix.equals(NAMESPACE + "/")) {
+ currentGeneration = mConfigsCacheGenerationStore.get(0);
+ mConfigsCacheGenerationStore.set(0, ++currentGeneration);
+ } else if (prefix.equals(NAMESPACE2 + "/")) {
+ currentGeneration = mConfigsCacheGenerationStore.get(1);
+ mConfigsCacheGenerationStore.set(1, ++currentGeneration);
+ }
Bundle result = new Bundle();
result.putInt(Settings.KEY_CONFIG_SET_ALL_RETURN,
Settings.SET_ALL_RESULT_SUCCESS);
@@ -102,32 +125,87 @@
});
// Returns the keyValues corresponding to a namespace, or an empty map if the namespace
- // doesn't have anything stored for it. Returns the generation key if the caller asked
- // for one.
+ // doesn't have anything stored for it. Returns the generation key if the map isn't empty
+ // and the caller asked for the generation key.
when(mMockIContentProvider.call(any(), eq(Settings.Config.CONTENT_URI.getAuthority()),
- eq(Settings.CALL_METHOD_LIST_CONFIG),
- any(), any(Bundle.class))).thenAnswer(invocationOnMock -> {
+ eq(Settings.CALL_METHOD_LIST_CONFIG), any(), any(Bundle.class))).thenAnswer(
+ invocationOnMock -> {
Bundle incomingBundle = invocationOnMock.getArgument(4);
String prefix = incomingBundle.getString(Settings.CALL_METHOD_PREFIX_KEY);
- if (!mStorage.containsKey(prefix)) {
- mStorage.put(prefix, new HashMap<>());
+ if (!mConfigsStorage.containsKey(prefix)) {
+ mConfigsStorage.put(prefix, new HashMap<>());
}
- HashMap<String, String> keyValues = mStorage.get(prefix);
+ HashMap<String, String> keyValues = mConfigsStorage.get(prefix);
Bundle bundle = new Bundle();
bundle.putSerializable(Settings.NameValueTable.VALUE, keyValues);
- if (incomingBundle.containsKey(Settings.CALL_METHOD_TRACK_GENERATION_KEY)) {
+ if (!keyValues.isEmpty() && incomingBundle.containsKey(
+ Settings.CALL_METHOD_TRACK_GENERATION_KEY)) {
+ int index = prefix.equals(NAMESPACE + "/") ? 0 : 1;
bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY,
- mCacheGenerationStore);
- bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, 0);
+ mConfigsCacheGenerationStore);
+ bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, index);
bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY,
- mCacheGenerationStore.get(0));
+ mConfigsCacheGenerationStore.get(index));
}
return bundle;
});
+
+ // Stores value for a given setting's name and increments the generation. (Note that this
+ // increments the generation no matter what, it doesn't pay attention to if anything
+ // actually changed).
+ when(mMockIContentProvider.call(any(), eq(Settings.Secure.CONTENT_URI.getAuthority()),
+ eq(Settings.CALL_METHOD_PUT_SECURE), any(), any(Bundle.class))).thenAnswer(
+ invocationOnMock -> {
+ Bundle incomingBundle = invocationOnMock.getArgument(4);
+ String key = invocationOnMock.getArgument(3);
+ String value = incomingBundle.getString(Settings.NameValueTable.VALUE);
+ mSettingsStorage.put(key, value);
+ int currentGeneration;
+ // Different settings have different generation codes
+ if (key.equals(SETTING)) {
+ currentGeneration = mSettingsCacheGenerationStore.get(0);
+ mSettingsCacheGenerationStore.set(0, ++currentGeneration);
+ } else if (key.equals(SETTING2)) {
+ currentGeneration = mSettingsCacheGenerationStore.get(1);
+ mSettingsCacheGenerationStore.set(1, ++currentGeneration);
+ }
+ return null;
+ });
+
+ // Returns the value corresponding to a setting, or null if the setting
+ // doesn't have a value stored for it. Returns the generation key if the value isn't null
+ // and the caller asked for the generation key.
+ when(mMockIContentProvider.call(any(), eq(Settings.Secure.CONTENT_URI.getAuthority()),
+ eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class))).thenAnswer(
+ invocationOnMock -> {
+ Bundle incomingBundle = invocationOnMock.getArgument(4);
+ String key = invocationOnMock.getArgument(3);
+ String value = mSettingsStorage.get(key);
+
+ Bundle bundle = new Bundle();
+ bundle.putSerializable(Settings.NameValueTable.VALUE, value);
+
+ if (value != null && incomingBundle.containsKey(
+ Settings.CALL_METHOD_TRACK_GENERATION_KEY)) {
+ int index = key.equals(SETTING) ? 0 : 1;
+ bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY,
+ mSettingsCacheGenerationStore);
+ bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, index);
+ bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY,
+ mSettingsCacheGenerationStore.get(index));
+ }
+ return bundle;
+ });
+ }
+
+ @After
+ public void cleanUp() throws IOException {
+ mConfigsStorage.clear();
+ mSettingsStorage.clear();
}
@Test
@@ -135,16 +213,13 @@
HashMap<String, String> keyValues = new HashMap<>();
keyValues.put("a", "b");
Settings.Config.setStrings(mMockContentResolver, NAMESPACE, keyValues);
- verify(mMockIContentProvider).call(any(), any(),
- eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
- any(), any(Bundle.class));
+ verify(mMockIContentProvider, times(1)).call(any(), any(),
+ eq(Settings.CALL_METHOD_SET_ALL_CONFIG), any(), any(Bundle.class));
Map<String, String> returnedValues = Settings.Config.getStrings(mMockContentResolver,
- NAMESPACE,
- Collections.emptyList());
- verify(mMockIContentProvider).call(any(), any(),
- eq(Settings.CALL_METHOD_LIST_CONFIG),
- any(), any(Bundle.class));
+ NAMESPACE, Collections.emptyList());
+ verify(mMockIContentProvider, times(1)).call(any(), any(),
+ eq(Settings.CALL_METHOD_LIST_CONFIG), any(), any(Bundle.class));
assertThat(returnedValues).containsExactlyEntriesIn(keyValues);
Map<String, String> cachedKeyValues = Settings.Config.getStrings(mMockContentResolver,
@@ -156,14 +231,12 @@
keyValues.put("a", "c");
Settings.Config.setStrings(mMockContentResolver, NAMESPACE, keyValues);
verify(mMockIContentProvider, times(2)).call(any(), any(),
- eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
- any(), any(Bundle.class));
+ eq(Settings.CALL_METHOD_SET_ALL_CONFIG), any(), any(Bundle.class));
Map<String, String> returnedValues2 = Settings.Config.getStrings(mMockContentResolver,
NAMESPACE, Collections.emptyList());
verify(mMockIContentProvider, times(2)).call(any(), any(),
- eq(Settings.CALL_METHOD_LIST_CONFIG),
- any(), any(Bundle.class));
+ eq(Settings.CALL_METHOD_LIST_CONFIG), any(), any(Bundle.class));
assertThat(returnedValues2).containsExactlyEntriesIn(keyValues);
Map<String, String> cachedKeyValues2 = Settings.Config.getStrings(mMockContentResolver,
@@ -177,36 +250,31 @@
HashMap<String, String> keyValues = new HashMap<>();
keyValues.put("a", "b");
Settings.Config.setStrings(mMockContentResolver, NAMESPACE, keyValues);
- verify(mMockIContentProvider).call(any(), any(),
- eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
+ verify(mMockIContentProvider).call(any(), any(), eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
any(), any(Bundle.class));
+ Map<String, String> returnedValues = Settings.Config.getStrings(mMockContentResolver,
+ NAMESPACE, Collections.emptyList());
+ verify(mMockIContentProvider).call(any(), any(), eq(Settings.CALL_METHOD_LIST_CONFIG),
+ any(), any(Bundle.class));
+ assertThat(returnedValues).containsExactlyEntriesIn(keyValues);
+
HashMap<String, String> keyValues2 = new HashMap<>();
keyValues2.put("c", "d");
keyValues2.put("e", "f");
Settings.Config.setStrings(mMockContentResolver, NAMESPACE2, keyValues2);
verify(mMockIContentProvider, times(2)).call(any(), any(),
- eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
- any(), any(Bundle.class));
-
- Map<String, String> returnedValues = Settings.Config.getStrings(mMockContentResolver,
- NAMESPACE,
- Collections.emptyList());
- verify(mMockIContentProvider).call(any(), any(),
- eq(Settings.CALL_METHOD_LIST_CONFIG),
- any(), any(Bundle.class));
- assertThat(returnedValues).containsExactlyEntriesIn(keyValues);
+ eq(Settings.CALL_METHOD_SET_ALL_CONFIG), any(), any(Bundle.class));
Map<String, String> returnedValues2 = Settings.Config.getStrings(mMockContentResolver,
- NAMESPACE2,
- Collections.emptyList());
+ NAMESPACE2, Collections.emptyList());
verify(mMockIContentProvider, times(2)).call(any(), any(),
- eq(Settings.CALL_METHOD_LIST_CONFIG),
- any(), any(Bundle.class));
+ eq(Settings.CALL_METHOD_LIST_CONFIG), any(), any(Bundle.class));
assertThat(returnedValues2).containsExactlyEntriesIn(keyValues2);
Map<String, String> cachedKeyValues = Settings.Config.getStrings(mMockContentResolver,
NAMESPACE, Collections.emptyList());
+ // Modifying the second namespace doesn't affect the cache of the first namespace
verifyNoMoreInteractions(mMockIContentProvider);
assertThat(cachedKeyValues).containsExactlyEntriesIn(keyValues);
@@ -219,17 +287,90 @@
@Test
public void testCaching_emptyNamespace() throws Exception {
Map<String, String> returnedValues = Settings.Config.getStrings(mMockContentResolver,
- NAMESPACE,
- Collections.emptyList());
- verify(mMockIContentProvider).call(any(), any(),
- eq(Settings.CALL_METHOD_LIST_CONFIG),
- any(), any(Bundle.class));
+ NAMESPACE, Collections.emptyList());
+ verify(mMockIContentProvider, times(1)).call(any(), any(),
+ eq(Settings.CALL_METHOD_LIST_CONFIG), any(), any(Bundle.class));
assertThat(returnedValues).isEmpty();
Map<String, String> cachedKeyValues = Settings.Config.getStrings(mMockContentResolver,
NAMESPACE, Collections.emptyList());
- verifyNoMoreInteractions(mMockIContentProvider);
+ // Empty list won't be cached
+ verify(mMockIContentProvider, times(2)).call(any(), any(),
+ eq(Settings.CALL_METHOD_LIST_CONFIG), any(), any(Bundle.class));
assertThat(cachedKeyValues).isEmpty();
}
+ @Test
+ public void testCaching_singleSetting() throws Exception {
+ Settings.Secure.putString(mMockContentResolver, SETTING, "a");
+ verify(mMockIContentProvider, times(1)).call(any(), any(),
+ eq(Settings.CALL_METHOD_PUT_SECURE), any(), any(Bundle.class));
+
+ String returnedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
+ verify(mMockIContentProvider, times(1)).call(any(), any(),
+ eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
+ assertThat(returnedValue).isEqualTo("a");
+
+ String cachedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
+ verifyNoMoreInteractions(mMockIContentProvider);
+ assertThat(cachedValue).isEqualTo("a");
+
+ // Modify the value to invalidate the cache.
+ Settings.Secure.putString(mMockContentResolver, SETTING, "b");
+ verify(mMockIContentProvider, times(2)).call(any(), any(),
+ eq(Settings.CALL_METHOD_PUT_SECURE), any(), any(Bundle.class));
+
+ String returnedValue2 = Settings.Secure.getString(mMockContentResolver, SETTING);
+ verify(mMockIContentProvider, times(2)).call(any(), any(),
+ eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
+ assertThat(returnedValue2).isEqualTo("b");
+
+ String cachedValue2 = Settings.Secure.getString(mMockContentResolver, SETTING);
+ verifyNoMoreInteractions(mMockIContentProvider);
+ assertThat(cachedValue2).isEqualTo("b");
+ }
+
+ @Test
+ public void testCaching_multipleSettings() throws Exception {
+ Settings.Secure.putString(mMockContentResolver, SETTING, "a");
+ verify(mMockIContentProvider, times(1)).call(any(), any(),
+ eq(Settings.CALL_METHOD_PUT_SECURE), any(), any(Bundle.class));
+
+ String returnedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
+ verify(mMockIContentProvider, times(1)).call(any(), any(),
+ eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
+ assertThat(returnedValue).isEqualTo("a");
+
+ Settings.Secure.putString(mMockContentResolver, SETTING2, "b");
+ verify(mMockIContentProvider, times(2)).call(any(), any(),
+ eq(Settings.CALL_METHOD_PUT_SECURE), any(), any(Bundle.class));
+
+ String returnedValue2 = Settings.Secure.getString(mMockContentResolver, SETTING2);
+ verify(mMockIContentProvider, times(2)).call(any(), any(),
+ eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
+ assertThat(returnedValue2).isEqualTo("b");
+
+ String cachedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
+ // Modifying the second setting doesn't affect the cache of the first setting
+ verifyNoMoreInteractions(mMockIContentProvider);
+ assertThat(cachedValue).isEqualTo("a");
+
+ String cachedValue2 = Settings.Secure.getString(mMockContentResolver, SETTING2);
+ verifyNoMoreInteractions(mMockIContentProvider);
+ assertThat(cachedValue2).isEqualTo("b");
+ }
+
+ @Test
+ public void testCaching_nullSetting() throws Exception {
+ String returnedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
+ verify(mMockIContentProvider, times(1)).call(any(), any(),
+ eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
+ assertThat(returnedValue).isNull();
+
+ String cachedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
+ // Empty list won't be cached
+ verify(mMockIContentProvider, times(2)).call(any(), any(),
+ eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
+ assertThat(cachedValue).isNull();
+ }
}
diff --git a/core/tests/coretests/src/android/view/KeyEventTest.java b/core/tests/coretests/src/android/view/KeyEventTest.java
index 32078ca..f723f15 100644
--- a/core/tests/coretests/src/android/view/KeyEventTest.java
+++ b/core/tests/coretests/src/android/view/KeyEventTest.java
@@ -47,14 +47,14 @@
private static final int ACTION = KeyEvent.ACTION_DOWN;
private static final int ANOTHER_ACTION = KeyEvent.ACTION_UP;
private static final int KEYCODE = KeyEvent.KEYCODE_0;
- private static final int REPEAT = 0;
- private static final int ANOTHER_REPEAT = 42;
- private static final int METASTATE = 0;
- private static final int DEVICE_ID = 0;
- private static final int SCAN_CODE = 0;
- private static final int FLAGS = 0;
+ private static final int REPEAT = 4;
+ private static final int ANOTHER_REPEAT = 8;
+ private static final int METASTATE = 15;
+ private static final int DEVICE_ID = 16;
+ private static final int SCAN_CODE = 23;
+ private static final int FLAGS = 42;
private static final int SOURCE = InputDevice.SOURCE_KEYBOARD;
- private static final String CHARACTERS = null;
+ private static final String CHARACTERS = "CHARACTERS, Y U NO @NONNULL?";
private static final int ID_SOURCE_MASK = 0x3 << 30;
@@ -178,7 +178,7 @@
DEVICE_ID, SCAN_CODE, FLAGS, SOURCE);
assertKeyEventFields(key, DOWN_TIME, EVENT_TIME, ACTION, KEYCODE, REPEAT, METASTATE,
- DEVICE_ID, SCAN_CODE, FLAGS, SOURCE, INVALID_DISPLAY, CHARACTERS);
+ DEVICE_ID, SCAN_CODE, FLAGS, SOURCE, INVALID_DISPLAY, /* characters= */ null);
}
@Test
@@ -187,7 +187,8 @@
DEVICE_ID, SCAN_CODE, FLAGS);
assertKeyEventFields(key, DOWN_TIME, EVENT_TIME, ACTION, KEYCODE, REPEAT, METASTATE,
- DEVICE_ID, SCAN_CODE, FLAGS, /* source= */ 0, INVALID_DISPLAY, CHARACTERS);
+ DEVICE_ID, SCAN_CODE, FLAGS, /* source= */ 0, INVALID_DISPLAY,
+ /* characters= */ null);
}
@Test
@@ -197,7 +198,7 @@
assertKeyEventFields(key, DOWN_TIME, EVENT_TIME, ACTION, KEYCODE, REPEAT, METASTATE,
DEVICE_ID, SCAN_CODE, /* flags= */ 0, /* source= */ 0, INVALID_DISPLAY,
- CHARACTERS);
+ /* characters= */ null);
}
@Test
@@ -206,7 +207,7 @@
assertKeyEventFields(key, DOWN_TIME, EVENT_TIME, ACTION, KEYCODE, REPEAT, METASTATE,
/* deviceId= */ KeyCharacterMap.VIRTUAL_KEYBOARD, /* scanCode= */ 0, /* flags= */ 0,
- /* source= */ 0, INVALID_DISPLAY, CHARACTERS);
+ /* source= */ 0, INVALID_DISPLAY, /* characters= */ null);
}
@Test
@@ -215,7 +216,8 @@
assertKeyEventFields(key, DOWN_TIME, EVENT_TIME, ACTION, KEYCODE, REPEAT,
/* metaState= */ 0, /* deviceId= */ KeyCharacterMap.VIRTUAL_KEYBOARD,
- /* scanCode= */ 0, /* flags= */ 0, /* source= */ 0, INVALID_DISPLAY, CHARACTERS);
+ /* scanCode= */ 0, /* flags= */ 0, /* source= */ 0, INVALID_DISPLAY,
+ /* characters= */ null);
}
@Test
@@ -233,8 +235,8 @@
assertKeyEventFields(key, /* downTime= */ 0, /* eventTime= */ 0, ACTION, KEYCODE,
/* repeat= */ 0, /* metaState= */ 0,
- /* deviceId= */ KeyCharacterMap.VIRTUAL_KEYBOARD, /* scanCode= */ 0, FLAGS,
- /* source= */ 0, INVALID_DISPLAY, CHARACTERS);
+ /* deviceId= */ KeyCharacterMap.VIRTUAL_KEYBOARD, /* scanCode= */ 0, /* flags= */ 0,
+ /* source= */ 0, INVALID_DISPLAY, /* characters= */ null);
}
@Test
@@ -244,7 +246,7 @@
assertKeyEventFields(key2, DOWN_TIME, EVENT_TIME, ANOTHER_ACTION, KEYCODE, REPEAT,
METASTATE, DEVICE_ID, SCAN_CODE, FLAGS, InputDevice.SOURCE_KEYBOARD,
- INVALID_DISPLAY, CHARACTERS);
+ INVALID_DISPLAY, /* characters= */ null);
expect.withMessage("id (key1=%s, key2=%s", key1, key2).that(key2.getId())
.isNotEqualTo(key1.getId());
}
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
index 95aa5d0..76f5277 100644
--- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
@@ -32,7 +32,6 @@
import android.content.Context;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
-import android.view.HandwritingDelegateConfiguration;
import android.view.HandwritingInitiator;
import android.view.InputDevice;
import android.view.MotionEvent;
@@ -210,14 +209,11 @@
@Test
public void onTouchEvent_startHandwriting_delegate() {
- int delegatorViewId = 234;
- View delegatorView = new View(mContext);
- delegatorView.setId(delegatorViewId);
+ View delegateView = new View(mContext);
+ delegateView.setIsHandwritingDelegate(true);
- mTestView.setHandwritingDelegateConfiguration(
- new HandwritingDelegateConfiguration(
- delegatorViewId,
- () -> mHandwritingInitiator.onInputConnectionCreated(delegatorView)));
+ mTestView.setHandwritingDelegatorCallback(
+ () -> mHandwritingInitiator.onInputConnectionCreated(delegateView));
final int x1 = (sHwArea.left + sHwArea.right) / 2;
final int y1 = (sHwArea.top + sHwArea.bottom) / 2;
@@ -229,7 +225,7 @@
MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
mHandwritingInitiator.onTouchEvent(stylusEvent2);
- verify(mHandwritingInitiator, times(1)).startHandwriting(delegatorView);
+ verify(mHandwritingInitiator, times(1)).tryAcceptStylusHandwritingDelegation(delegateView);
}
@Test
diff --git a/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java b/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java
index 0c7550e..777246b 100644
--- a/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java
@@ -33,6 +33,8 @@
import android.app.PendingIntent;
import android.app.RemoteAction;
import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.Icon;
import android.view.ContextMenu;
import android.view.MenuItem;
@@ -167,4 +169,75 @@
assertThat(idCaptor.getValue()).isEqualTo(TextView.ID_ASSIST);
assertThat(titleCaptor.getValue().toString()).isEqualTo(ACTION_TITLE);
}
+
+ @UiThreadTest
+ @Test
+ public void testAdjustIconSpaces() {
+ GradientDrawable gd = new GradientDrawable();
+ gd.setSize(128, 256);
+
+ // Setup mocks
+ ContextMenu menu = mock(ContextMenu.class);
+
+ MenuItem mockIconMenu = newMockMenuItem();
+ when(mockIconMenu.getIcon()).thenReturn(gd);
+
+ MenuItem mockNoIconMenu = newMockMenuItem();
+ when(mockNoIconMenu.getIcon()).thenReturn(null);
+
+ MenuItem mockNoIconMenu2 = newMockMenuItem();
+ when(mockNoIconMenu2.getIcon()).thenReturn(null);
+
+ when(menu.size()).thenReturn(3);
+ when(menu.getItem(0)).thenReturn(mockIconMenu);
+ when(menu.getItem(1)).thenReturn(mockNoIconMenu);
+ when(menu.getItem(2)).thenReturn(mockNoIconMenu2);
+
+
+ // Execute the test method
+ EditText et = mActivity.findViewById(R.id.editText);
+ Editor editor = et.getEditorForTesting();
+ editor.adjustIconSpacing(menu);
+
+ // Verify
+ ArgumentCaptor<Drawable> drawableCaptor = ArgumentCaptor.forClass(Drawable.class);
+ verify(mockNoIconMenu).setIcon(drawableCaptor.capture());
+
+ Drawable paddingDrawable = drawableCaptor.getValue();
+ assertThat(paddingDrawable).isNotNull();
+ assertThat(paddingDrawable.getIntrinsicWidth()).isEqualTo(128);
+ assertThat(paddingDrawable.getIntrinsicHeight()).isEqualTo(256);
+
+ ArgumentCaptor<Drawable> drawableCaptor2 = ArgumentCaptor.forClass(Drawable.class);
+ verify(mockNoIconMenu2).setIcon(drawableCaptor2.capture());
+
+ Drawable paddingDrawable2 = drawableCaptor2.getValue();
+ assertThat(paddingDrawable2).isSameInstanceAs(paddingDrawable);
+ }
+
+ @UiThreadTest
+ @Test
+ public void testAdjustIconSpacesNoIconCase() {
+ // Setup mocks
+ ContextMenu menu = mock(ContextMenu.class);
+
+ MenuItem mockNoIconMenu = newMockMenuItem();
+ when(mockNoIconMenu.getIcon()).thenReturn(null);
+
+ MenuItem mockNoIconMenu2 = newMockMenuItem();
+ when(mockNoIconMenu2.getIcon()).thenReturn(null);
+
+ when(menu.size()).thenReturn(2);
+ when(menu.getItem(0)).thenReturn(mockNoIconMenu);
+ when(menu.getItem(1)).thenReturn(mockNoIconMenu2);
+
+ // Execute the test method
+ EditText et = mActivity.findViewById(R.id.editText);
+ Editor editor = et.getEditorForTesting();
+ editor.adjustIconSpacing(menu);
+
+ // Verify
+ verify(mockNoIconMenu, times(0)).setIcon(any());
+ verify(mockNoIconMenu2, times(0)).setIcon(any());
+ }
}
diff --git a/core/tests/mockingcoretests/src/android/view/DisplayTest.java b/core/tests/mockingcoretests/src/android/view/DisplayTest.java
index 9ccf3b3..3b8b8c7 100644
--- a/core/tests/mockingcoretests/src/android/view/DisplayTest.java
+++ b/core/tests/mockingcoretests/src/android/view/DisplayTest.java
@@ -117,6 +117,31 @@
}
@Test
+ public void testGetReportedHdrTypes_returns_mode_specific_hdr_types() {
+ setDisplayInfoPortrait(mDisplayInfo);
+ float[] alternativeRefreshRates = new float[0];
+ int[] hdrTypesWithDv = new int[] {1, 2, 3, 4};
+ Display.Mode modeWithDv = new Display.Mode(/* modeId= */ 0, 0, 0, 0f,
+ alternativeRefreshRates, hdrTypesWithDv);
+
+ int[] hdrTypesWithoutDv = new int[]{2, 3, 4};
+ Display.Mode modeWithoutDv = new Display.Mode(/* modeId= */ 1, 0, 0, 0f,
+ alternativeRefreshRates, hdrTypesWithoutDv);
+
+ mDisplayInfo.supportedModes = new Display.Mode[] {modeWithoutDv, modeWithDv};
+ mDisplayInfo.hdrCapabilities = new Display.HdrCapabilities(hdrTypesWithDv, 0, 0, 0);
+
+ final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
+ DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
+
+ mDisplayInfo.modeId = 0;
+ assertArrayEquals(hdrTypesWithDv, display.getReportedHdrTypes());
+
+ mDisplayInfo.modeId = 1;
+ assertArrayEquals(hdrTypesWithoutDv, display.getReportedHdrTypes());
+ }
+
+ @Test
public void testConstructor_defaultDisplayAdjustments_matchesDisplayInfo() {
setDisplayInfoPortrait(mDisplayInfo);
final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 71050fa..f3318f4 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -122,6 +122,7 @@
<permission name="android.permission.BIND_CARRIER_SERVICES"/>
<permission name="android.permission.BIND_CELL_BROADCAST_SERVICE"/>
<permission name="android.permission.BIND_IMS_SERVICE"/>
+ <permission name="android.permission.BIND_SATELLITE_SERVICE"/>
<permission name="android.permission.BIND_TELEPHONY_DATA_SERVICE"/>
<permission name="android.permission.BIND_VISUAL_VOICEMAIL_SERVICE"/>
<permission name="android.permission.CALL_PRIVILEGED"/>
@@ -508,6 +509,8 @@
<permission name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT"/>
<!-- Permission required for CTS test - CtsTelephonyProviderTestCases -->
<permission name="android.permission.WRITE_APN_SETTINGS"/>
+ <!-- Permission required for GTS test - GtsStatsdHostTestCases -->
+ <permission name="android.permission.READ_RESTRICTED_STATS"/>
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java
index e2012b4..88525aa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java
@@ -16,8 +16,6 @@
package com.android.wm.shell;
-import android.os.Build;
-
import com.android.wm.shell.protolog.ShellProtoLogImpl;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellInit;
@@ -43,9 +41,6 @@
void onInit() {
mShellCommandHandler.addCommandCallback("protolog", this, this);
- if (Build.IS_DEBUGGABLE) {
- mShellProtoLog.startProtoLog(null /* PrintWriter */);
- }
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index acf17e6..b3fff1d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -29,8 +29,9 @@
import android.database.ContentObserver;
import android.hardware.input.InputManager;
import android.net.Uri;
+import android.os.Bundle;
import android.os.Handler;
-import android.os.IBinder;
+import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemProperties;
@@ -39,7 +40,6 @@
import android.util.Log;
import android.util.SparseArray;
import android.view.IRemoteAnimationRunner;
-import android.view.IWindowFocusObserver;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
@@ -121,23 +121,22 @@
private IOnBackInvokedCallback mActiveCallback;
@VisibleForTesting
- final IWindowFocusObserver mFocusObserver = new IWindowFocusObserver.Stub() {
- @Override
- public void focusGained(IBinder inputToken) { }
- @Override
- public void focusLost(IBinder inputToken) {
- mShellExecutor.execute(() -> {
- if (!mBackGestureStarted || mPostCommitAnimationInProgress) {
- // If an uninterruptible animation is already in progress, we should ignore
- // this due to it may cause focus lost. (alpha = 0)
- return;
+ final RemoteCallback mNavigationObserver = new RemoteCallback(
+ new RemoteCallback.OnResultListener() {
+ @Override
+ public void onResult(@Nullable Bundle result) {
+ mShellExecutor.execute(() -> {
+ if (!mBackGestureStarted || mPostCommitAnimationInProgress) {
+ // If an uninterruptible animation is already in progress, we should
+ // ignore this due to it may cause focus lost. (alpha = 0)
+ return;
+ }
+ ProtoLog.i(WM_SHELL_BACK_PREVIEW, "Navigation window gone.");
+ setTriggerBack(false);
+ onGestureFinished(false);
+ });
}
- ProtoLog.i(WM_SHELL_BACK_PREVIEW, "Target window lost focus.");
- setTriggerBack(false);
- onGestureFinished(false);
});
- }
- };
private final BackAnimationBackground mAnimationBackground;
@@ -351,7 +350,7 @@
try {
mBackNavigationInfo = mActivityTaskManager.startBackNavigation(
- mFocusObserver, mEnableAnimations.get() ? mBackAnimationAdapter : null);
+ mNavigationObserver, mEnableAnimations.get() ? mBackAnimationAdapter : null);
onBackNavigationInfoReceived(mBackNavigationInfo);
} catch (RemoteException remoteException) {
Log.e(TAG, "Failed to initAnimation", remoteException);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index 5933ac2..713dd39 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -59,9 +59,6 @@
public static final long TOUCH_ANIMATION_DURATION = 150;
public static final long TOUCH_RELEASE_ANIMATION_DURATION = 200;
- /** The task bar expanded height. Used to determine whether to insets divider bounds or not. */
- private float mExpandedTaskBarHeight;
-
private final int mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
private SplitLayout mSplitLayout;
@@ -216,17 +213,19 @@
void onInsetsChanged(InsetsState insetsState, boolean animate) {
mSplitLayout.getDividerBounds(mTempRect);
- final InsetsSource taskBarInsetsSource =
- insetsState.peekSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
// Only insets the divider bar with task bar when it's expanded so that the rounded corners
// will be drawn against task bar.
// But there is no need to do it when IME showing because there are no rounded corners at
// the bottom. This also avoids the problem of task bar height not changing when IME
// floating.
- if (!insetsState.isSourceOrDefaultVisible(InsetsSource.ID_IME, WindowInsets.Type.ime())
- && taskBarInsetsSource != null
- && taskBarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
- mTempRect.inset(taskBarInsetsSource.calculateVisibleInsets(mTempRect));
+ if (!insetsState.isSourceOrDefaultVisible(InsetsSource.ID_IME, WindowInsets.Type.ime())) {
+ for (int i = insetsState.sourceSize() - 1; i >= 0; i--) {
+ final InsetsSource source = insetsState.sourceAt(i);
+ if (source.getType() == WindowInsets.Type.navigationBars()
+ && source.insetsRoundedCornerFrame()) {
+ mTempRect.inset(source.calculateVisibleInsets(mTempRect));
+ }
+ }
}
if (!mTempRect.equals(mDividerBounds)) {
@@ -251,8 +250,6 @@
mDividerBar = findViewById(R.id.divider_bar);
mHandle = findViewById(R.id.docked_divider_handle);
mBackground = findViewById(R.id.docked_divider_background);
- mExpandedTaskBarHeight = getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.taskbar_frame_height);
mTouchElevation = getResources().getDimensionPixelSize(
R.dimen.docked_stack_divider_lift_elevation);
mDoubleTapDetector = new GestureDetector(getContext(), new DoubleTapListener());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index f616e6f..8a18271 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -1091,7 +1091,7 @@
// ImePositionProcessor#onImeVisibilityChanged directly in DividerView is not enough
// because DividerView won't receive onImeVisibilityChanged callback after it being
// re-inflated.
- mSplitWindowManager.setInteractive(!mImeShown || !mHasImeFocus,
+ mSplitWindowManager.setInteractive(!mImeShown || !mHasImeFocus || isFloating,
"onImeStartPositioning");
return needOffset ? IME_ANIMATION_NO_ALPHA : 0;
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 fce0138..73a7403 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
@@ -85,7 +85,7 @@
fun showDesktopApps() {
ProtoLog.v(WM_SHELL_DESKTOP_MODE, "showDesktopApps")
val wct = WindowContainerTransaction()
- bringDesktopAppsToFront(wct, force = true)
+ bringDesktopAppsToFront(wct)
// Execute transaction if there are pending operations
if (!wct.isEmpty) {
@@ -156,19 +156,9 @@
?: WINDOWING_MODE_UNDEFINED
}
- private fun bringDesktopAppsToFront(wct: WindowContainerTransaction, force: Boolean = false) {
- val activeTasks = desktopModeTaskRepository.getActiveTasks()
-
- // Skip if all tasks are already visible
- if (!force && activeTasks.all(desktopModeTaskRepository::isVisibleTask)) {
- ProtoLog.d(
- WM_SHELL_DESKTOP_MODE,
- "bringDesktopAppsToFront: active tasks are already in front, skipping."
- )
- return
- }
-
+ private fun bringDesktopAppsToFront(wct: WindowContainerTransaction) {
ProtoLog.v(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront")
+ val activeTasks = desktopModeTaskRepository.getActiveTasks()
// First move home to front and then other tasks on top of it
moveHomeTaskToFront(wct)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
index 26f47fc..d094c22 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
@@ -108,6 +108,10 @@
}
if (!createdWindowDecor) {
mSyncQueue.runInSync(t -> {
+ if (!leash.isValid()) {
+ // Task vanished before sync completion
+ return;
+ }
// Reset several properties back to fullscreen (PiP, for example, leaves all these
// properties in a bad state).
t.setWindowCrop(leash, null);
@@ -136,6 +140,10 @@
final Point positionInParent = state.mTaskInfo.positionInParent;
if (!oldPositionInParent.equals(state.mTaskInfo.positionInParent)) {
mSyncQueue.runInSync(t -> {
+ if (!state.mLeash.isValid()) {
+ // Task vanished before sync completion
+ return;
+ }
t.setPosition(state.mLeash, positionInParent.x, positionInParent.y);
});
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
index ac13f96..9796e4c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
@@ -36,6 +36,7 @@
import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.SurfaceControl;
+import android.view.WindowInsets;
import android.window.ITaskOrganizerController;
import android.window.TaskAppearedInfo;
import android.window.WindowContainerToken;
@@ -57,7 +58,6 @@
import java.io.PrintWriter;
import java.util.List;
-import java.util.Objects;
import java.util.Optional;
/**
@@ -130,14 +130,34 @@
new DisplayInsetsController.OnInsetsChangedListener() {
@Override
public void insetsChanged(InsetsState insetsState) {
- // Update bounds only when the insets of navigation bar or task bar is changed.
- if (Objects.equals(insetsState.peekSource(InsetsState.ITYPE_NAVIGATION_BAR),
- mInsetsState.peekSource(InsetsState.ITYPE_NAVIGATION_BAR))
- && Objects.equals(insetsState.peekSource(
- InsetsState.ITYPE_EXTRA_NAVIGATION_BAR),
- mInsetsState.peekSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR))) {
+ final boolean[] navigationBarChanged = {false};
+ InsetsState.traverse(insetsState, mInsetsState, new InsetsState.OnTraverseCallbacks() {
+ @Override
+ public void onIdMatch(InsetsSource source1, InsetsSource source2) {
+ if (source1.getType() == WindowInsets.Type.navigationBars()
+ && !source1.equals(source2)) {
+ navigationBarChanged[0] = true;
+ }
+ }
+
+ @Override
+ public void onIdNotFoundInState1(int index2, InsetsSource source2) {
+ if (source2.getType() == WindowInsets.Type.navigationBars()) {
+ navigationBarChanged[0] = true;
+ }
+ }
+
+ @Override
+ public void onIdNotFoundInState2(int index1, InsetsSource source1) {
+ if (source1.getType() == WindowInsets.Type.navigationBars()) {
+ navigationBarChanged[0] = true;
+ }
+ }
+ });
+ if (!navigationBarChanged[0]) {
return;
}
+ // Update bounds only when the insets of navigation bar or task bar is changed.
mInsetsState.set(insetsState);
updateBounds();
}
@@ -344,16 +364,8 @@
private Rect calculateBounds() {
final Rect bounds = new Rect(0, 0, mDisplayWidth, mDisplayHeight);
- final InsetsSource navBarSource = mInsetsState.peekSource(InsetsState.ITYPE_NAVIGATION_BAR);
- final InsetsSource taskBarSource = mInsetsState.peekSource(
- InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
- if (navBarSource != null && !navBarSource.getFrame().isEmpty()) {
- bounds.inset(navBarSource.calculateInsets(bounds, false /* ignoreVisibility */));
- } else if (taskBarSource != null && !taskBarSource.getFrame().isEmpty()) {
- bounds.inset(taskBarSource.calculateInsets(bounds, false /* ignoreVisibility */));
- } else {
- bounds.setEmpty();
- }
+ bounds.inset(mInsetsState.calculateInsets(
+ bounds, WindowInsets.Type.navigationBars(), false /* ignoreVisibility */));
return bounds;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
index c59c42d..93ffb3d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
@@ -31,7 +31,8 @@
*/
public class ShellProtoLogImpl extends BaseProtoLogImpl {
private static final String TAG = "ProtoLogImpl";
- private static final int BUFFER_CAPACITY = 128 * 1024;
+ private static final int BUFFER_CAPACITY = 1024 * 1024;
+ // TODO: find a proper location to save the protolog message file
private static final String LOG_FILENAME = "/data/misc/wmtrace/shell_log.winscope";
private static final String VIEWER_CONFIG_FILENAME = "/system_ext/etc/wmshell.protolog.json.gz";
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java
index 06a8438..f81fc6f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java
@@ -35,6 +35,7 @@
import android.view.InsetsState;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
+import android.view.WindowInsets;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.wm.shell.common.DisplayInsetsController;
@@ -66,13 +67,12 @@
private static final float START_SCALE = END_SCALE - VERTICAL_START_MARGIN * 2;
private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>();
- private final int mExpandedTaskBarHeight;
private final DisplayInsetsController mDisplayInsetsController;
private final UnfoldBackgroundController mBackgroundController;
private final Context mContext;
private final ShellController mShellController;
- private InsetsSource mTaskbarInsetsSource;
+ private InsetsSource mExpandedTaskbarInsetsSource;
private float mWindowCornerRadiusPx;
public FullscreenUnfoldTaskAnimator(Context context,
@@ -82,8 +82,6 @@
mDisplayInsetsController = displayInsetsController;
mBackgroundController = backgroundController;
mShellController = shellController;
- mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.taskbar_frame_height);
mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context);
}
@@ -101,21 +99,32 @@
@Override
public void insetsChanged(InsetsState insetsState) {
- mTaskbarInsetsSource = insetsState.peekSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+ mExpandedTaskbarInsetsSource = getExpandedTaskbarSource(insetsState);
for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
AnimationContext context = mAnimationContextByTaskId.valueAt(i);
- context.update(mTaskbarInsetsSource, context.mTaskInfo);
+ context.update(mExpandedTaskbarInsetsSource, context.mTaskInfo);
}
}
+ private static InsetsSource getExpandedTaskbarSource(InsetsState state) {
+ for (int i = state.sourceSize() - 1; i >= 0; i--) {
+ final InsetsSource source = state.sourceAt(i);
+ if (source.getType() == WindowInsets.Type.navigationBars()
+ && source.insetsRoundedCornerFrame()) {
+ return source;
+ }
+ }
+ return null;
+ }
+
public boolean hasActiveTasks() {
return mAnimationContextByTaskId.size() > 0;
}
@Override
public void onTaskAppeared(TaskInfo taskInfo, SurfaceControl leash) {
- AnimationContext animationContext = new AnimationContext(leash, mTaskbarInsetsSource,
- taskInfo);
+ AnimationContext animationContext = new AnimationContext(
+ leash, mExpandedTaskbarInsetsSource, taskInfo);
mAnimationContextByTaskId.put(taskInfo.taskId, animationContext);
}
@@ -123,7 +132,7 @@
public void onTaskChanged(TaskInfo taskInfo) {
AnimationContext animationContext = mAnimationContextByTaskId.get(taskInfo.taskId);
if (animationContext != null) {
- animationContext.update(mTaskbarInsetsSource, taskInfo);
+ animationContext.update(mExpandedTaskbarInsetsSource, taskInfo);
}
}
@@ -222,11 +231,7 @@
mStartCropRect.set(mTaskInfo.getConfiguration().windowConfiguration.getBounds());
if (taskBarInsetsSource != null) {
- // Only insets the cropping window with task bar when it's expanded
- if (taskBarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
- mStartCropRect.inset(taskBarInsetsSource
- .calculateVisibleInsets(mStartCropRect));
- }
+ mStartCropRect.inset(taskBarInsetsSource.calculateVisibleInsets(mStartCropRect));
}
mEndCropRect.set(mStartCropRect);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
index f835c7b..2f0c964 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
@@ -37,6 +37,7 @@
import android.view.InsetsState;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
+import android.view.WindowInsets;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.wm.shell.common.DisplayInsetsController;
@@ -76,7 +77,6 @@
private final Executor mExecutor;
private final DisplayInsetsController mDisplayInsetsController;
private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>();
- private final int mExpandedTaskBarHeight;
private final ShellController mShellController;
private final Lazy<Optional<SplitScreenController>> mSplitScreenController;
private final UnfoldBackgroundController mUnfoldBackgroundController;
@@ -86,7 +86,7 @@
private final Rect mRootStageBounds = new Rect();
private float mWindowCornerRadiusPx;
- private InsetsSource mTaskbarInsetsSource;
+ private InsetsSource mExpandedTaskbarInsetsSource;
@SplitPosition
private int mMainStagePosition = SPLIT_POSITION_UNDEFINED;
@@ -103,8 +103,6 @@
mShellController = shellController;
mUnfoldBackgroundController = unfoldBackgroundController;
mSplitScreenController = splitScreenController;
- mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.taskbar_frame_height);
mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context);
}
@@ -143,10 +141,21 @@
@Override
public void insetsChanged(InsetsState insetsState) {
- mTaskbarInsetsSource = insetsState.peekSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+ mExpandedTaskbarInsetsSource = getExpandedTaskbarSource(insetsState);
updateContexts();
}
+ private static InsetsSource getExpandedTaskbarSource(InsetsState state) {
+ for (int i = state.sourceSize() - 1; i >= 0; i--) {
+ final InsetsSource source = state.sourceAt(i);
+ if (source.getType() == WindowInsets.Type.navigationBars()
+ && source.insetsRoundedCornerFrame()) {
+ return source;
+ }
+ }
+ return null;
+ }
+
@Override
public void onTaskStageChanged(int taskId, int stage, boolean visible) {
final AnimationContext context = mAnimationContextByTaskId.get(taskId);
@@ -307,7 +316,8 @@
boolean taskbarExpanded = isTaskbarExpanded();
if (taskbarExpanded) {
// Only insets the cropping window with taskbar when taskbar is expanded
- mStartCropRect.inset(mTaskbarInsetsSource.calculateVisibleInsets(mStartCropRect));
+ mStartCropRect.inset(mExpandedTaskbarInsetsSource.calculateVisibleInsets(
+ mStartCropRect));
}
// Offset to surface coordinates as layout bounds are in screen coordinates
@@ -360,8 +370,7 @@
}
private boolean isTaskbarExpanded() {
- return mTaskbarInsetsSource != null
- && mTaskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight;
+ return mExpandedTaskbarInsetsSource != null;
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 2981f5e..9224b3c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -174,7 +174,7 @@
mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
final TaskPositioner taskPositioner =
- new TaskPositioner(mTaskOrganizer, windowDecoration);
+ new TaskPositioner(mTaskOrganizer, windowDecoration, mDisplayController);
final CaptionTouchEventListener touchEventListener =
new CaptionTouchEventListener(taskInfo, taskPositioner);
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 75bc985..766c4cd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -558,7 +558,8 @@
mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
final TaskPositioner taskPositioner =
- new TaskPositioner(mTaskOrganizer, windowDecoration, mDragStartListener);
+ new TaskPositioner(mTaskOrganizer, windowDecoration, mDisplayController,
+ mDragStartListener);
final DesktopModeTouchEventListener touchEventListener =
new DesktopModeTouchEventListener(taskInfo, taskPositioner);
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
index d3f9227..a3d364a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
@@ -19,9 +19,11 @@
import android.annotation.IntDef;
import android.graphics.PointF;
import android.graphics.Rect;
+import android.util.DisplayMetrics;
import android.window.WindowContainerTransaction;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
class TaskPositioner implements DragPositioningCallback {
@@ -35,6 +37,7 @@
static final int CTRL_TYPE_BOTTOM = 8;
private final ShellTaskOrganizer mTaskOrganizer;
+ private final DisplayController mDisplayController;
private final WindowDecoration mWindowDecoration;
private final Rect mTaskBoundsAtDragStart = new Rect();
@@ -45,14 +48,16 @@
private int mCtrlType;
private DragStartListener mDragStartListener;
- TaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration) {
- this(taskOrganizer, windowDecoration, dragStartListener -> {});
+ TaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
+ DisplayController displayController) {
+ this(taskOrganizer, windowDecoration, displayController, dragStartListener -> {});
}
TaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
- DragStartListener dragStartListener) {
+ DisplayController displayController, DragStartListener dragStartListener) {
mTaskOrganizer = taskOrganizer;
mWindowDecoration = windowDecoration;
+ mDisplayController = displayController;
mDragStartListener = dragStartListener;
}
@@ -128,15 +133,44 @@
mRepositionTaskBounds.offset((int) deltaX, (int) deltaY);
}
+ // If width or height are negative or less than the minimum width or height, revert the
+ // respective bounds to use previous bound dimensions.
+ if (mRepositionTaskBounds.width() < getMinWidth()) {
+ mRepositionTaskBounds.right = oldRight;
+ mRepositionTaskBounds.left = oldLeft;
+ }
+ if (mRepositionTaskBounds.height() < getMinHeight()) {
+ mRepositionTaskBounds.top = oldTop;
+ mRepositionTaskBounds.bottom = oldBottom;
+ }
+ // If there are no changes to the bounds after checking new bounds against minimum width
+ // and height, do not set bounds and return false
if (oldLeft == mRepositionTaskBounds.left && oldTop == mRepositionTaskBounds.top
&& oldRight == mRepositionTaskBounds.right
&& oldBottom == mRepositionTaskBounds.bottom) {
return false;
}
+
wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
return true;
}
+ private float getMinWidth() {
+ return mWindowDecoration.mTaskInfo.minWidth < 0 ? getDefaultMinSize()
+ : mWindowDecoration.mTaskInfo.minWidth;
+ }
+
+ private float getMinHeight() {
+ return mWindowDecoration.mTaskInfo.minHeight < 0 ? getDefaultMinSize()
+ : mWindowDecoration.mTaskInfo.minHeight;
+ }
+
+ private float getDefaultMinSize() {
+ float density = mDisplayController.getDisplayLayout(mWindowDecoration.mTaskInfo.displayId)
+ .densityDpi() * DisplayMetrics.DENSITY_DEFAULT_SCALE;
+ return mWindowDecoration.mTaskInfo.defaultMinSize * density;
+ }
+
interface DragStartListener {
/**
* Inform the implementing class that a drag resize has started
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 8a5b490..5a4a44f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -25,7 +25,6 @@
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
@@ -40,7 +39,6 @@
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
-import android.os.IBinder;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.provider.Settings;
@@ -341,8 +339,7 @@
mController.setTriggerBack(true); // Fake trigger back
// In case the focus has been changed.
- IBinder token = mock(IBinder.class);
- mController.mFocusObserver.focusLost(token);
+ mController.mNavigationObserver.sendResult(null);
mShellExecutor.flushAll();
verify(mAnimatorCallback).onBackCancelled();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
index f185a8a..8f66f4e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
@@ -8,6 +8,8 @@
import android.window.WindowContainerToken
import android.window.WindowContainerTransaction.Change.CHANGE_DRAG_RESIZING
import androidx.test.filters.SmallTest
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_RIGHT
@@ -45,6 +47,11 @@
@Mock
private lateinit var taskBinder: IBinder
+ @Mock
+ private lateinit var mockDisplayController: DisplayController
+ @Mock
+ private lateinit var mockDisplayLayout: DisplayLayout
+
private lateinit var taskPositioner: TaskPositioner
@Before
@@ -54,12 +61,21 @@
taskPositioner = TaskPositioner(
mockShellTaskOrganizer,
mockWindowDecoration,
+ mockDisplayController,
mockDragStartListener
)
+
`when`(taskToken.asBinder()).thenReturn(taskBinder)
+ `when`(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
+ `when`(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
+
mockWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply {
taskId = TASK_ID
token = taskToken
+ minWidth = MIN_WIDTH
+ minHeight = MIN_HEIGHT
+ defaultMinSize = DEFAULT_MIN
+ displayId = DISPLAY_ID
configuration.windowConfiguration.bounds = STARTING_BOUNDS
}
}
@@ -209,8 +225,239 @@
})
}
+ @Test
+ fun testDragResize_resize_setBoundsDoesNotChangeHeightWhenLessThanMin() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ // Resize to width of 95px and height of 5px with min width of 10px
+ val newX = STARTING_BOUNDS.right.toFloat() - 5
+ val newY = STARTING_BOUNDS.top.toFloat() + 95
+ taskPositioner.onDragPositioningMove(
+ newX,
+ newY
+ )
+
+ taskPositioner.onDragPositioningEnd(newX, newY)
+
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS)
+ != 0) && change.configuration.windowConfiguration.bounds.top ==
+ STARTING_BOUNDS.top &&
+ change.configuration.windowConfiguration.bounds.bottom ==
+ STARTING_BOUNDS.bottom
+ }
+ })
+ }
+
+ @Test
+ fun testDragResize_resize_setBoundsDoesNotChangeWidthWhenLessThanMin() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ // Resize to height of 95px and width of 5px with min width of 10px
+ val newX = STARTING_BOUNDS.right.toFloat() - 95
+ val newY = STARTING_BOUNDS.top.toFloat() + 5
+ taskPositioner.onDragPositioningMove(
+ newX,
+ newY
+ )
+
+ taskPositioner.onDragPositioningEnd(newX, newY)
+
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS)
+ != 0) && change.configuration.windowConfiguration.bounds.right ==
+ STARTING_BOUNDS.right &&
+ change.configuration.windowConfiguration.bounds.left ==
+ STARTING_BOUNDS.left
+ }
+ })
+ }
+
+ @Test
+ fun testDragResize_resize_setBoundsDoesNotChangeHeightWhenNegative() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ // Resize to height of -5px and width of 95px
+ val newX = STARTING_BOUNDS.right.toFloat() - 5
+ val newY = STARTING_BOUNDS.top.toFloat() + 105
+ taskPositioner.onDragPositioningMove(
+ newX,
+ newY
+ )
+
+ taskPositioner.onDragPositioningEnd(newX, newY)
+
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS)
+ != 0) && change.configuration.windowConfiguration.bounds.top ==
+ STARTING_BOUNDS.top &&
+ change.configuration.windowConfiguration.bounds.bottom ==
+ STARTING_BOUNDS.bottom
+ }
+ })
+ }
+
+ @Test
+ fun testDragResize_resize_setBoundsDoesNotChangeWidthWhenNegative() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ // Resize to width of -5px and height of 95px
+ val newX = STARTING_BOUNDS.right.toFloat() - 105
+ val newY = STARTING_BOUNDS.top.toFloat() + 5
+ taskPositioner.onDragPositioningMove(
+ newX,
+ newY
+ )
+
+ taskPositioner.onDragPositioningEnd(newX, newY)
+
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS)
+ != 0) && change.configuration.windowConfiguration.bounds.right ==
+ STARTING_BOUNDS.right &&
+ change.configuration.windowConfiguration.bounds.left ==
+ STARTING_BOUNDS.left
+ }
+ })
+ }
+
+ @Test
+ fun testDragResize_resize_setBoundsRunsWhenResizeBoundsValid() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ // Shrink to height 20px and width 20px with both min height/width equal to 10px
+ val newX = STARTING_BOUNDS.right.toFloat() - 80
+ val newY = STARTING_BOUNDS.top.toFloat() + 80
+ taskPositioner.onDragPositioningMove(
+ newX,
+ newY
+ )
+
+ taskPositioner.onDragPositioningEnd(newX, newY)
+
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
+ }
+ })
+ }
+
+ @Test
+ fun testDragResize_resize_setBoundsDoesNotRunWithNegativeHeightAndWidth() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ // Shrink to height 5px and width 5px with both min height/width equal to 10px
+ val newX = STARTING_BOUNDS.right.toFloat() - 95
+ val newY = STARTING_BOUNDS.top.toFloat() + 95
+ taskPositioner.onDragPositioningMove(
+ newX,
+ newY
+ )
+
+ taskPositioner.onDragPositioningEnd(newX, newY)
+
+ verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
+ }
+ })
+ }
+
+ @Test
+ fun testDragResize_resize_useDefaultMinWhenMinWidthInvalid() {
+ mockWindowDecoration.mTaskInfo.minWidth = -1
+
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ // Shrink to width and height of 3px with invalid minWidth = -1 and defaultMinSize = 5px
+ val newX = STARTING_BOUNDS.right.toFloat() - 97
+ val newY = STARTING_BOUNDS.top.toFloat() + 97
+ taskPositioner.onDragPositioningMove(
+ newX,
+ newY
+ )
+
+ taskPositioner.onDragPositioningEnd(newX, newY)
+
+ verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
+ }
+ })
+ }
+
+ @Test
+ fun testDragResize_resize_useMinWidthWhenValid() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ // Shrink to width and height of 7px with valid minWidth = 10px and defaultMinSize = 5px
+ val newX = STARTING_BOUNDS.right.toFloat() - 93
+ val newY = STARTING_BOUNDS.top.toFloat() + 93
+ taskPositioner.onDragPositioningMove(
+ newX,
+ newY
+ )
+
+ taskPositioner.onDragPositioningEnd(newX, newY)
+
+ verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
+ }
+ })
+ }
+
companion object {
private const val TASK_ID = 5
+ private const val MIN_WIDTH = 10
+ private const val MIN_HEIGHT = 10
+ private const val DENSITY_DPI = 20
+ private const val DEFAULT_MIN = 40
+ private const val DISPLAY_ID = 1
private val STARTING_BOUNDS = Rect(0, 0, 100, 100)
}
}
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 0b08681..faa7f7f 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -3049,7 +3049,13 @@
/**
- * Gets the {@link LogSessionId}.
+ * Sets the {@link LogSessionId}.
+ *
+ * <p>The implementation of this method varies by DRM provider; Please refer
+ * to your DRM provider documentation for more details on this method.
+ *
+ * @throws UnsupportedOperationException when the vendor plugin does not
+ * implement this method
*/
public void setLogSessionId(@NonNull LogSessionId logSessionId) {
Objects.requireNonNull(logSessionId);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
index afc2bb1..6eed483 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
@@ -190,9 +190,7 @@
break;
}
case DO_SEND_TIME_SHIFT_MODE: {
- SomeArgs args = (SomeArgs) msg.obj;
- mSessionImpl.sendTimeShiftMode(args.argi1);
- args.recycle();
+ mSessionImpl.sendTimeShiftMode((Integer) msg.obj);
break;
}
case DO_SEND_AVAILABLE_SPEEDS: {
@@ -447,7 +445,7 @@
@Override
public void sendTimeShiftMode(int mode) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageI(DO_SEND_TIME_SHIFT_MODE, mode));
+ mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SEND_TIME_SHIFT_MODE, mode));
}
@Override
diff --git a/packages/CredentialManager/AndroidManifest.xml b/packages/CredentialManager/AndroidManifest.xml
index b36cb5c..dfc8aa0 100644
--- a/packages/CredentialManager/AndroidManifest.xml
+++ b/packages/CredentialManager/AndroidManifest.xml
@@ -41,6 +41,15 @@
android:excludeFromRecents="true"
android:theme="@style/Theme.CredentialSelector">
</activity>
+
+ <receiver
+ android:name=".CredentialProviderReceiver"
+ android:exported="true"
+ android:permission="android.permission.LAUNCH_CREDENTIAL_SELECTOR">
+ <intent-filter>
+ <action android:name="android.credentials.ui.action.CREDMAN_ENABLED_PROVIDERS_UPDATED"/>
+ </intent-filter>
+ </receiver>
</application>
</manifest>
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 0d25bec..30b97bf 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -73,7 +73,7 @@
requestInfo = intent.extras?.getParcelable(
RequestInfo.EXTRA_REQUEST_INFO,
RequestInfo::class.java
- ) ?: testCreatePasskeyRequestInfo()
+ ) ?: testCreatePasswordRequestInfo()
providerEnabledList = when (requestInfo.type) {
RequestInfo.TYPE_CREATE ->
@@ -402,30 +402,26 @@
)
val credentialData = request.credentialData
return RequestInfo.newCreateRequestInfo(
- Binder(),
- CreateCredentialRequest(
- "androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL",
- credentialData,
- /*candidateQueryData=*/ Bundle(),
- /*isSystemProviderRequired=*/ false,
- /*alwaysSendAppInfoToProvider=*/ true
- ),
- "com.google.android.youtube"
+ Binder(),
+ CreateCredentialRequest.Builder(credentialData, Bundle())
+ .setType("androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL")
+ .setIsSystemProviderRequired(false)
+ .setAlwaysSendAppInfoToProvider(true)
+ .build(),
+ "com.google.android.youtube"
)
}
private fun testCreatePasswordRequestInfo(): RequestInfo {
val request = CreatePasswordRequest("beckett-bakert@gmail.com", "password123")
return RequestInfo.newCreateRequestInfo(
- Binder(),
- CreateCredentialRequest(
- TYPE_PASSWORD_CREDENTIAL,
- request.credentialData,
- request.candidateQueryData,
- /*isSystemProviderRequired=*/ false,
- /*alwaysSendAppInfoToProvider=*/ true
- ),
- "com.google.android.youtube"
+ Binder(),
+ CreateCredentialRequest.Builder(request.credentialData, request.candidateQueryData)
+ .setType(TYPE_PASSWORD_CREDENTIAL)
+ .setIsSystemProviderRequired(false)
+ .setAlwaysSendAppInfoToProvider(true)
+ .build(),
+ "com.google.android.youtube"
)
}
@@ -437,15 +433,13 @@
displayInfo.toBundle()
)
return RequestInfo.newCreateRequestInfo(
- Binder(),
- CreateCredentialRequest(
- "other-sign-ins",
- data,
- /*candidateQueryData=*/ Bundle(),
- /*isSystemProviderRequired=*/ false,
- /*alwaysSendAppInfoToProvider=*/ true
- ),
- "com.google.android.youtube"
+ Binder(),
+ CreateCredentialRequest.Builder(data, Bundle())
+ .setType("other-sign-ins")
+ .setIsSystemProviderRequired(false)
+ .setAlwaysSendAppInfoToProvider(true)
+ .build(),
+ "com.google.android.youtube"
)
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialProviderReceiver.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialProviderReceiver.kt
new file mode 100644
index 0000000..ee8cffe
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialProviderReceiver.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.credentialmanager
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.util.Log
+import com.android.credentialmanager.common.Constants
+
+
+class CredentialProviderReceiver : BroadcastReceiver() {
+
+ override fun onReceive(context: Context?, intent: Intent?) {
+ Log.d(Constants.LOG_TAG, "Received intent in CredentialProviderReceiver")
+
+ val sharedPreferences = context?.getSharedPreferences(context?.packageName,
+ Context.MODE_PRIVATE)
+ sharedPreferences?.edit()?.remove(UserConfigRepo.DEFAULT_PROVIDER)?.commit()
+ }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/SpaPrivileged/AndroidManifest.xml b/packages/SettingsLib/SpaPrivileged/AndroidManifest.xml
index d1dceb3..5396de0 100644
--- a/packages/SettingsLib/SpaPrivileged/AndroidManifest.xml
+++ b/packages/SettingsLib/SpaPrivileged/AndroidManifest.xml
@@ -17,6 +17,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.settingslib.spaprivileged">
-<uses-permission android:name="android.permission.MANAGE_USERS" />
+ <uses-permission android:name="android.permission.MANAGE_USERS" />
+ <uses-permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS" />
</manifest>
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
index 171903f..c609004 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
@@ -16,11 +16,14 @@
package com.android.settingslib.spaprivileged.model.app
+import android.app.AppOpsManager;
import android.app.AppOpsManager.MODE_ALLOWED
import android.app.AppOpsManager.MODE_ERRORED
import android.app.AppOpsManager.Mode
import android.content.Context
import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.os.UserHandle
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.map
@@ -44,17 +47,25 @@
private val setModeByUid: Boolean = false,
) : IAppOpsController {
private val appOpsManager = context.appOpsManager
+ private val packageManager = context.packageManager
override val mode: LiveData<Int>
get() = _mode
override fun setAllowed(allowed: Boolean) {
val mode = if (allowed) MODE_ALLOWED else modeForNotAllowed
+
if (setModeByUid) {
appOpsManager.setUidMode(op, app.uid, mode)
} else {
appOpsManager.setMode(op, app.uid, app.packageName, mode)
}
+
+ val permission = AppOpsManager.opToPermission(op)
+ packageManager.updatePermissionFlags(permission, app.packageName,
+ PackageManager.FLAG_PERMISSION_USER_SET, PackageManager.FLAG_PERMISSION_USER_SET,
+ UserHandle.getUserHandleForUid(app.uid))
+
_mode.postValue(mode)
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt
index fd2ceb7..23270c1 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt
@@ -22,6 +22,7 @@
import android.app.AppOpsManager.MODE_IGNORED
import android.content.Context
import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spaprivileged.framework.common.appOpsManager
@@ -31,6 +32,10 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.doNothing
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.Spy
@@ -45,9 +50,13 @@
@Mock private lateinit var appOpsManager: AppOpsManager
+ @Mock private lateinit var packageManager: PackageManager
+
@Before
fun setUp() {
whenever(context.appOpsManager).thenReturn(appOpsManager)
+ doNothing().`when`(packageManager)
+ .updatePermissionFlags(anyString(), anyString(), anyInt(), anyInt(), any())
}
@Test
diff --git a/packages/SettingsLib/res/drawable-mcc310-mnc012/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc310-mnc012/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc310-mnc012/ic_5g_plus_mobiledata.xml
@@ -0,0 +1,35 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="22"
+ android:viewportHeight="17"
+ android:width="22dp"
+ android:height="17dp">
+ <group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+ </group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+ </group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+ </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc310-mnc590/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc310-mnc590/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc310-mnc590/ic_5g_plus_mobiledata.xml
@@ -0,0 +1,35 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="22"
+ android:viewportHeight="17"
+ android:width="22dp"
+ android:height="17dp">
+ <group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+ </group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+ </group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+ </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc310-mnc591/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc310-mnc591/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc310-mnc591/ic_5g_plus_mobiledata.xml
@@ -0,0 +1,35 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="22"
+ android:viewportHeight="17"
+ android:width="22dp"
+ android:height="17dp">
+ <group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+ </group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+ </group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+ </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc310-mnc592/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc310-mnc592/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc310-mnc592/ic_5g_plus_mobiledata.xml
@@ -0,0 +1,35 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="22"
+ android:viewportHeight="17"
+ android:width="22dp"
+ android:height="17dp">
+ <group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+ </group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+ </group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+ </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc310-mnc593/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc310-mnc593/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc310-mnc593/ic_5g_plus_mobiledata.xml
@@ -0,0 +1,35 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="22"
+ android:viewportHeight="17"
+ android:width="22dp"
+ android:height="17dp">
+ <group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+ </group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+ </group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+ </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc310-mnc594/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc310-mnc594/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc310-mnc594/ic_5g_plus_mobiledata.xml
@@ -0,0 +1,35 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="22"
+ android:viewportHeight="17"
+ android:width="22dp"
+ android:height="17dp">
+ <group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+ </group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+ </group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+ </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc310-mnc595/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc310-mnc595/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc310-mnc595/ic_5g_plus_mobiledata.xml
@@ -0,0 +1,35 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="22"
+ android:viewportHeight="17"
+ android:width="22dp"
+ android:height="17dp">
+ <group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+ </group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+ </group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+ </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc310-mnc596/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc310-mnc596/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc310-mnc596/ic_5g_plus_mobiledata.xml
@@ -0,0 +1,35 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="22"
+ android:viewportHeight="17"
+ android:width="22dp"
+ android:height="17dp">
+ <group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+ </group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+ </group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+ </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc310-mnc597/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc310-mnc597/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc310-mnc597/ic_5g_plus_mobiledata.xml
@@ -0,0 +1,35 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="22"
+ android:viewportHeight="17"
+ android:width="22dp"
+ android:height="17dp">
+ <group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+ </group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+ </group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+ </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc310-mnc598/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc310-mnc598/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc310-mnc598/ic_5g_plus_mobiledata.xml
@@ -0,0 +1,35 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="22"
+ android:viewportHeight="17"
+ android:width="22dp"
+ android:height="17dp">
+ <group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+ </group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+ </group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+ </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc310-mnc599/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc310-mnc599/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc310-mnc599/ic_5g_plus_mobiledata.xml
@@ -0,0 +1,35 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="22"
+ android:viewportHeight="17"
+ android:width="22dp"
+ android:height="17dp">
+ <group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+ </group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+ </group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+ </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc310-mnc890/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc310-mnc890/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc310-mnc890/ic_5g_plus_mobiledata.xml
@@ -0,0 +1,35 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="22"
+ android:viewportHeight="17"
+ android:width="22dp"
+ android:height="17dp">
+ <group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+ </group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+ </group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+ </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc270/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc270/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc270/ic_5g_plus_mobiledata.xml
@@ -0,0 +1,35 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="22"
+ android:viewportHeight="17"
+ android:width="22dp"
+ android:height="17dp">
+ <group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+ </group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+ </group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+ </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc280/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc280/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc280/ic_5g_plus_mobiledata.xml
@@ -0,0 +1,35 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="22"
+ android:viewportHeight="17"
+ android:width="22dp"
+ android:height="17dp">
+ <group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+ </group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+ </group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+ </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc281/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc281/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc281/ic_5g_plus_mobiledata.xml
@@ -0,0 +1,35 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="22"
+ android:viewportHeight="17"
+ android:width="22dp"
+ android:height="17dp">
+ <group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+ </group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+ </group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+ </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc282/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc282/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc282/ic_5g_plus_mobiledata.xml
@@ -0,0 +1,35 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="22"
+ android:viewportHeight="17"
+ android:width="22dp"
+ android:height="17dp">
+ <group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+ </group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+ </group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+ </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc283/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc283/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc283/ic_5g_plus_mobiledata.xml
@@ -0,0 +1,35 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="22"
+ android:viewportHeight="17"
+ android:width="22dp"
+ android:height="17dp">
+ <group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+ </group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+ </group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+ </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc284/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc284/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc284/ic_5g_plus_mobiledata.xml
@@ -0,0 +1,35 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="22"
+ android:viewportHeight="17"
+ android:width="22dp"
+ android:height="17dp">
+ <group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+ </group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+ </group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+ </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc285/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc285/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc285/ic_5g_plus_mobiledata.xml
@@ -0,0 +1,35 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="22"
+ android:viewportHeight="17"
+ android:width="22dp"
+ android:height="17dp">
+ <group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+ </group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+ </group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+ </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc286/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc286/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc286/ic_5g_plus_mobiledata.xml
@@ -0,0 +1,35 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="22"
+ android:viewportHeight="17"
+ android:width="22dp"
+ android:height="17dp">
+ <group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+ </group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+ </group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+ </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc287/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc287/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc287/ic_5g_plus_mobiledata.xml
@@ -0,0 +1,35 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="22"
+ android:viewportHeight="17"
+ android:width="22dp"
+ android:height="17dp">
+ <group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+ </group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+ </group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+ </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc288/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc288/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc288/ic_5g_plus_mobiledata.xml
@@ -0,0 +1,35 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="22"
+ android:viewportHeight="17"
+ android:width="22dp"
+ android:height="17dp">
+ <group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+ </group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+ </group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+ </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc289/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc289/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc289/ic_5g_plus_mobiledata.xml
@@ -0,0 +1,35 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="22"
+ android:viewportHeight="17"
+ android:width="22dp"
+ android:height="17dp">
+ <group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+ </group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+ </group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+ </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc480/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc480/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc480/ic_5g_plus_mobiledata.xml
@@ -0,0 +1,35 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="22"
+ android:viewportHeight="17"
+ android:width="22dp"
+ android:height="17dp">
+ <group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+ </group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+ </group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+ </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc481/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc481/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc481/ic_5g_plus_mobiledata.xml
@@ -0,0 +1,35 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="22"
+ android:viewportHeight="17"
+ android:width="22dp"
+ android:height="17dp">
+ <group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+ </group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+ </group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+ </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc482/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc482/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc482/ic_5g_plus_mobiledata.xml
@@ -0,0 +1,35 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="22"
+ android:viewportHeight="17"
+ android:width="22dp"
+ android:height="17dp">
+ <group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+ </group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+ </group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+ </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc483/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc483/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc483/ic_5g_plus_mobiledata.xml
@@ -0,0 +1,35 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="22"
+ android:viewportHeight="17"
+ android:width="22dp"
+ android:height="17dp">
+ <group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+ </group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+ </group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+ </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc484/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc484/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc484/ic_5g_plus_mobiledata.xml
@@ -0,0 +1,35 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="22"
+ android:viewportHeight="17"
+ android:width="22dp"
+ android:height="17dp">
+ <group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+ </group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+ </group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+ </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc485/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc485/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc485/ic_5g_plus_mobiledata.xml
@@ -0,0 +1,35 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="22"
+ android:viewportHeight="17"
+ android:width="22dp"
+ android:height="17dp">
+ <group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+ </group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+ </group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+ </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc486/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc486/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc486/ic_5g_plus_mobiledata.xml
@@ -0,0 +1,35 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="22"
+ android:viewportHeight="17"
+ android:width="22dp"
+ android:height="17dp">
+ <group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+ </group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+ </group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+ </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc487/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc487/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc487/ic_5g_plus_mobiledata.xml
@@ -0,0 +1,35 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="22"
+ android:viewportHeight="17"
+ android:width="22dp"
+ android:height="17dp">
+ <group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+ </group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+ </group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+ </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc488/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc488/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc488/ic_5g_plus_mobiledata.xml
@@ -0,0 +1,35 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="22"
+ android:viewportHeight="17"
+ android:width="22dp"
+ android:height="17dp">
+ <group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+ </group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+ </group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+ </group>
+</vector>
diff --git a/packages/SettingsLib/res/drawable-mcc311-mnc489/ic_5g_plus_mobiledata.xml b/packages/SettingsLib/res/drawable-mcc311-mnc489/ic_5g_plus_mobiledata.xml
new file mode 100644
index 0000000..c1103fe
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mcc311-mnc489/ic_5g_plus_mobiledata.xml
@@ -0,0 +1,35 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="22"
+ android:viewportHeight="17"
+ android:width="22dp"
+ android:height="17dp">
+ <group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.98,3.54v2.81c0,0.47 -0.15,0.84 -0.44,1.11s-0.69,0.41 -1.2,0.41c-0.5,0 -0.89,-0.13 -1.19,-0.4s-0.44,-0.63 -0.45,-1.09V3.54h0.88v2.82c0,0.28 0.07,0.48 0.2,0.61c0.13,0.13 0.32,0.19 0.56,0.19c0.49,0 0.75,-0.26 0.75,-0.78V3.54H19.98z"/>
+ <path android:fillColor="#FF000000"
+ android:pathData="M19.42,12.25l0.57,-3.04h0.88l-0.95,4.27h-0.88l-0.69,-2.85l-0.69,2.85h-0.88l-0.95,-4.27h0.88l0.58,3.03l0.7,-3.03h0.74L19.42,12.25z"/>
+ </group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M0.94,8.49l0.43,-4.96H5.7v1.17H2.39L2.15,7.41c0.41,-0.29 0.85,-0.43 1.33,-0.43c0.77,0 1.38,0.3 1.83,0.9c0.44,0.6 0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43c-0.48,0.59 -1.14,0.88 -1.98,0.88c-0.75,0 -1.36,-0.24 -1.83,-0.73c-0.47,-0.49 -0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59C4.05,8.32 3.67,8.11 3.19,8.11c-0.4,0 -0.72,0.1 -0.96,0.31L1.9,8.75L0.94,8.49z"/>
+ </group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M13.86,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07c-0.61,-0.71 -0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13c0.56,-0.7 1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.22,0.79c0.54,0.53 0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45c-0.29,-0.35 -0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7C8.85,5.63 8.68,6.37 8.66,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+ </group>
+</vector>
diff --git a/packages/SettingsLib/res/values-mcc310-mnc012/strings.xml b/packages/SettingsLib/res/values-mcc310-mnc012/strings.xml
new file mode 100644
index 0000000..aa504ef
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc310-mnc012/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+ <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
+
diff --git a/packages/SettingsLib/res/values-mcc310-mnc590/strings.xml b/packages/SettingsLib/res/values-mcc310-mnc590/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc310-mnc590/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+ <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc310-mnc591/strings.xml b/packages/SettingsLib/res/values-mcc310-mnc591/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc310-mnc591/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+ <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc310-mnc592/strings.xml b/packages/SettingsLib/res/values-mcc310-mnc592/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc310-mnc592/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+ <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc310-mnc593/strings.xml b/packages/SettingsLib/res/values-mcc310-mnc593/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc310-mnc593/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+ <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc310-mnc594/strings.xml b/packages/SettingsLib/res/values-mcc310-mnc594/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc310-mnc594/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+ <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc310-mnc595/strings.xml b/packages/SettingsLib/res/values-mcc310-mnc595/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc310-mnc595/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+ <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc310-mnc596/strings.xml b/packages/SettingsLib/res/values-mcc310-mnc596/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc310-mnc596/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+ <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc310-mnc597/strings.xml b/packages/SettingsLib/res/values-mcc310-mnc597/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc310-mnc597/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+ <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc310-mnc598/strings.xml b/packages/SettingsLib/res/values-mcc310-mnc598/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc310-mnc598/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+ <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc310-mnc599/strings.xml b/packages/SettingsLib/res/values-mcc310-mnc599/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc310-mnc599/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+ <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc310-mnc890/strings.xml b/packages/SettingsLib/res/values-mcc310-mnc890/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc310-mnc890/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+ <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc311-mnc270/strings.xml b/packages/SettingsLib/res/values-mcc311-mnc270/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc311-mnc270/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+ <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc311-mnc280/strings.xml b/packages/SettingsLib/res/values-mcc311-mnc280/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc311-mnc280/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+ <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc311-mnc281/strings.xml b/packages/SettingsLib/res/values-mcc311-mnc281/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc311-mnc281/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+ <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc311-mnc282/strings.xml b/packages/SettingsLib/res/values-mcc311-mnc282/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc311-mnc282/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+ <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc311-mnc283/strings.xml b/packages/SettingsLib/res/values-mcc311-mnc283/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc311-mnc283/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+ <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc311-mnc284/strings.xml b/packages/SettingsLib/res/values-mcc311-mnc284/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc311-mnc284/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+ <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc311-mnc285/strings.xml b/packages/SettingsLib/res/values-mcc311-mnc285/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc311-mnc285/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+ <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc311-mnc286/strings.xml b/packages/SettingsLib/res/values-mcc311-mnc286/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc311-mnc286/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+ <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc311-mnc287/strings.xml b/packages/SettingsLib/res/values-mcc311-mnc287/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc311-mnc287/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+ <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc311-mnc288/strings.xml b/packages/SettingsLib/res/values-mcc311-mnc288/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc311-mnc288/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+ <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc311-mnc289/strings.xml b/packages/SettingsLib/res/values-mcc311-mnc289/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc311-mnc289/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+ <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc311-mnc481/strings.xml b/packages/SettingsLib/res/values-mcc311-mnc481/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc311-mnc481/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+ <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc311-mnc482/strings.xml b/packages/SettingsLib/res/values-mcc311-mnc482/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc311-mnc482/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+ <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc311-mnc483/strings.xml b/packages/SettingsLib/res/values-mcc311-mnc483/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc311-mnc483/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+ <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc311-mnc484/strings.xml b/packages/SettingsLib/res/values-mcc311-mnc484/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc311-mnc484/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+ <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc311-mnc485/strings.xml b/packages/SettingsLib/res/values-mcc311-mnc485/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc311-mnc485/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+ <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc311-mnc486/strings.xml b/packages/SettingsLib/res/values-mcc311-mnc486/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc311-mnc486/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+ <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc311-mnc487/strings.xml b/packages/SettingsLib/res/values-mcc311-mnc487/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc311-mnc487/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+ <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc311-mnc488/strings.xml b/packages/SettingsLib/res/values-mcc311-mnc488/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc311-mnc488/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+ <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-mcc311-mnc489/strings.xml b/packages/SettingsLib/res/values-mcc311-mnc489/strings.xml
new file mode 100644
index 0000000..4cd04d0
--- /dev/null
+++ b/packages/SettingsLib/res/values-mcc311-mnc489/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Content description of the data connection type 5G UW. [CHAR LIMIT=NONE] -->
+ <string name="data_connection_5g_plus" translatable="false">5G UW</string>
+</resources>
diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml
index 337c251..5022960 100644
--- a/packages/SettingsLib/res/values/arrays.xml
+++ b/packages/SettingsLib/res/values/arrays.xml
@@ -118,9 +118,9 @@
<!-- Titles for Bluetooth HCI Snoop Filtered Logging -->
<string-array name="bt_hci_snoop_log_filters_entries">
- <item>Headers Filtered</item>
- <item>A2DP Media Packets Filtered</item>
- <item>RFCOMM Channel Filtered</item>
+ <item>Leave only ACL headers</item>
+ <item>Filter A2DP media packets</item>
+ <item>Filter RFCOMM channel</item>
</string-array>
<!-- Values for Bluetooth HCI Snoop Filtered Logging -->
@@ -132,10 +132,10 @@
<!-- Titles for Bluetooth HCI Snoop Filtered Logging -->
<string-array name="bt_hci_snoop_log_profile_filter_entries">
- <item>Disabled</item>
- <item>Magic</item>
- <item>Header</item>
- <item>Full Filter</item>
+ <item>Disable</item>
+ <item>Fill with string of characters</item>
+ <item>Leave only header</item>
+ <item>Fully remove</item>
</string-array>
<!-- Values for Bluetooth HCI Snoop Filtered Logging -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java b/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
index 85e8aad..59735f41 100644
--- a/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
@@ -183,7 +183,7 @@
final String currentShortcutServiceId = Settings.Secure.getStringForUser(
context.getContentResolver(), Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
userId);
- if (!TextUtils.isEmpty(currentShortcutServiceId)) {
+ if (currentShortcutServiceId != null) {
return currentShortcutServiceId;
}
return context.getString(R.string.config_defaultAccessibilityService);
diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp
index 1ac20471..346462d 100644
--- a/packages/SettingsProvider/Android.bp
+++ b/packages/SettingsProvider/Android.bp
@@ -48,6 +48,7 @@
"test/**/*.java",
"src/android/provider/settings/backup/*",
"src/android/provider/settings/validators/*",
+ "src/com/android/providers/settings/GenerationRegistry.java",
"src/com/android/providers/settings/SettingsBackupAgent.java",
"src/com/android/providers/settings/SettingsState.java",
"src/com/android/providers/settings/SettingsHelper.java",
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
index 5617331..7f3b0ff 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
@@ -17,19 +17,19 @@
package com.android.providers.settings;
import android.os.Bundle;
-import android.os.UserManager;
import android.provider.Settings;
+import android.util.ArrayMap;
import android.util.MemoryIntArray;
import android.util.Slog;
-import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import java.io.IOException;
/**
* This class tracks changes for config/global/secure/system tables
- * on a per user basis and updates a shared memory region which
+ * on a per user basis and updates shared memory regions which
* client processes can read to determine if their local caches are
* stale.
*/
@@ -40,138 +40,187 @@
private final Object mLock;
+ // Key -> backingStore mapping
@GuardedBy("mLock")
- private final SparseIntArray mKeyToIndexMap = new SparseIntArray();
+ private final ArrayMap<Integer, MemoryIntArray> mKeyToBackingStoreMap = new ArrayMap();
+
+ // Key -> (String->Index map) mapping
+ @GuardedBy("mLock")
+ private final ArrayMap<Integer, ArrayMap<String, Integer>> mKeyToIndexMapMap = new ArrayMap<>();
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ // Maximum number of backing stores allowed
+ static final int NUM_MAX_BACKING_STORE = 8;
@GuardedBy("mLock")
- private MemoryIntArray mBackingStore;
+ private int mNumBackingStore = 0;
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ // Maximum size of an individual backing store
+ static final int MAX_BACKING_STORE_SIZE = MemoryIntArray.getMaxSize();
public GenerationRegistry(Object lock) {
mLock = lock;
}
- public void incrementGeneration(int key) {
+ /**
+ * Increment the generation number if the setting is already cached in the backing stores.
+ * Otherwise, do nothing.
+ */
+ public void incrementGeneration(int key, String name) {
+ final boolean isConfig =
+ (SettingsState.getTypeFromKey(key) == SettingsState.SETTINGS_TYPE_CONFIG);
+ // Only store the prefix if the mutated setting is a config
+ final String indexMapKey = isConfig ? (name.split("/")[0] + "/") : name;
synchronized (mLock) {
- MemoryIntArray backingStore = getBackingStoreLocked();
- if (backingStore != null) {
- try {
- final int index = getKeyIndexLocked(key, mKeyToIndexMap, backingStore);
- if (index >= 0) {
- final int generation = backingStore.get(index) + 1;
- backingStore.set(index, generation);
- }
- } catch (IOException e) {
- Slog.e(LOG_TAG, "Error updating generation id", e);
- destroyBackingStore();
+ final MemoryIntArray backingStore = getBackingStoreLocked(key,
+ /* createIfNotExist= */ false);
+ if (backingStore == null) {
+ return;
+ }
+ try {
+ final int index = getKeyIndexLocked(key, indexMapKey, mKeyToIndexMapMap,
+ backingStore, /* createIfNotExist= */ false);
+ if (index < 0) {
+ return;
}
+ final int generation = backingStore.get(index) + 1;
+ backingStore.set(index, generation);
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Incremented generation for setting:" + indexMapKey
+ + " key:" + SettingsState.keyToString(key)
+ + " at index:" + index);
+ }
+ } catch (IOException e) {
+ Slog.e(LOG_TAG, "Error updating generation id", e);
+ destroyBackingStoreLocked(key);
}
}
}
- public void addGenerationData(Bundle bundle, int key) {
+ /**
+ * Return the backing store's reference, the index and the current generation number
+ * of a cached setting. If it was not in the backing store, first create the entry in it before
+ * returning the result.
+ */
+ public void addGenerationData(Bundle bundle, int key, String indexMapKey) {
synchronized (mLock) {
- MemoryIntArray backingStore = getBackingStoreLocked();
+ final MemoryIntArray backingStore = getBackingStoreLocked(key,
+ /* createIfNotExist= */ true);
+ if (backingStore == null) {
+ // Error accessing existing backing store or no new backing store is available
+ return;
+ }
try {
- if (backingStore != null) {
- final int index = getKeyIndexLocked(key, mKeyToIndexMap, backingStore);
- if (index >= 0) {
- bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY,
- backingStore);
- bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, index);
- bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY,
- backingStore.get(index));
- if (DEBUG) {
- Slog.i(LOG_TAG, "Exported index:" + index + " for key:"
- + SettingsProvider.keyToString(key));
- }
- }
+ final int index = getKeyIndexLocked(key, indexMapKey, mKeyToIndexMapMap,
+ backingStore, /* createIfNotExist= */ true);
+ if (index < 0) {
+ // Should not happen unless having error accessing the backing store
+ return;
+ }
+ bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY,
+ backingStore);
+ bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, index);
+ bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY,
+ backingStore.get(index));
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Exported index:" + index
+ + " for setting:" + indexMapKey
+ + " key:" + SettingsState.keyToString(key));
}
} catch (IOException e) {
Slog.e(LOG_TAG, "Error adding generation data", e);
- destroyBackingStore();
+ destroyBackingStoreLocked(key);
}
}
}
public void onUserRemoved(int userId) {
+ final int secureKey = SettingsState.makeKey(
+ SettingsState.SETTINGS_TYPE_SECURE, userId);
+ final int systemKey = SettingsState.makeKey(
+ SettingsState.SETTINGS_TYPE_SYSTEM, userId);
synchronized (mLock) {
- MemoryIntArray backingStore = getBackingStoreLocked();
- if (backingStore != null && mKeyToIndexMap.size() > 0) {
- try {
- final int secureKey = SettingsProvider.makeKey(
- SettingsProvider.SETTINGS_TYPE_SECURE, userId);
- resetSlotForKeyLocked(secureKey, mKeyToIndexMap, backingStore);
-
- final int systemKey = SettingsProvider.makeKey(
- SettingsProvider.SETTINGS_TYPE_SYSTEM, userId);
- resetSlotForKeyLocked(systemKey, mKeyToIndexMap, backingStore);
- } catch (IOException e) {
- Slog.e(LOG_TAG, "Error cleaning up for user", e);
- destroyBackingStore();
- }
+ if (mKeyToIndexMapMap.containsKey(secureKey)) {
+ destroyBackingStoreLocked(secureKey);
+ mKeyToIndexMapMap.remove(secureKey);
+ mNumBackingStore = mNumBackingStore - 1;
+ }
+ if (mKeyToIndexMapMap.containsKey(systemKey)) {
+ destroyBackingStoreLocked(systemKey);
+ mKeyToIndexMapMap.remove(systemKey);
+ mNumBackingStore = mNumBackingStore - 1;
}
}
}
@GuardedBy("mLock")
- private MemoryIntArray getBackingStoreLocked() {
- if (mBackingStore == null) {
- // One for the config table, one for the global table, two for system
- // and secure tables for a managed profile (managed profile is not
- // included in the max user count), ten for partially deleted users if
- // users are quickly removed, and twice max user count for system and
- // secure.
- final int size = 1 + 1 + 2 + 10 + 2 * UserManager.getMaxSupportedUsers();
+ private MemoryIntArray getBackingStoreLocked(int key, boolean createIfNotExist) {
+ MemoryIntArray backingStore = mKeyToBackingStoreMap.get(key);
+ if (!createIfNotExist) {
+ return backingStore;
+ }
+ if (backingStore == null) {
try {
- mBackingStore = new MemoryIntArray(size);
+ if (mNumBackingStore >= NUM_MAX_BACKING_STORE) {
+ Slog.e(LOG_TAG, "Error creating backing store - at capacity");
+ return null;
+ }
+ backingStore = new MemoryIntArray(MAX_BACKING_STORE_SIZE);
+ mKeyToBackingStoreMap.put(key, backingStore);
+ mNumBackingStore += 1;
if (DEBUG) {
- Slog.e(LOG_TAG, "Created backing store " + mBackingStore);
+ Slog.e(LOG_TAG, "Created backing store for "
+ + SettingsState.keyToString(key) + " on user: "
+ + SettingsState.getUserIdFromKey(key));
}
} catch (IOException e) {
Slog.e(LOG_TAG, "Error creating generation tracker", e);
}
}
- return mBackingStore;
+ return backingStore;
}
- private void destroyBackingStore() {
- if (mBackingStore != null) {
+ @GuardedBy("mLock")
+ private void destroyBackingStoreLocked(int key) {
+ MemoryIntArray backingStore = mKeyToBackingStoreMap.get(key);
+ if (backingStore != null) {
try {
- mBackingStore.close();
+ backingStore.close();
if (DEBUG) {
- Slog.e(LOG_TAG, "Destroyed backing store " + mBackingStore);
+ Slog.e(LOG_TAG, "Destroyed backing store " + backingStore);
}
} catch (IOException e) {
Slog.e(LOG_TAG, "Cannot close generation memory array", e);
}
- mBackingStore = null;
+ mKeyToBackingStoreMap.remove(key);
}
}
- private static void resetSlotForKeyLocked(int key, SparseIntArray keyToIndexMap,
- MemoryIntArray backingStore) throws IOException {
- final int index = keyToIndexMap.get(key, -1);
- if (index >= 0) {
- keyToIndexMap.delete(key);
- backingStore.set(index, 0);
- if (DEBUG) {
- Slog.i(LOG_TAG, "Freed index:" + index + " for key:"
- + SettingsProvider.keyToString(key));
+ private static int getKeyIndexLocked(int key, String indexMapKey,
+ ArrayMap<Integer, ArrayMap<String, Integer>> keyToIndexMapMap,
+ MemoryIntArray backingStore, boolean createIfNotExist) throws IOException {
+ ArrayMap<String, Integer> nameToIndexMap = keyToIndexMapMap.get(key);
+ if (nameToIndexMap == null) {
+ if (!createIfNotExist) {
+ return -1;
}
+ nameToIndexMap = new ArrayMap<>();
+ keyToIndexMapMap.put(key, nameToIndexMap);
}
- }
-
- private static int getKeyIndexLocked(int key, SparseIntArray keyToIndexMap,
- MemoryIntArray backingStore) throws IOException {
- int index = keyToIndexMap.get(key, -1);
+ int index = nameToIndexMap.getOrDefault(indexMapKey, -1);
if (index < 0) {
+ if (!createIfNotExist) {
+ return -1;
+ }
index = findNextEmptyIndex(backingStore);
if (index >= 0) {
backingStore.set(index, 1);
- keyToIndexMap.append(key, index);
+ nameToIndexMap.put(indexMapKey, index);
if (DEBUG) {
- Slog.i(LOG_TAG, "Allocated index:" + index + " for key:"
- + SettingsProvider.keyToString(key));
+ Slog.i(LOG_TAG, "Allocated index:" + index + " for setting:" + indexMapKey
+ + " of type:" + SettingsState.keyToString(key)
+ + " on user:" + SettingsState.getUserIdFromKey(key));
}
} else {
Slog.e(LOG_TAG, "Could not allocate generation index");
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 683c08e..d7c7130 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -35,6 +35,7 @@
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
import static com.android.internal.accessibility.util.AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM;
import static com.android.providers.settings.SettingsState.FALLBACK_FILE_SUFFIX;
+import static com.android.providers.settings.SettingsState.makeKey;
import android.Manifest;
import android.annotation.NonNull;
@@ -375,10 +376,6 @@
@GuardedBy("mLock")
private boolean mSyncConfigDisabledUntilReboot;
- public static int makeKey(int type, int userId) {
- return SettingsState.makeKey(type, userId);
- }
-
public static int getTypeFromKey(int key) {
return SettingsState.getTypeFromKey(key);
}
@@ -387,9 +384,6 @@
return SettingsState.getUserIdFromKey(key);
}
- public static String keyToString(int key) {
- return SettingsState.keyToString(key);
- }
@ChangeId
@EnabledSince(targetSdkVersion=android.os.Build.VERSION_CODES.S)
private static final long ENFORCE_READ_PERMISSION_FOR_MULTI_SIM_DATA_CALL = 172670679L;
@@ -553,7 +547,7 @@
case Settings.CALL_METHOD_LIST_CONFIG: {
String prefix = getSettingPrefix(args);
- Bundle result = packageValuesForCallResult(getAllConfigFlags(prefix),
+ Bundle result = packageValuesForCallResult(prefix, getAllConfigFlags(prefix),
isTrackingGeneration(args));
reportDeviceConfigAccess(prefix);
return result;
@@ -1319,6 +1313,7 @@
return false;
}
+ @NonNull
private HashMap<String, String> getAllConfigFlags(@Nullable String prefix) {
if (DEBUG) {
Slog.v(LOG_TAG, "getAllConfigFlags() for " + prefix);
@@ -2316,7 +2311,8 @@
"get/set setting for user", null);
}
- private Bundle packageValueForCallResult(Setting setting, boolean trackingGeneration) {
+ private Bundle packageValueForCallResult(@Nullable Setting setting,
+ boolean trackingGeneration) {
if (!trackingGeneration) {
if (setting == null || setting.isNull()) {
return NULL_SETTING_BUNDLE;
@@ -2327,19 +2323,26 @@
result.putString(Settings.NameValueTable.VALUE,
!setting.isNull() ? setting.getValue() : null);
- mSettingsRegistry.mGenerationRegistry.addGenerationData(result, setting.getKey());
+ if (setting != null && !setting.isNull()) {
+ // No need to track generation if the setting doesn't exist
+ synchronized (mLock) {
+ mSettingsRegistry.mGenerationRegistry.addGenerationData(result, setting.getKey(),
+ setting.getName());
+ }
+ }
return result;
}
- private Bundle packageValuesForCallResult(HashMap<String, String> keyValues,
- boolean trackingGeneration) {
+ private Bundle packageValuesForCallResult(String prefix,
+ @NonNull HashMap<String, String> keyValues, boolean trackingGeneration) {
Bundle result = new Bundle();
result.putSerializable(Settings.NameValueTable.VALUE, keyValues);
- if (trackingGeneration) {
+ if (trackingGeneration && !keyValues.isEmpty()) {
+ // No need to track generation if the namespace is empty
synchronized (mLock) {
mSettingsRegistry.mGenerationRegistry.addGenerationData(result,
- mSettingsRegistry.getSettingsLocked(
- SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM).mKey);
+ mSettingsRegistry.getSettingsLocked(SETTINGS_TYPE_CONFIG,
+ UserHandle.USER_SYSTEM).mKey, prefix);
}
}
@@ -3449,7 +3452,7 @@
private void notifyForSettingsChange(int key, String name) {
// Increment the generation first, so observers always see the new value
- mGenerationRegistry.incrementGeneration(key);
+ mGenerationRegistry.incrementGeneration(key, name);
if (isGlobalSettingsKey(key) || isConfigSettingsKey(key)) {
final long token = Binder.clearCallingIdentity();
@@ -3487,7 +3490,7 @@
List<String> changedSettings) {
// Increment the generation first, so observers always see the new value
- mGenerationRegistry.incrementGeneration(key);
+ mGenerationRegistry.incrementGeneration(key, prefix);
StringBuilder stringBuilder = new StringBuilder(prefix);
for (int i = 0; i < changedSettings.size(); ++i) {
@@ -3513,7 +3516,7 @@
if (profileId != userId) {
final int key = makeKey(type, profileId);
// Increment the generation first, so observers always see the new value
- mGenerationRegistry.incrementGeneration(key);
+ mGenerationRegistry.incrementGeneration(key, name);
mHandler.obtainMessage(MyHandler.MSG_NOTIFY_URI_CHANGED,
profileId, 0, uri).sendToTarget();
}
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java
new file mode 100644
index 0000000..d34fe694
--- /dev/null
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java
@@ -0,0 +1,177 @@
+/*
+ * 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.providers.settings;
+
+import static android.provider.Settings.CALL_METHOD_GENERATION_INDEX_KEY;
+import static android.provider.Settings.CALL_METHOD_GENERATION_KEY;
+import static android.provider.Settings.CALL_METHOD_TRACK_GENERATION_KEY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Bundle;
+import android.util.MemoryIntArray;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+@RunWith(AndroidJUnit4.class)
+public class GenerationRegistryTest {
+ @Test
+ public void testGenerationsWithRegularSetting() throws IOException {
+ final GenerationRegistry generationRegistry = new GenerationRegistry(new Object());
+ final int secureKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, 0);
+ final String testSecureSetting = "test_secure_setting";
+ Bundle b = new Bundle();
+ // IncrementGeneration should have no effect on a non-cached setting.
+ generationRegistry.incrementGeneration(secureKey, testSecureSetting);
+ generationRegistry.incrementGeneration(secureKey, testSecureSetting);
+ generationRegistry.incrementGeneration(secureKey, testSecureSetting);
+ generationRegistry.addGenerationData(b, secureKey, testSecureSetting);
+ // Default index is 0 and generation is only 1 despite early calls of incrementGeneration
+ checkBundle(b, 0, 1, false);
+
+ generationRegistry.incrementGeneration(secureKey, testSecureSetting);
+ generationRegistry.addGenerationData(b, secureKey, testSecureSetting);
+ // Index is still 0 and generation is now 2; also check direct array access
+ assertThat(getArray(b).get(0)).isEqualTo(2);
+
+ final int systemKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SYSTEM, 0);
+ final String testSystemSetting = "test_system_setting";
+ generationRegistry.addGenerationData(b, systemKey, testSystemSetting);
+ // Default index is 0 and generation is 1 for another backingStore (system)
+ checkBundle(b, 0, 1, false);
+
+ final String testSystemSetting2 = "test_system_setting2";
+ generationRegistry.addGenerationData(b, systemKey, testSystemSetting2);
+ // Second system setting index is 1 and default generation is 1
+ checkBundle(b, 1, 1, false);
+
+ generationRegistry.incrementGeneration(systemKey, testSystemSetting);
+ generationRegistry.incrementGeneration(systemKey, testSystemSetting);
+ generationRegistry.addGenerationData(b, systemKey, testSystemSetting);
+ // First system setting generation now incremented to 3
+ checkBundle(b, 0, 3, false);
+
+ final int systemKey2 = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SYSTEM, 10);
+ generationRegistry.addGenerationData(b, systemKey2, testSystemSetting);
+ // User 10 has a new set of backingStores
+ checkBundle(b, 0, 1, false);
+
+ // Check user removal
+ generationRegistry.onUserRemoved(10);
+ generationRegistry.incrementGeneration(systemKey2, testSystemSetting);
+
+ // Removed user should not affect existing caches
+ generationRegistry.addGenerationData(b, secureKey, testSecureSetting);
+ assertThat(getArray(b).get(0)).isEqualTo(2);
+
+ // IncrementGeneration should have no effect for a non-cached user
+ b.clear();
+ checkBundle(b, -1, -1, true);
+ // AddGeneration should create new backing store for the non-cached user
+ generationRegistry.addGenerationData(b, systemKey2, testSystemSetting);
+ checkBundle(b, 0, 1, false);
+ }
+
+ @Test
+ public void testGenerationsWithConfigSetting() throws IOException {
+ final GenerationRegistry generationRegistry = new GenerationRegistry(new Object());
+ final String prefix = "test_namespace/";
+ final int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
+
+ Bundle b = new Bundle();
+ generationRegistry.addGenerationData(b, configKey, prefix);
+ checkBundle(b, 0, 1, false);
+
+ final String setting = "test_namespace/test_setting";
+ // Check that the generation of the prefix is incremented correctly
+ generationRegistry.incrementGeneration(configKey, setting);
+ generationRegistry.addGenerationData(b, configKey, prefix);
+ checkBundle(b, 0, 2, false);
+ }
+
+ @Test
+ public void testMaxNumBackingStores() throws IOException {
+ final GenerationRegistry generationRegistry = new GenerationRegistry(new Object());
+ final String testSecureSetting = "test_secure_setting";
+ Bundle b = new Bundle();
+ for (int i = 0; i < GenerationRegistry.NUM_MAX_BACKING_STORE; i++) {
+ b.clear();
+ final int key = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, i);
+ generationRegistry.addGenerationData(b, key, testSecureSetting);
+ checkBundle(b, 0, 1, false);
+ }
+ b.clear();
+ final int key = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE,
+ GenerationRegistry.NUM_MAX_BACKING_STORE + 1);
+ generationRegistry.addGenerationData(b, key, testSecureSetting);
+ // Should fail to add generation because the number of backing stores has reached limit
+ checkBundle(b, -1, -1, true);
+ // Remove one user should free up a backing store
+ generationRegistry.onUserRemoved(0);
+ generationRegistry.addGenerationData(b, key, testSecureSetting);
+ checkBundle(b, 0, 1, false);
+ }
+
+ @Test
+ public void testMaxSizeBackingStore() throws IOException {
+ final GenerationRegistry generationRegistry = new GenerationRegistry(new Object());
+ final int secureKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, 0);
+ final String testSecureSetting = "test_secure_setting";
+ Bundle b = new Bundle();
+ for (int i = 0; i < GenerationRegistry.MAX_BACKING_STORE_SIZE; i++) {
+ generationRegistry.addGenerationData(b, secureKey, testSecureSetting + i);
+ checkBundle(b, i, 1, false);
+ }
+ b.clear();
+ generationRegistry.addGenerationData(b, secureKey, testSecureSetting);
+ // Should fail to increase index because the number of entries in the backing store has
+ // reached the limit
+ checkBundle(b, -1, -1, true);
+ // Shouldn't affect other cached entries
+ generationRegistry.addGenerationData(b, secureKey, testSecureSetting + "0");
+ checkBundle(b, 0, 1, false);
+ }
+
+ private void checkBundle(Bundle b, int expectedIndex, int expectedGeneration, boolean isNull)
+ throws IOException {
+ final MemoryIntArray array = getArray(b);
+ if (isNull) {
+ assertThat(array).isNull();
+ } else {
+ assertThat(array).isNotNull();
+ }
+ final int index = b.getInt(
+ CALL_METHOD_GENERATION_INDEX_KEY, -1);
+ assertThat(index).isEqualTo(expectedIndex);
+ final int generation = b.getInt(CALL_METHOD_GENERATION_KEY, -1);
+ assertThat(generation).isEqualTo(expectedGeneration);
+ if (!isNull) {
+ // Read into the result array with the result index should match the result generation
+ assertThat(array.get(index)).isEqualTo(generation);
+ }
+ }
+
+ private MemoryIntArray getArray(Bundle b) {
+ return b.getParcelable(
+ CALL_METHOD_TRACK_GENERATION_KEY, android.util.MemoryIntArray.class);
+ }
+}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 82ca63d..596ff0e 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -460,6 +460,9 @@
<!-- Permission needed to test registering pull atom callbacks -->
<uses-permission android:name="android.permission.REGISTER_STATS_PULL_ATOM" />
+ <!-- Permission needed to test querying restricted metrics -->
+ <uses-permission android:name="android.permission.READ_RESTRICTED_STATS" />
+
<!-- Permission needed to modify settings overrideable by restore in CTS tests -->
<uses-permission android:name="android.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE" />
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
index 40af294..c1f2aa8 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
@@ -275,7 +275,6 @@
info.brightnessMinimum,
info.brightnessMaximum
);
- mDisplayManager.setTemporaryBrightness(getDisplayId(), brightness);
mDisplayManager.setBrightness(getDisplayId(), brightness);
mA11yMenuLayout.showSnackbar(
getString(R.string.brightness_percentage_label,
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
index dd0a4f0..a25790a 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
@@ -16,6 +16,8 @@
package com.android.systemui.accessibility.accessibilitymenu.view;
+import static android.view.Display.DEFAULT_DISPLAY;
+
import static java.lang.Math.max;
import android.animation.Animator;
@@ -133,7 +135,12 @@
initLayoutParams();
}
- mLayout = new FrameLayout(mService);
+ final Display display = mService.getSystemService(
+ DisplayManager.class).getDisplay(DEFAULT_DISPLAY);
+
+ mLayout = new FrameLayout(
+ mService.createDisplayContext(display).createWindowContext(
+ WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY, null));
updateLayoutPosition();
inflateLayoutAndSetOnTouchListener(mLayout);
mA11yMenuViewPager = new A11yMenuViewPager(mService);
diff --git a/packages/SystemUI/res/drawable/statusbar_chip_bg.xml b/packages/SystemUI/res/drawable/statusbar_chip_bg.xml
new file mode 100644
index 0000000..d7de16d
--- /dev/null
+++ b/packages/SystemUI/res/drawable/statusbar_chip_bg.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <solid android:color="?androidprv:attr/colorAccentPrimary" />
+ <corners android:radius="@dimen/ongoing_appops_chip_bg_corner_radius" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/privacy_chip_bg.xml b/packages/SystemUI/res/drawable/statusbar_privacy_chip_bg.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/privacy_chip_bg.xml
rename to packages/SystemUI/res/drawable/statusbar_privacy_chip_bg.xml
diff --git a/packages/SystemUI/res/layout/battery_status_chip.xml b/packages/SystemUI/res/layout/battery_status_chip.xml
new file mode 100644
index 0000000..ff68ac0
--- /dev/null
+++ b/packages/SystemUI/res/layout/battery_status_chip.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ 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.
+-->
+
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_vertical|end"
+ tools:parentTag="com.android.systemui.statusbar.BatteryStatusChip">
+
+ <LinearLayout
+ android:id="@+id/rounded_container"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/ongoing_appops_chip_height"
+ android:layout_gravity="center"
+ android:background="@drawable/statusbar_chip_bg"
+ android:clipToOutline="true"
+ android:gravity="center"
+ android:maxWidth="@dimen/ongoing_appops_chip_max_width"
+ android:minWidth="@dimen/ongoing_appops_chip_min_width">
+
+ <com.android.systemui.battery.BatteryMeterView
+ android:id="@+id/battery_meter_view"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_marginHorizontal="10dp" />
+
+ </LinearLayout>
+</merge>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml
index d689828..dffe40b 100644
--- a/packages/SystemUI/res/layout/combined_qs_header.xml
+++ b/packages/SystemUI/res/layout/combined_qs_header.xml
@@ -141,11 +141,14 @@
android:layout_width="wrap_content"
android:layout_height="@dimen/large_screen_shade_header_min_height"
android:gravity="center"
- app:layout_constraintEnd_toEndOf="@id/end_guide"
- app:layout_constraintTop_toTopOf="@id/date"
app:layout_constraintBottom_toBottomOf="@id/date"
- >
- <include layout="@layout/ongoing_privacy_chip"/>
+ app:layout_constraintEnd_toEndOf="@id/end_guide"
+ app:layout_constraintTop_toTopOf="@id/date">
+
+ <com.android.systemui.privacy.OngoingPrivacyChip
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent" />
+
</FrameLayout>
</com.android.systemui.util.NoRemeasureMotionLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/notif_half_shelf.xml b/packages/SystemUI/res/layout/notif_half_shelf.xml
index 8ba1ff3..37b8ae0 100644
--- a/packages/SystemUI/res/layout/notif_half_shelf.xml
+++ b/packages/SystemUI/res/layout/notif_half_shelf.xml
@@ -76,7 +76,10 @@
android:layout_height="48dp"
android:layout_width="wrap_content"
android:layout_gravity="center_vertical"
- android:padding="8dp" />
+ android:padding="8dp"
+ android:track="@drawable/settingslib_track_selector"
+ android:thumb="@drawable/settingslib_thumb_selector"
+ android:theme="@style/MainSwitch.Settingslib"/>
</com.android.systemui.statusbar.notification.row.AppControlView>
<!-- ChannelRows get added dynamically -->
@@ -101,7 +104,7 @@
android:minWidth="@dimen/notification_importance_toggle_size"
android:minHeight="@dimen/notification_importance_toggle_size"
android:maxWidth="200dp"
- style="@style/TextAppearance.NotificationInfo.Button"/>
+ style="@style/Widget.Dialog.Button"/>
<TextView
android:id="@+id/done_button"
android:text="@string/inline_ok_button"
@@ -113,7 +116,7 @@
android:minWidth="@dimen/notification_importance_toggle_size"
android:minHeight="@dimen/notification_importance_toggle_size"
android:layout_alignParentEnd="true"
- style="@style/TextAppearance.NotificationInfo.Button"/>
+ style="@style/Widget.Dialog.Button"/>
</RelativeLayout>
</LinearLayout>
</FrameLayout>
diff --git a/packages/SystemUI/res/layout/notif_half_shelf_row.xml b/packages/SystemUI/res/layout/notif_half_shelf_row.xml
index d03cd7e..190f999 100644
--- a/packages/SystemUI/res/layout/notif_half_shelf_row.xml
+++ b/packages/SystemUI/res/layout/notif_half_shelf_row.xml
@@ -85,6 +85,9 @@
android:layout_width="wrap_content"
android:layout_gravity="center_vertical"
android:padding="8dp"
+ android:track="@drawable/settingslib_track_selector"
+ android:thumb="@drawable/settingslib_thumb_selector"
+ android:theme="@style/MainSwitch.Settingslib"
/>
</LinearLayout>
</com.android.systemui.statusbar.notification.row.ChannelRow>
diff --git a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
index d1a2cf4..2c7467d 100644
--- a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
+++ b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
@@ -16,16 +16,15 @@
-->
-<com.android.systemui.privacy.OngoingPrivacyChip
- xmlns:android="http://schemas.android.com/apk/res/android"
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/privacy_chip"
+ xmlns:tools="http://schemas.android.com/tools"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:layout_gravity="center_vertical|end"
- android:focusable="true"
android:clipChildren="false"
android:clipToPadding="false"
- android:paddingStart="8dp"
+ tools:parentTag="com.android.systemui.privacy.OngoingPrivacyChip">
>
<LinearLayout
@@ -35,8 +34,9 @@
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:gravity="center"
+ android:clipToOutline="true"
+ android:clipToPadding="false"
android:layout_gravity="center"
android:minWidth="@dimen/ongoing_appops_chip_min_width"
- android:maxWidth="@dimen/ongoing_appops_chip_max_width"
- />
-</com.android.systemui.privacy.OngoingPrivacyChip>
\ No newline at end of file
+ android:maxWidth="@dimen/ongoing_appops_chip_max_width" />
+</merge>
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_rear_landscape_base.json b/packages/SystemUI/res/raw/biometricprompt_rear_landscape_base.json
new file mode 100644
index 0000000..49c1c40
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_rear_landscape_base.json
@@ -0,0 +1 @@
+{"v":"5.8.1","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"BiometricPrompt_Rear_Landscape_Base_Foldable","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 18","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[169.478,169.749,0],"ix":2,"l":2},"a":{"a":0,"k":[-48.123,-30.19,0],"ix":1,"l":2},"s":{"a":0,"k":[132,132,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".grey400","cl":"grey400","parent":13,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"black circle matte","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey904","cl":"grey904","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-62.577,35.536,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-2.552,0.087],[0,0]],"o":[[0,0],[0,-3.287],[0,0],[0,0]],"v":[[-2.301,8.869],[-2.301,-3.772],[2.301,-9.806],[2.301,9.806]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"black circle matte 2","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".blue401","cl":"blue401","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-62.577,-27.655,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,3.286],[0,0],[-2.552,0.086],[0,0]],"o":[[0,0],[0,-3.286],[0,0],[-2.552,-0.086]],"v":[[-2.301,16.282],[-2.301,-16.281],[2.301,-22.313],[2.301,22.313]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"black circle matte 3","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Finger 2","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-129,"s":[-67]},{"t":-29,"s":[0]}],"ix":10},"p":{"a":0,"k":[-75.352,41.307,0],"ix":2,"l":2},"a":{"a":0,"k":[94.648,211.307,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[6.72,-5.642],[0,0],[-9.394,-0.562],[-0.298,-0.038]],"o":[[-5.153,4.329],[3.882,-16.05],[0.31,0.019],[-0.044,0.75]],"v":[[0.863,12.222],[-8.931,14.755],[8.005,-15.108],[8.931,-15.021]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[81.486,130.081],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.459,6.045],[-5.153,4.329],[-0.044,0.75],[3.116,-24.664],[5.23,-22.052],[8.666,11.92],[-2.9,9.135]],"o":[[0,0],[6.72,-5.642],[12.723,1.335],[-2.369,18.762],[-13.993,-5.333],[2.255,-5.502],[1.843,-5.815]],"v":[[-9.99,-18.348],[-0.196,-20.881],[7.872,-48.124],[21.578,-9.331],[12.104,48.124],[-22.574,21.555],[-14.791,-0.206]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[82.545,163.184],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 8","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"black circle matte 4","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".grey903","cl":"grey903","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-18.345,-92.442,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[24.07,0],[0,0],[-8.27,0],[0,0]],"o":[[0,0],[0,8.269],[0,0],[-14.024,-17.379]],"v":[[-29.778,-14.252],[-29.778,-0.721],[-14.805,14.252],[29.778,14.252]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"black circle matte 5","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".grey902","cl":"grey902","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-15.947,-30.19,0],"ix":2,"l":2},"a":{"a":0,"k":[154.053,139.81,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[2.3,0.367],[0,0],[-2.364,0.157],[0,0]],"o":[[0,0],[2.3,-0.367],[0,0],[-2.364,-0.157]],"v":[[-3.5,75.533],[-3.5,-75.533],[3.5,-76.312],[3.5,76.312]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[113.225,139.81],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 7","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,8.269],[0,0],[2.181,-0.187],[0,0],[-2.23,0],[0,42.252],[10.593,13.127],[0,0]],"o":[[0,0],[-2.23,0],[0,0],[2.181,0.187],[42.252,0],[0,-18.182],[0,0],[-8.27,0]],"v":[[-34.946,-62.973],[-34.946,-76.504],[-41.558,-76.201],[-41.558,76.201],[-34.946,76.504],[41.558,0],[24.61,-48],[-19.973,-48]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[156.824,139.81],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 5","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".black 2","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-48.123,-30.19,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.833],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.167],"y":[0,0,0]},"t":-129,"s":[0,0,100]},{"t":-79,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".grey700","cl":"grey700","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-56.481,-59.936,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[3.767,0],[0,0],[0,-3.767],[0,0],[-3.767,0],[0,0],[0,3.767],[0,0]],"o":[[0,0],[-3.767,0],[0,0],[0,3.767],[0,0],[3.767,0],[0,0],[0,-3.767]],"v":[[46.055,-14.479],[-46.056,-14.479],[-52.876,-7.659],[-52.876,7.658],[-46.056,14.479],[46.055,14.479],[52.876,7.658],[52.876,-7.659]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":".grey901","cl":"grey901","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[16.485,2.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[50,50,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[4.184,0],[0,0],[0,0],[0,0],[0,-4.375]],"o":[[0,4.184],[0,0],[0,0],[0,0],[4.375,0],[0,0]],"v":[[114.116,92.129],[106.54,99.705],[7.788,99.705],[7.788,-99.704],[106.161,-99.704],[114.116,-91.749]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[5.707,0],[0,0],[1.894,-1.05],[0.886,0.346],[0,0],[2.166,0],[0,0],[0,-5.707],[0,0],[0,-1.46],[0,0],[-1.133,-0.038],[0,0],[0,-1.459],[0,0],[-1.133,-0.038],[0,0],[-5.708,0],[0,0],[-1.894,1.05],[-0.846,-0.289],[0,0],[-2.166,0],[0,0],[0,5.706],[0,0]],"o":[[0,0],[-2.166,0],[-0.883,0.354],[0,0],[-1.895,-1.05],[0,0],[-5.708,0],[0,0],[-1.133,0.038],[0,0],[0,1.46],[0,0],[-1.133,0.038],[0,0],[0,1.46],[0,0],[0,5.707],[0,0],[2.165,0],[0.833,-0.334],[0,0],[1.894,1.05],[0,0],[5.707,0],[0,0],[0,-5.707]],"v":[[106.16,-102.082],[8.455,-102.082],[2.265,-100.48],[-0.488,-100.468],[-0.519,-100.48],[-6.71,-102.082],[-104.116,-102.082],[-114.45,-91.748],[-114.45,-36.119],[-116.494,-33.44],[-116.494,-18.979],[-114.45,-16.3],[-114.45,-0.877],[-116.494,1.802],[-116.494,28.704],[-114.45,31.383],[-114.45,91.749],[-104.116,102.083],[-6.495,102.083],[-0.305,100.481],[2.294,100.425],[2.395,100.481],[9.872,102.083],[106.161,102.083],[116.494,91.75],[116.494,-91.748]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.529411792755,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_rear_portrait_base.json b/packages/SystemUI/res/raw/biometricprompt_rear_portrait_base.json
new file mode 100644
index 0000000..9ea0d35
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_rear_portrait_base.json
@@ -0,0 +1 @@
+{"v":"5.8.1","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"BiometricPrompt_Rear_Portrait_Base_Foldable","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 18","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[169.478,169.749,0],"ix":2,"l":2},"a":{"a":0,"k":[-48.123,-30.19,0],"ix":1,"l":2},"s":{"a":0,"k":[132,132,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".grey400","cl":"grey400","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"black circle matte","parent":14,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey904","cl":"grey904","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-62.577,35.536,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-2.552,0.087],[0,0]],"o":[[0,0],[0,-3.287],[0,0],[0,0]],"v":[[-2.301,8.869],[-2.301,-3.772],[2.301,-9.806],[2.301,9.806]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"black circle matte 2","parent":14,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".blue401","cl":"blue401","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-62.577,-27.655,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,3.286],[0,0],[-2.552,0.086],[0,0]],"o":[[0,0],[0,-3.286],[0,0],[-2.552,-0.086]],"v":[[-2.301,16.282],[-2.301,-16.281],[2.301,-22.313],[2.301,22.313]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"black circle matte 3","parent":14,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Finger 3","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-2,"ix":10},"p":{"a":0,"k":[260.134,83.782,0],"ix":2,"l":2},"a":{"a":0,"k":[302.634,38.782,0],"ix":1,"l":2},"s":{"a":0,"k":[178,178,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.262,5.076],[0,0],[-0.424,-7.095],[-0.028,-0.225]],"o":[[3.269,-3.892],[-12.123,2.932],[0.015,0.234],[0.567,-0.034]],"v":[[9.232,0.652],[11.145,-6.746],[-11.412,6.046],[-11.346,6.746]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[241.281,55.033],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 5","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[4.565,-1.102],[3.269,-3.892],[0.566,-0.033],[-18.63,2.353],[-16.656,3.951],[9.004,6.546],[6.9,-2.19]],"o":[[0,0],[-4.262,5.076],[1.008,9.61],[14.171,-1.79],[-4.028,-10.569],[-4.156,1.703],[-4.392,1.392]],"v":[[-13.858,-7.546],[-15.771,-0.148],[-36.349,5.946],[-7.047,16.299],[36.349,9.142],[16.281,-17.051],[-0.156,-11.172]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[266.285,55.833],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 4","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"black circle matte 4","parent":14,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".grey903","cl":"grey903","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-18.345,-92.442,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[24.07,0],[0,0],[-8.27,0],[0,0]],"o":[[0,0],[0,8.269],[0,0],[-14.024,-17.379]],"v":[[-29.778,-14.252],[-29.778,-0.721],[-14.805,14.252],[29.778,14.252]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"black circle matte 5","parent":14,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".grey902","cl":"grey902","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-15.947,-30.19,0],"ix":2,"l":2},"a":{"a":0,"k":[154.053,139.81,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[2.3,0.367],[0,0],[-2.364,0.157],[0,0]],"o":[[0,0],[2.3,-0.367],[0,0],[-2.364,-0.157]],"v":[[-3.5,75.533],[-3.5,-75.533],[3.5,-76.312],[3.5,76.312]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[113.225,139.81],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 7","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,8.269],[0,0],[2.181,-0.187],[0,0],[-2.23,0],[0,42.252],[10.593,13.127],[0,0]],"o":[[0,0],[-2.23,0],[0,0],[2.181,0.187],[42.252,0],[0,-18.182],[0,0],[-8.27,0]],"v":[[-34.946,-62.973],[-34.946,-76.504],[-41.558,-76.201],[-41.558,76.201],[-34.946,76.504],[41.558,0],[24.61,-48],[-19.973,-48]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[156.824,139.81],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 5","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".black 2","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-48.123,-30.19,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.833],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.167],"y":[0,0,0]},"t":-129,"s":[0,0,100]},{"t":-79,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":".grey700","cl":"grey700","parent":16,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-56.481,-59.936,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[3.767,0],[0,0],[0,-3.767],[0,0],[-3.767,0],[0,0],[0,3.767],[0,0]],"o":[[0,0],[-3.767,0],[0,0],[0,3.767],[0,0],[3.767,0],[0,0],[0,-3.767]],"v":[[46.055,-14.479],[-46.056,-14.479],[-52.876,-7.659],[-52.876,7.658],[-46.056,14.479],[46.055,14.479],[52.876,7.658],[52.876,-7.659]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":".grey901","cl":"grey901","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[16.485,2.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[50,50,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[4.184,0],[0,0],[0,0],[0,0],[0,-4.375]],"o":[[0,4.184],[0,0],[0,0],[0,0],[4.375,0],[0,0]],"v":[[114.116,92.129],[106.54,99.705],[7.788,99.705],[7.788,-99.704],[106.161,-99.704],[114.116,-91.749]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[5.707,0],[0,0],[1.894,-1.05],[0.886,0.346],[0,0],[2.166,0],[0,0],[0,-5.707],[0,0],[0,-1.46],[0,0],[-1.133,-0.038],[0,0],[0,-1.459],[0,0],[-1.133,-0.038],[0,0],[-5.708,0],[0,0],[-1.894,1.05],[-0.846,-0.289],[0,0],[-2.166,0],[0,0],[0,5.706],[0,0]],"o":[[0,0],[-2.166,0],[-0.883,0.354],[0,0],[-1.895,-1.05],[0,0],[-5.708,0],[0,0],[-1.133,0.038],[0,0],[0,1.46],[0,0],[-1.133,0.038],[0,0],[0,1.46],[0,0],[0,5.707],[0,0],[2.165,0],[0.833,-0.334],[0,0],[1.894,1.05],[0,0],[5.707,0],[0,0],[0,-5.707]],"v":[[106.16,-102.082],[8.455,-102.082],[2.265,-100.48],[-0.488,-100.468],[-0.519,-100.48],[-6.71,-102.082],[-104.116,-102.082],[-114.45,-91.748],[-114.45,-36.119],[-116.494,-33.44],[-116.494,-18.979],[-114.45,-16.3],[-114.45,-0.877],[-116.494,1.802],[-116.494,28.704],[-114.45,31.383],[-114.45,91.749],[-104.116,102.083],[-6.495,102.083],[-0.305,100.481],[2.294,100.425],[2.395,100.481],[9.872,102.083],[106.161,102.083],[116.494,91.75],[116.494,-91.748]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.529411792755,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_rear_portrait_reverse_base.json b/packages/SystemUI/res/raw/biometricprompt_rear_portrait_reverse_base.json
new file mode 100644
index 0000000..f2b2593
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_rear_portrait_reverse_base.json
@@ -0,0 +1 @@
+{"v":"5.8.1","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"BiometricPrompt_Rear_Portrait_Reverse_Base_Foldable","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 18","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":270,"ix":10},"p":{"a":0,"k":[169.478,169.749,0],"ix":2,"l":2},"a":{"a":0,"k":[-48.123,-30.19,0],"ix":1,"l":2},"s":{"a":0,"k":[132,132,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".grey400","cl":"grey400","parent":13,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"black circle matte","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey904","cl":"grey904","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-62.577,35.536,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-2.552,0.087],[0,0]],"o":[[0,0],[0,-3.287],[0,0],[0,0]],"v":[[-2.301,8.869],[-2.301,-3.772],[2.301,-9.806],[2.301,9.806]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"black circle matte 2","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".blue401","cl":"blue401","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-62.577,-27.655,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,3.286],[0,0],[-2.552,0.086],[0,0]],"o":[[0,0],[0,-3.286],[0,0],[-2.552,-0.086]],"v":[[-2.301,16.282],[-2.301,-16.281],[2.301,-22.313],[2.301,22.313]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"black circle matte 3","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Finger 2","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-75.352,41.307,0],"ix":2,"l":2},"a":{"a":0,"k":[94.648,211.307,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[6.72,-5.642],[0,0],[-9.394,-0.562],[-0.298,-0.038]],"o":[[-5.153,4.329],[3.882,-16.05],[0.31,0.019],[-0.044,0.75]],"v":[[0.863,12.222],[-8.931,14.755],[8.005,-15.108],[8.931,-15.021]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[81.486,130.081],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.459,6.045],[-5.153,4.329],[-0.044,0.75],[3.116,-24.664],[5.23,-22.052],[8.666,11.92],[-2.9,9.135]],"o":[[0,0],[6.72,-5.642],[12.723,1.335],[-2.369,18.762],[-13.993,-5.333],[2.255,-5.502],[1.843,-5.815]],"v":[[-9.99,-18.348],[-0.196,-20.881],[7.872,-48.124],[21.578,-9.331],[12.104,48.124],[-22.574,21.555],[-14.791,-0.206]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[82.545,163.184],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 8","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"black circle matte 4","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".grey903","cl":"grey903","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-18.345,-92.442,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[24.07,0],[0,0],[-8.27,0],[0,0]],"o":[[0,0],[0,8.269],[0,0],[-14.024,-17.379]],"v":[[-29.778,-14.252],[-29.778,-0.721],[-14.805,14.252],[29.778,14.252]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"black circle matte 5","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".grey902","cl":"grey902","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-15.947,-30.19,0],"ix":2,"l":2},"a":{"a":0,"k":[154.053,139.81,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[2.3,0.367],[0,0],[-2.364,0.157],[0,0]],"o":[[0,0],[2.3,-0.367],[0,0],[-2.364,-0.157]],"v":[[-3.5,75.533],[-3.5,-75.533],[3.5,-76.312],[3.5,76.312]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[113.225,139.81],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 7","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,8.269],[0,0],[2.181,-0.187],[0,0],[-2.23,0],[0,42.252],[10.593,13.127],[0,0]],"o":[[0,0],[-2.23,0],[0,0],[2.181,0.187],[42.252,0],[0,-18.182],[0,0],[-8.27,0]],"v":[[-34.946,-62.973],[-34.946,-76.504],[-41.558,-76.201],[-41.558,76.201],[-34.946,76.504],[41.558,0],[24.61,-48],[-19.973,-48]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[156.824,139.81],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 5","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".black 2","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-48.123,-30.19,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.833],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.167],"y":[0,0,0]},"t":-129,"s":[0,0,100]},{"t":-79,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".grey700","cl":"grey700","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-56.481,-59.936,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[3.767,0],[0,0],[0,-3.767],[0,0],[-3.767,0],[0,0],[0,3.767],[0,0]],"o":[[0,0],[-3.767,0],[0,0],[0,3.767],[0,0],[3.767,0],[0,0],[0,-3.767]],"v":[[46.055,-14.479],[-46.056,-14.479],[-52.876,-7.659],[-52.876,7.658],[-46.056,14.479],[46.055,14.479],[52.876,7.658],[52.876,-7.659]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":".grey901","cl":"grey901","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[16.485,2.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[50,50,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[4.184,0],[0,0],[0,0],[0,0],[0,-4.375]],"o":[[0,4.184],[0,0],[0,0],[0,0],[4.375,0],[0,0]],"v":[[114.116,92.129],[106.54,99.705],[7.788,99.705],[7.788,-99.704],[106.161,-99.704],[114.116,-91.749]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[5.707,0],[0,0],[1.894,-1.05],[0.886,0.346],[0,0],[2.166,0],[0,0],[0,-5.707],[0,0],[0,-1.46],[0,0],[-1.133,-0.038],[0,0],[0,-1.459],[0,0],[-1.133,-0.038],[0,0],[-5.708,0],[0,0],[-1.894,1.05],[-0.846,-0.289],[0,0],[-2.166,0],[0,0],[0,5.706],[0,0]],"o":[[0,0],[-2.166,0],[-0.883,0.354],[0,0],[-1.895,-1.05],[0,0],[-5.708,0],[0,0],[-1.133,0.038],[0,0],[0,1.46],[0,0],[-1.133,0.038],[0,0],[0,1.46],[0,0],[0,5.707],[0,0],[2.165,0],[0.833,-0.334],[0,0],[1.894,1.05],[0,0],[5.707,0],[0,0],[0,-5.707]],"v":[[106.16,-102.082],[8.455,-102.082],[2.265,-100.48],[-0.488,-100.468],[-0.519,-100.48],[-6.71,-102.082],[-104.116,-102.082],[-114.45,-91.748],[-114.45,-36.119],[-116.494,-33.44],[-116.494,-18.979],[-114.45,-16.3],[-114.45,-0.877],[-116.494,1.802],[-116.494,28.704],[-114.45,31.383],[-114.45,91.749],[-104.116,102.083],[-6.495,102.083],[-0.305,100.481],[2.294,100.425],[2.395,100.481],[9.872,102.083],[106.161,102.083],[116.494,91.75],[116.494,-91.748]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.529411792755,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 3ebaae0..7b3caff 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1721,13 +1721,13 @@
<string name="keyboard_shortcut_search_list_hint">Search shortcuts</string>
<!-- The description for no shortcuts results [CHAR LIMIT=25] -->
<string name="keyboard_shortcut_search_list_no_result">No shortcuts found</string>
- <!-- The title of system category in shortcut search list. [CHAR LIMIT=15] -->
+ <!-- The title of system category in shortcut search list. [CHAR LIMIT=25] -->
<string name="keyboard_shortcut_search_category_system">System</string>
- <!-- The title of input category in shortcut search list. [CHAR LIMIT=15] -->
+ <!-- The title of input category in shortcut search list. [CHAR LIMIT=25] -->
<string name="keyboard_shortcut_search_category_input">Input</string>
- <!-- The title of open apps category in shortcut search list. [CHAR LIMIT=15] -->
+ <!-- The title of open apps category in shortcut search list. [CHAR LIMIT=25] -->
<string name="keyboard_shortcut_search_category_open_apps">Open apps</string>
- <!-- The title of current app category in shortcut search list. [CHAR LIMIT=15] -->
+ <!-- The title of current app category in shortcut search list. [CHAR LIMIT=25] -->
<string name="keyboard_shortcut_search_category_current_app">Current app</string>
<!-- User visible title for the keyboard shortcut that triggers the notification shade. [CHAR LIMIT=70] -->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index a71fb56..4bc9491 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -104,4 +104,9 @@
* Sent when the surface for navigation bar is created or changed
*/
void onNavigationBarSurface(in SurfaceControl surface) = 26;
+
+ /**
+ * Sent when the task bar stash state is toggled.
+ */
+ void onTaskbarToggled() = 27;
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index 2a37bd3..80b9758 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -36,7 +36,6 @@
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;
-import android.util.SparseArray;
import android.view.IRecentsAnimationController;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
@@ -106,12 +105,24 @@
private RecentsAnimationListener mListener = null;
private RecentsAnimationControllerCompat mWrapped = null;
private IRemoteTransitionFinishedCallback mFinishCB = null;
+
+ /**
+ * List of tasks that we are switching away from via this transition. Upon finish, these
+ * pausing tasks will become invisible.
+ * These need to be ordered since the order must be restored if there is no task-switch.
+ */
private ArrayList<TaskState> mPausingTasks = null;
+
+ /**
+ * List of tasks that we are switching to. Upon finish, these will remain visible and
+ * on top.
+ */
+ private ArrayList<TaskState> mOpeningTasks = null;
+
private WindowContainerToken mPipTask = null;
private WindowContainerToken mRecentsTask = null;
private int mRecentsTaskId = 0;
private TransitionInfo mInfo = null;
- private ArrayList<SurfaceControl> mOpeningLeashes = null;
private boolean mOpeningSeparateHome = false;
private ArrayMap<SurfaceControl, SurfaceControl> mLeashMap = null;
private PictureInPictureSurfaceTransaction mPipTransaction = null;
@@ -120,6 +131,15 @@
private RemoteAnimationTarget[] mAppearedTargets;
private boolean mWillFinishToHome = false;
+ /** The animation is idle, waiting for the user to choose a task to switch to. */
+ private static final int STATE_NORMAL = 0;
+
+ /** The user chose a new task to switch to and the animation is animating to it. */
+ private static final int STATE_NEW_TASK = 1;
+
+ /** The latest state that the recents animation is operating in. */
+ private int mState = STATE_NORMAL;
+
void start(RecentsAnimationControllerCompat wrapped, RecentsAnimationListener listener,
IBinder transition, TransitionInfo info, SurfaceControl.Transaction t,
IRemoteTransitionFinishedCallback finishedCallback) {
@@ -132,12 +152,14 @@
mInfo = info;
mFinishCB = finishedCallback;
mPausingTasks = new ArrayList<>();
+ mOpeningTasks = new ArrayList<>();
mPipTask = null;
mRecentsTask = null;
mRecentsTaskId = -1;
mLeashMap = new ArrayMap<>();
mTransition = transition;
mKeyguardLocked = (info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0;
+ mState = STATE_NORMAL;
final ArrayList<RemoteAnimationTarget> apps = new ArrayList<>();
final ArrayList<RemoteAnimationTarget> wallpapers = new ArrayList<>();
@@ -178,6 +200,9 @@
} else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
mRecentsTask = taskInfo.token;
mRecentsTaskId = taskInfo.taskId;
+ } else if (change.getMode() == TRANSIT_OPEN
+ || change.getMode() == TRANSIT_TO_FRONT) {
+ mOpeningTasks.add(new TaskState(change, target.leash));
}
}
}
@@ -189,34 +214,41 @@
@SuppressLint("NewApi")
boolean merge(TransitionInfo info, SurfaceControl.Transaction t) {
- SparseArray<TransitionInfo.Change> openingTasks = null;
+ ArrayList<TransitionInfo.Change> openingTasks = null;
+ ArrayList<TransitionInfo.Change> closingTasks = null;
mAppearedTargets = null;
- boolean foundHomeOpening = false;
+ mOpeningSeparateHome = false;
+ TransitionInfo.Change recentsOpening = null;
boolean foundRecentsClosing = false;
boolean hasChangingApp = false;
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final RemoteAnimationTargetCompat.LeafTaskFilter leafTaskFilter =
+ new RemoteAnimationTargetCompat.LeafTaskFilter();
+ for (int i = 0; i < info.getChanges().size(); ++i) {
final TransitionInfo.Change change = info.getChanges().get(i);
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ final boolean isLeafTask = leafTaskFilter.test(change);
if (change.getMode() == TRANSIT_OPEN || change.getMode() == TRANSIT_TO_FRONT) {
- final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
- if (taskInfo != null) {
+ if (mRecentsTask.equals(change.getContainer())) {
+ recentsOpening = change;
+ } else if (isLeafTask) {
if (taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
// This is usually a 3p launcher
- foundHomeOpening = true;
+ mOpeningSeparateHome = true;
}
if (openingTasks == null) {
- openingTasks = new SparseArray<>();
+ openingTasks = new ArrayList<>();
}
- if (taskInfo.hasParentTask()) {
- // Collects opening leaf tasks only since Launcher monitors leaf task
- // ids to perform recents animation.
- openingTasks.remove(taskInfo.parentTaskId);
- }
- openingTasks.put(taskInfo.taskId, change);
+ openingTasks.add(change);
}
} else if (change.getMode() == TRANSIT_CLOSE
|| change.getMode() == TRANSIT_TO_BACK) {
if (mRecentsTask.equals(change.getContainer())) {
foundRecentsClosing = true;
+ } else if (isLeafTask) {
+ if (closingTasks == null) {
+ closingTasks = new ArrayList<>();
+ }
+ closingTasks.add(change);
}
} else if (change.getMode() == TRANSIT_CHANGE) {
hasChangingApp = true;
@@ -234,45 +266,72 @@
}
return false;
}
- if (openingTasks == null) return false;
- int pauseMatches = 0;
- if (!foundHomeOpening) {
+ if (recentsOpening != null) {
+ // the recents task re-appeared. This happens if the user gestures before the
+ // task-switch (NEW_TASK) animation finishes.
+ if (mState == STATE_NORMAL) {
+ Log.e(TAG, "Returning to recents while recents is already idle.");
+ }
+ if (closingTasks == null || closingTasks.size() == 0) {
+ Log.e(TAG, "Returning to recents without closing any opening tasks.");
+ }
+ // Setup may hide it initially since it doesn't know that overview was still active.
+ t.show(recentsOpening.getLeash());
+ t.setAlpha(recentsOpening.getLeash(), 1.f);
+ mState = STATE_NORMAL;
+ }
+ boolean didMergeThings = false;
+ if (closingTasks != null) {
+ // Cancelling a task-switch. Move the tasks back to mPausing from mOpening
+ for (int i = 0; i < closingTasks.size(); ++i) {
+ final TransitionInfo.Change change = closingTasks.get(i);
+ int openingIdx = TaskState.indexOf(mOpeningTasks, change);
+ if (openingIdx < 0) {
+ Log.e(TAG, "Back to existing recents animation from an unrecognized "
+ + "task: " + change.getTaskInfo().taskId);
+ continue;
+ }
+ mPausingTasks.add(mOpeningTasks.remove(openingIdx));
+ didMergeThings = true;
+ }
+ }
+ if (openingTasks != null && openingTasks.size() > 0) {
+ // Switching to some new tasks, add to mOpening and remove from mPausing. Also,
+ // enter NEW_TASK state since this will start the switch-to animation.
+ final int layer = mInfo.getChanges().size() * 3;
+ final RemoteAnimationTarget[] targets =
+ new RemoteAnimationTarget[openingTasks.size()];
for (int i = 0; i < openingTasks.size(); ++i) {
- if (TaskState.indexOf(mPausingTasks, openingTasks.valueAt(i)) >= 0) {
- ++pauseMatches;
+ final TransitionInfo.Change change = openingTasks.get(i);
+ int pausingIdx = TaskState.indexOf(mPausingTasks, change);
+ if (pausingIdx >= 0) {
+ // Something is showing/opening a previously-pausing app.
+ targets[i] = newTarget(change, layer, mPausingTasks.get(pausingIdx).mLeash);
+ mOpeningTasks.add(mPausingTasks.remove(pausingIdx));
+ // Setup hides opening tasks initially, so make it visible again (since we
+ // are already showing it).
+ t.show(change.getLeash());
+ t.setAlpha(change.getLeash(), 1.f);
+ } else {
+ // We are receiving new opening tasks, so convert to onTasksAppeared.
+ targets[i] = newTarget(change, layer, info, t, mLeashMap);
+ t.reparent(targets[i].leash, mInfo.getRootLeash());
+ t.setLayer(targets[i].leash, layer);
+ mOpeningTasks.add(new TaskState(change, targets[i].leash));
}
}
+ didMergeThings = true;
+ mState = STATE_NEW_TASK;
+ mAppearedTargets = targets;
}
- if (pauseMatches > 0) {
- if (pauseMatches != mPausingTasks.size()) {
- // We are not really "returning" properly... something went wrong.
- throw new IllegalStateException("\"Concelling\" a recents transitions by "
- + "unpausing " + pauseMatches + " apps after pausing "
- + mPausingTasks.size() + " apps.");
- }
- // In this case, we are "returning" to an already running app, so just consume
- // the merge and do nothing.
- info.releaseAllSurfaces();
- t.close();
- return true;
- }
- final int layer = mInfo.getChanges().size() * 3;
- mOpeningLeashes = new ArrayList<>();
- mOpeningSeparateHome = foundHomeOpening;
- final RemoteAnimationTarget[] targets =
- new RemoteAnimationTarget[openingTasks.size()];
- for (int i = 0; i < openingTasks.size(); ++i) {
- final TransitionInfo.Change change = openingTasks.valueAt(i);
- mOpeningLeashes.add(change.getLeash());
- // We are receiving new opening tasks, so convert to onTasksAppeared.
- targets[i] = newTarget(change, layer, info, t, mLeashMap);
- t.reparent(targets[i].leash, mInfo.getRootLeash());
- t.setLayer(targets[i].leash, layer);
+ if (!didMergeThings) {
+ // Didn't recognize anything in incoming transition so don't merge it.
+ Log.w(TAG, "Don't know how to merge this transition.");
+ return false;
}
t.apply();
// not using the incoming anim-only surfaces
info.releaseAnimSurfaces();
- mAppearedTargets = targets;
return true;
}
@@ -300,6 +359,8 @@
if (enabled) {
// transient launches don't receive focus automatically. Since we are taking over
// the gesture now, take focus explicitly.
+ // This also moves recents back to top if the user gestured before a switch
+ // animation finished.
try {
ActivityTaskManager.getService().setFocusedTask(mRecentsTaskId);
} catch (RemoteException e) {
@@ -336,8 +397,8 @@
if (toHome) wct.reorder(mRecentsTask, true /* toTop */);
else wct.restoreTransientOrder(mRecentsTask);
}
- if (!toHome && !mWillFinishToHome && mPausingTasks != null && mOpeningLeashes == null) {
- // The gesture went back to opening the app rather than continuing with
+ if (!toHome && !mWillFinishToHome && mPausingTasks != null && mState == STATE_NORMAL) {
+ // The gesture is returning to the pausing-task(s) rather than continuing with
// recents, so end the transition by moving the app back to the top (and also
// re-showing it's task).
for (int i = mPausingTasks.size() - 1; i >= 0; --i) {
@@ -349,25 +410,28 @@
wct.restoreTransientOrder(mRecentsTask);
}
} else if (toHome && mOpeningSeparateHome && mPausingTasks != null) {
- // Special situaition where 3p launcher was changed during recents (this happens
+ // Special situation where 3p launcher was changed during recents (this happens
// during tapltests...). Here we get both "return to home" AND "home opening".
- // This is basically going home, but we have to restore recents order and also
- // treat the home "pausing" task properly.
- for (int i = mPausingTasks.size() - 1; i >= 0; --i) {
- final TaskState state = mPausingTasks.get(i);
+ // This is basically going home, but we have to restore the recents and home order.
+ for (int i = 0; i < mOpeningTasks.size(); ++i) {
+ final TaskState state = mOpeningTasks.get(i);
if (state.mTaskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
- // Treat as opening (see above)
+ // Make sure it is on top.
wct.reorder(state.mToken, true /* onTop */);
- t.show(state.mTaskSurface);
- } else {
- // Treat as hiding (see below)
- t.hide(state.mTaskSurface);
}
+ t.show(state.mTaskSurface);
+ }
+ for (int i = mPausingTasks.size() - 1; i >= 0; --i) {
+ t.hide(mPausingTasks.get(i).mTaskSurface);
}
if (!mKeyguardLocked && mRecentsTask != null) {
wct.restoreTransientOrder(mRecentsTask);
}
} else {
+ // The general case: committing to recents, going home, or switching tasks.
+ for (int i = 0; i < mOpeningTasks.size(); ++i) {
+ t.show(mOpeningTasks.get(i).mTaskSurface);
+ }
for (int i = 0; i < mPausingTasks.size(); ++i) {
if (!sendUserLeaveHint) {
// This means recents is not *actually* finishing, so of course we gotta
@@ -391,7 +455,7 @@
try {
mFinishCB.onTransitionFinished(wct.isEmpty() ? null : wct, t);
} catch (RemoteException e) {
- Log.e("RemoteTransitionCompat", "Failed to call animation finish callback", e);
+ Log.e(TAG, "Failed to call animation finish callback", e);
t.apply();
}
// Only release the non-local created surface references. The animator is responsible
@@ -402,12 +466,13 @@
mListener = null;
mFinishCB = null;
mPausingTasks = null;
+ mOpeningTasks = null;
mAppearedTargets = null;
mInfo = null;
- mOpeningLeashes = null;
mOpeningSeparateHome = false;
mLeashMap = null;
mTransition = null;
+ mState = STATE_NORMAL;
}
@Override public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) {
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
index a25b281..c5a06b4 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
@@ -24,7 +24,6 @@
import android.widget.Button;
import com.android.internal.util.EmergencyAffordanceManager;
-import com.android.internal.widget.LockPatternUtils;
/**
* This class implements a smart emergency button that updates itself based
@@ -40,8 +39,6 @@
private int mDownY;
private boolean mLongPressWasDragged;
- private LockPatternUtils mLockPatternUtils;
-
private final boolean mEnableEmergencyCallWhileSimLocked;
public EmergencyButton(Context context) {
@@ -58,7 +55,6 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mLockPatternUtils = new LockPatternUtils(mContext);
if (mEmergencyAffordanceManager.needsEmergencyAffordance()) {
setOnLongClickListener(v -> {
if (!mLongPressWasDragged
@@ -95,7 +91,8 @@
return super.performLongClick();
}
- void updateEmergencyCallButton(boolean isInCall, boolean hasTelephonyRadio, boolean simLocked) {
+ void updateEmergencyCallButton(boolean isInCall, boolean hasTelephonyRadio, boolean simLocked,
+ boolean isSecure) {
boolean visible = false;
if (hasTelephonyRadio) {
// Emergency calling requires a telephony radio.
@@ -107,7 +104,7 @@
visible = mEnableEmergencyCallWhileSimLocked;
} else {
// Only show if there is a secure screen (pin/pattern/SIM pin/SIM puk);
- visible = mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser());
+ visible = isSecure;
}
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
index ea808eb..f7e8eb4 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
@@ -18,6 +18,7 @@
import static com.android.systemui.DejankUtils.whitelistIpcs;
+import android.annotation.SuppressLint;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
import android.content.Intent;
@@ -31,16 +32,22 @@
import android.util.Log;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.dagger.KeyguardBouncerScope;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.util.EmergencyDialerConstants;
import com.android.systemui.util.ViewController;
+import java.util.concurrent.Executor;
+
import javax.inject.Inject;
/** View Controller for {@link com.android.keyguard.EmergencyButton}. */
@@ -57,6 +64,9 @@
private final MetricsLogger mMetricsLogger;
private EmergencyButtonCallback mEmergencyButtonCallback;
+ private LockPatternUtils mLockPatternUtils;
+ private Executor mMainExecutor;
+ private Executor mBackgroundExecutor;
private final KeyguardUpdateMonitorCallback mInfoCallback =
new KeyguardUpdateMonitorCallback() {
@@ -78,12 +88,15 @@
}
};
- private EmergencyButtonController(@Nullable EmergencyButton view,
+ @VisibleForTesting
+ public EmergencyButtonController(@Nullable EmergencyButton view,
ConfigurationController configurationController,
KeyguardUpdateMonitor keyguardUpdateMonitor, TelephonyManager telephonyManager,
PowerManager powerManager, ActivityTaskManager activityTaskManager,
ShadeController shadeController,
- @Nullable TelecomManager telecomManager, MetricsLogger metricsLogger) {
+ @Nullable TelecomManager telecomManager, MetricsLogger metricsLogger,
+ LockPatternUtils lockPatternUtils,
+ Executor mainExecutor, Executor backgroundExecutor) {
super(view);
mConfigurationController = configurationController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -93,6 +106,9 @@
mShadeController = shadeController;
mTelecomManager = telecomManager;
mMetricsLogger = metricsLogger;
+ mLockPatternUtils = lockPatternUtils;
+ mMainExecutor = mainExecutor;
+ mBackgroundExecutor = backgroundExecutor;
}
@Override
@@ -113,13 +129,27 @@
mConfigurationController.removeCallback(mConfigurationListener);
}
- private void updateEmergencyCallButton() {
+ /**
+ * Updates the visibility of the emergency button.
+ *
+ * This method runs binder calls in a background thread.
+ */
+ @VisibleForTesting
+ @SuppressLint("MissingPermission")
+ public void updateEmergencyCallButton() {
if (mView != null) {
- mView.updateEmergencyCallButton(
- mTelecomManager != null && mTelecomManager.isInCall(),
- getContext().getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_TELEPHONY),
- mKeyguardUpdateMonitor.isSimPinVoiceSecure());
+ // Run in bg thread to avoid throttling the main thread with binder call.
+ mBackgroundExecutor.execute(() -> {
+ boolean isInCall = mTelecomManager != null && mTelecomManager.isInCall();
+ boolean isSecure = mLockPatternUtils
+ .isSecure(KeyguardUpdateMonitor.getCurrentUser());
+ mMainExecutor.execute(() -> mView.updateEmergencyCallButton(
+ /* isInCall= */ isInCall,
+ /* hasTelephonyRadio= */ getContext().getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_TELEPHONY),
+ /* simLocked= */ mKeyguardUpdateMonitor.isSimPinVoiceSecure(),
+ /* isSecure= */ isSecure));
+ });
}
}
@@ -129,6 +159,7 @@
/**
* Shows the emergency dialer or returns the user to the existing call.
*/
+ @SuppressLint("MissingPermission")
public void takeEmergencyCallAction() {
mMetricsLogger.action(MetricsEvent.ACTION_EMERGENCY_CALL);
if (mPowerManager != null) {
@@ -136,29 +167,35 @@
}
mActivityTaskManager.stopSystemLockTaskMode();
mShadeController.collapseShade(false);
- if (mTelecomManager != null && mTelecomManager.isInCall()) {
- mTelecomManager.showInCallScreen(false);
- if (mEmergencyButtonCallback != null) {
- mEmergencyButtonCallback.onEmergencyButtonClickedWhenInCall();
- }
- } else {
- mKeyguardUpdateMonitor.reportEmergencyCallAction(true /* bypassHandler */);
- if (mTelecomManager == null) {
- Log.wtf(LOG_TAG, "TelecomManager was null, cannot launch emergency dialer");
- return;
- }
- Intent emergencyDialIntent =
- mTelecomManager.createLaunchEmergencyDialerIntent(null /* number*/)
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
- | Intent.FLAG_ACTIVITY_CLEAR_TOP)
- .putExtra(EmergencyDialerConstants.EXTRA_ENTRY_TYPE,
- EmergencyDialerConstants.ENTRY_TYPE_LOCKSCREEN_BUTTON);
+ // Run in bg thread to avoid throttling the main thread with binder call.
+ mBackgroundExecutor.execute(() -> {
+ boolean isInCall = mTelecomManager != null && mTelecomManager.isInCall();
+ mMainExecutor.execute(() -> {
+ if (isInCall) {
+ mTelecomManager.showInCallScreen(false);
+ if (mEmergencyButtonCallback != null) {
+ mEmergencyButtonCallback.onEmergencyButtonClickedWhenInCall();
+ }
+ } else {
+ mKeyguardUpdateMonitor.reportEmergencyCallAction(true /* bypassHandler */);
+ if (mTelecomManager == null) {
+ Log.wtf(LOG_TAG, "TelecomManager was null, cannot launch emergency dialer");
+ return;
+ }
+ Intent emergencyDialIntent =
+ mTelecomManager.createLaunchEmergencyDialerIntent(null /* number*/)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
+ | Intent.FLAG_ACTIVITY_CLEAR_TOP)
+ .putExtra(EmergencyDialerConstants.EXTRA_ENTRY_TYPE,
+ EmergencyDialerConstants.ENTRY_TYPE_LOCKSCREEN_BUTTON);
- getContext().startActivityAsUser(emergencyDialIntent,
- ActivityOptions.makeCustomAnimation(getContext(), 0, 0).toBundle(),
- new UserHandle(KeyguardUpdateMonitor.getCurrentUser()));
- }
+ getContext().startActivityAsUser(emergencyDialIntent,
+ ActivityOptions.makeCustomAnimation(getContext(), 0, 0).toBundle(),
+ new UserHandle(KeyguardUpdateMonitor.getCurrentUser()));
+ }
+ });
+ });
}
/** */
@@ -178,13 +215,19 @@
@Nullable
private final TelecomManager mTelecomManager;
private final MetricsLogger mMetricsLogger;
+ private final LockPatternUtils mLockPatternUtils;
+ private final Executor mMainExecutor;
+ private final Executor mBackgroundExecutor;
@Inject
public Factory(ConfigurationController configurationController,
KeyguardUpdateMonitor keyguardUpdateMonitor, TelephonyManager telephonyManager,
PowerManager powerManager, ActivityTaskManager activityTaskManager,
ShadeController shadeController,
- @Nullable TelecomManager telecomManager, MetricsLogger metricsLogger) {
+ @Nullable TelecomManager telecomManager, MetricsLogger metricsLogger,
+ LockPatternUtils lockPatternUtils,
+ @Main Executor mainExecutor,
+ @Background Executor backgroundExecutor) {
mConfigurationController = configurationController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -194,14 +237,17 @@
mShadeController = shadeController;
mTelecomManager = telecomManager;
mMetricsLogger = metricsLogger;
+ mLockPatternUtils = lockPatternUtils;
+ mMainExecutor = mainExecutor;
+ mBackgroundExecutor = backgroundExecutor;
}
/** Construct an {@link com.android.keyguard.EmergencyButtonController}. */
public EmergencyButtonController create(EmergencyButton view) {
return new EmergencyButtonController(view, mConfigurationController,
mKeyguardUpdateMonitor, mTelephonyManager, mPowerManager, mActivityTaskManager,
- mShadeController,
- mTelecomManager, mMetricsLogger);
+ mShadeController, mTelecomManager, mMetricsLogger, mLockPatternUtils,
+ mMainExecutor, mBackgroundExecutor);
}
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
index 0a4880e..3b0644e 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
@@ -33,7 +33,6 @@
import androidx.annotation.Nullable;
-import com.android.internal.widget.LockPatternUtils;
import com.android.settingslib.Utils;
import com.android.systemui.R;
@@ -46,7 +45,6 @@
private final TextView mDigitText;
private final TextView mKlondikeText;
- private final LockPatternUtils mLockPatternUtils;
private final PowerManager mPM;
private int mDigit = -1;
@@ -107,7 +105,6 @@
setOnHoverListener(new LiftToActivateListener(
(AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE)));
- mLockPatternUtils = new LockPatternUtils(context);
mPM = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
index 03d999f..ce42534 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
@@ -24,6 +24,7 @@
import android.animation.LayoutTransition;
import android.animation.ObjectAnimator;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -195,7 +196,13 @@
return false;
}
- void onBatteryLevelChanged(int level, boolean pluggedIn) {
+ /**
+ * Update battery level
+ *
+ * @param level int between 0 and 100 (representing percentage value)
+ * @param pluggedIn whether the device is plugged in or not
+ */
+ public void onBatteryLevelChanged(@IntRange(from = 0, to = 100) int level, boolean pluggedIn) {
mDrawable.setCharging(pluggedIn);
mDrawable.setBatteryLevel(level);
mCharging = pluggedIn;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
index 3ea3cd1..709ddf5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
@@ -231,21 +231,29 @@
if (isReverseDefaultRotation) (rotation + 1) % 4 else rotation
@RawRes
- private fun getSideFpsAnimationForTransition(rotation: Int): Int = when (rotation) {
- Surface.ROTATION_90 -> if (isDeviceFolded) {
- R.raw.biometricprompt_folded_base_topleft
- } else {
- R.raw.biometricprompt_portrait_base_topleft
- }
- Surface.ROTATION_270 -> if (isDeviceFolded) {
- R.raw.biometricprompt_folded_base_bottomright
- } else {
- R.raw.biometricprompt_portrait_base_bottomright
- }
- else -> if (isDeviceFolded) {
- R.raw.biometricprompt_folded_base_default
- } else {
- R.raw.biometricprompt_landscape_base
+ private fun getSideFpsAnimationForTransition(rotation: Int): Int {
+ when (rotation) {
+ Surface.ROTATION_90 -> if (context.isInRearDisplayMode()) {
+ return R.raw.biometricprompt_rear_portrait_reverse_base
+ } else if (isDeviceFolded) {
+ return R.raw.biometricprompt_folded_base_topleft
+ } else {
+ return R.raw.biometricprompt_portrait_base_topleft
+ }
+ Surface.ROTATION_270 -> if (context.isInRearDisplayMode()) {
+ return R.raw.biometricprompt_rear_portrait_base
+ } else if (isDeviceFolded) {
+ return R.raw.biometricprompt_folded_base_bottomright
+ } else {
+ return R.raw.biometricprompt_portrait_base_bottomright
+ }
+ else -> if (context.isInRearDisplayMode()) {
+ return R.raw.biometricprompt_rear_landscape_base
+ } else if (isDeviceFolded) {
+ return R.raw.biometricprompt_folded_base_default
+ } else {
+ return R.raw.biometricprompt_landscape_base
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index e12c170..a7b6e6a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -658,6 +658,7 @@
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
mIconController.onConfigurationChanged(newConfig);
+ updateState(mSavedState.getInt(AuthDialog.KEY_BIOMETRIC_STATE));
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index 6c49078..ac6a22c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -172,12 +172,16 @@
override fun show(
sensorId: Int,
@BiometricOverlayConstants.ShowReason reason: Int
- ) =
- if (reason.isReasonToAutoShow(activityTaskManager)) {
+ ) {
+ if (
+ reason.isReasonToAutoShow(activityTaskManager) &&
+ !context.isInRearDisplayMode()
+ ) {
show(SideFpsUiRequestSource.AUTO_SHOW, reason)
} else {
hide(SideFpsUiRequestSource.AUTO_SHOW)
}
+ }
override fun hide(sensorId: Int) = hide(SideFpsUiRequestSource.AUTO_SHOW)
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt
index d0d6f4c..3d56326 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt
@@ -36,6 +36,7 @@
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityManager
import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.R
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
@@ -117,4 +118,7 @@
@Retention(RetentionPolicy.SOURCE)
@IntDef(CREDENTIAL_PIN, CREDENTIAL_PATTERN, CREDENTIAL_PASSWORD)
internal annotation class CredentialType
-}
\ No newline at end of file
+}
+
+fun Context.isInRearDisplayMode(): Boolean = resources.getIntArray(
+ com.android.internal.R.array.config_rearDisplayDeviceStates).isNotEmpty()
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialog.java
index 8e062bd..653c12e 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialog.java
@@ -46,11 +46,11 @@
@VisibleForTesting
protected View mDialogView;
private MediaOutputDialogFactory mMediaOutputDialogFactory;
- private String mSwitchBroadcastApp;
+ private String mCurrentBroadcastApp;
private String mOutputPackageName;
public BroadcastDialog(Context context, MediaOutputDialogFactory mediaOutputDialogFactory,
- String switchBroadcastApp, String outputPkgName, UiEventLogger uiEventLogger) {
+ String currentBroadcastApp, String outputPkgName, UiEventLogger uiEventLogger) {
super(context);
if (DEBUG) {
Log.d(TAG, "Init BroadcastDialog");
@@ -58,7 +58,7 @@
mContext = getContext();
mMediaOutputDialogFactory = mediaOutputDialogFactory;
- mSwitchBroadcastApp = switchBroadcastApp;
+ mCurrentBroadcastApp = currentBroadcastApp;
mOutputPackageName = outputPkgName;
mUiEventLogger = uiEventLogger;
}
@@ -77,20 +77,18 @@
TextView title = mDialogView.requireViewById(R.id.dialog_title);
TextView subTitle = mDialogView.requireViewById(R.id.dialog_subtitle);
- title.setText(
- mContext.getString(R.string.bt_le_audio_broadcast_dialog_title,
- MediaDataUtils.getAppLabel(mContext, mOutputPackageName,
- mContext.getString(
- R.string.bt_le_audio_broadcast_dialog_unknown_name))));
- subTitle.setText(
- mContext.getString(R.string.bt_le_audio_broadcast_dialog_sub_title,
- mSwitchBroadcastApp));
+ title.setText(mContext.getString(
+ R.string.bt_le_audio_broadcast_dialog_title, mCurrentBroadcastApp));
+ String switchBroadcastApp = MediaDataUtils.getAppLabel(mContext, mOutputPackageName,
+ mContext.getString(R.string.bt_le_audio_broadcast_dialog_unknown_name));
+ subTitle.setText(mContext.getString(
+ R.string.bt_le_audio_broadcast_dialog_sub_title, switchBroadcastApp));
Button switchBroadcast = mDialogView.requireViewById(R.id.switch_broadcast);
Button changeOutput = mDialogView.requireViewById(R.id.change_output);
Button cancelBtn = mDialogView.requireViewById(R.id.cancel);
switchBroadcast.setText(mContext.getString(
- R.string.bt_le_audio_broadcast_dialog_switch_app, mSwitchBroadcastApp), null);
+ R.string.bt_le_audio_broadcast_dialog_switch_app, switchBroadcastApp));
changeOutput.setOnClickListener((view) -> {
mMediaOutputDialogFactory.create(mOutputPackageName, true, null);
dismiss();
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogController.java b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogController.java
index 8a54345..1b699e8 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogController.java
@@ -47,10 +47,15 @@
mMediaOutputDialogFactory = mediaOutputDialogFactory;
}
- public void createBroadcastDialog(String switchAppName, String outputPkgName,
+ /** Creates a [BroadcastDialog] for the user to switch broadcast or change the output device
+ *
+ * @param currentBroadcastAppName Indicates the APP name currently broadcasting
+ * @param outputPkgName Indicates the output media package name to be switched
+ */
+ public void createBroadcastDialog(String currentBroadcastAppName, String outputPkgName,
boolean aboveStatusBar, View view) {
BroadcastDialog broadcastDialog = new BroadcastDialog(mContext, mMediaOutputDialogFactory,
- switchAppName, outputPkgName, mUiEventLogger);
+ currentBroadcastAppName, outputPkgName, mUiEventLogger);
if (view != null) {
mDialogLaunchAnimator.showFromView(broadcastDialog, view);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
index a2077a3..24f6296 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
@@ -108,7 +108,7 @@
mSeekbar.setOnSeekBarChangeListener(mSeekBarListener);
- mIconStart.setOnClickListener((view) -> {
+ mIconStartFrame.setOnClickListener((view) -> {
final int progress = mSeekbar.getProgress();
if (progress > 0) {
mSeekbar.setProgress(progress - 1);
@@ -116,7 +116,7 @@
}
});
- mIconEnd.setOnClickListener((view) -> {
+ mIconEndFrame.setOnClickListener((view) -> {
final int progress = mSeekbar.getProgress();
if (progress < mSeekbar.getMax()) {
mSeekbar.setProgress(progress + 1);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 19b000b..d5a4146 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -54,6 +54,7 @@
import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.dagger.StartCentralSurfacesModule;
+import com.android.systemui.statusbar.events.StatusBarEventsModule;
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.phone.DozeServiceHost;
@@ -104,6 +105,7 @@
QSModule.class,
ReferenceScreenshotModule.class,
RotationLockModule.class,
+ StatusBarEventsModule.class,
StartCentralSurfacesModule.class,
VolumeModule.class,
KeyboardShortcutsModule.class
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index f6bb85f..c96946b 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -256,6 +256,11 @@
@BindsOptionalOf
abstract FingerprintInteractiveToAuthProvider optionalFingerprintInteractiveToAuthProvider();
+ @BindsOptionalOf
+ //TODO(b/269430792 remove full qualifier. Full qualifier is used to avoid merge conflict.)
+ abstract com.android.systemui.statusbar.events.SystemStatusAnimationScheduler
+ optionalSystemStatusAnimationScheduler();
+
@SysUISingleton
@Binds
abstract SystemClock bindSystemClock(SystemClockImpl systemClock);
diff --git a/packages/SystemUI/src/com/android/systemui/devicepolicy/DevicePolicyManagerExt.kt b/packages/SystemUI/src/com/android/systemui/devicepolicy/DevicePolicyManagerExt.kt
deleted file mode 100644
index 1390b4d..0000000
--- a/packages/SystemUI/src/com/android/systemui/devicepolicy/DevicePolicyManagerExt.kt
+++ /dev/null
@@ -1,32 +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.devicepolicy
-
-import android.app.admin.DevicePolicyManager
-import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL
-import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL
-import android.content.ComponentName
-
-/** Returns true if the admin of [userId] disallows keyguard shortcuts. */
-fun DevicePolicyManager.areKeyguardShortcutsDisabled(
- admin: ComponentName? = null,
- userId: Int
-): Boolean {
- val flags = getKeyguardDisabledFeatures(admin, userId)
- return flags and KEYGUARD_DISABLE_SHORTCUTS_ALL == KEYGUARD_DISABLE_SHORTCUTS_ALL ||
- flags and KEYGUARD_DISABLE_FEATURES_ALL == KEYGUARD_DISABLE_FEATURES_ALL
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index a14aa52..f282aae 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -221,7 +221,14 @@
mDreamOverlayTouchMonitor.init();
mStateController.setShouldShowComplications(shouldShowComplications());
- addOverlayWindowLocked(layoutParams);
+
+ // If we are not able to add the overlay window, reset the overlay.
+ if (!addOverlayWindowLocked(layoutParams)) {
+ resetCurrentDreamOverlayLocked();
+ return;
+ }
+
+
setCurrentStateLocked(Lifecycle.State.RESUMED);
mStateController.setOverlayActive(true);
final ComponentName dreamComponent = getDreamComponent();
@@ -267,7 +274,7 @@
* @param layoutParams The {@link android.view.WindowManager.LayoutParams} which allow inserting
* into the dream window.
*/
- private void addOverlayWindowLocked(WindowManager.LayoutParams layoutParams) {
+ private boolean addOverlayWindowLocked(WindowManager.LayoutParams layoutParams) {
mWindow = new PhoneWindow(mContext);
// Default to SystemUI name for TalkBack.
mWindow.setTitle("");
@@ -292,9 +299,22 @@
// risk an IllegalStateException in some cases when setting the container view as the
// window's content view and the container view hasn't been properly removed previously).
removeContainerViewFromParentLocked();
+
mWindow.setContentView(mDreamOverlayContainerViewController.getContainerView());
- mWindowManager.addView(mWindow.getDecorView(), mWindow.getAttributes());
+ // It is possible that a dream's window (and the dream as a whole) is no longer valid by
+ // the time the overlay service processes the dream. This can happen for example if
+ // another dream is started immediately after the existing dream begins. In this case, the
+ // overlay service should identify the situation through the thrown exception and tear down
+ // the overlay.
+ try {
+ mWindowManager.addView(mWindow.getDecorView(), mWindow.getAttributes());
+ return true;
+ } catch (WindowManager.BadTokenException exception) {
+ Log.e(TAG, "Dream activity window invalid: " + layoutParams.packageName,
+ exception);
+ return false;
+ }
}
private void removeContainerViewFromParentLocked() {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/BouncerScrimController.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/BouncerScrimController.java
index f5bbba7..776b7bd 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/BouncerScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/BouncerScrimController.java
@@ -34,7 +34,7 @@
@Override
public void show() {
- mStatusBarKeyguardViewManager.showBouncer(false);
+ mStatusBarKeyguardViewManager.showPrimaryBouncer(false);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 0b41c55..ab78b1b 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -155,7 +155,7 @@
// TODO(b/255618149): Tracking Bug
@JvmField
val CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES =
- unreleasedFlag(216, "customizable_lock_screen_quick_affordances", teamfood = true)
+ releasedFlag(216, "customizable_lock_screen_quick_affordances")
/** Shows chipbar UI whenever the device is unlocked by ActiveUnlock (watch). */
// TODO(b/256513609): Tracking Bug
@@ -187,7 +187,7 @@
// TODO(b/262780002): Tracking Bug
@JvmField
- val REVAMPED_WALLPAPER_UI = unreleasedFlag(222, "revamped_wallpaper_ui", teamfood = true)
+ val REVAMPED_WALLPAPER_UI = releasedFlag(222, "revamped_wallpaper_ui")
/** A different path for unocclusion transitions back to keyguard */
// TODO(b/262859270): Tracking Bug
@@ -214,10 +214,9 @@
// TODO(b/266242192): Tracking Bug
@JvmField
val LOCK_SCREEN_LONG_PRESS_ENABLED =
- unreleasedFlag(
+ releasedFlag(
228,
- "lock_screen_long_press_enabled",
- teamfood = true,
+ "lock_screen_long_press_enabled"
)
// 300 - power menu
@@ -247,6 +246,9 @@
"qs_user_detail_shortcut"
)
+ @JvmField
+ val QS_PIPELINE_NEW_HOST = unreleasedFlag(504, "qs_pipeline_new_host", teamfood = false)
+
// TODO(b/254512383): Tracking Bug
@JvmField
val FULL_SCREEN_USER_SWITCHER =
@@ -295,6 +297,9 @@
val NEW_STATUS_BAR_ICONS_DEBUG_COLORING =
unreleasedFlag(611, "new_status_bar_icons_debug_coloring")
+ // TODO(b/265892345): Tracking Bug
+ val PLUG_IN_STATUS_BAR_CHIP = unreleasedFlag(265892345, "plug_in_status_bar_chip")
+
// 700 - dialer/calls
// TODO(b/254512734): Tracking Bug
val ONGOING_CALL_STATUS_BAR_CHIP = releasedFlag(700, "ongoing_call_status_bar_chip")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
index dfe1038..0140529 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
@@ -24,6 +24,7 @@
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.LegacyAlternateBouncer
+import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -33,6 +34,7 @@
class AlternateBouncerInteractor
@Inject
constructor(
+ private val keyguardStateController: KeyguardStateController,
private val bouncerRepository: KeyguardBouncerRepository,
private val biometricSettingsRepository: BiometricSettingsRepository,
private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
@@ -102,7 +104,8 @@
biometricSettingsRepository.isFingerprintEnrolled.value &&
biometricSettingsRepository.isStrongBiometricAllowed.value &&
biometricSettingsRepository.isFingerprintEnabledByDevicePolicy.value &&
- !deviceEntryFingerprintAuthRepository.isLockedOut.value
+ !deviceEntryFingerprintAuthRepository.isLockedOut.value &&
+ !keyguardStateController.isUnlocked
} else {
legacyAlternateBouncer != null &&
keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(true)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 9b5f7f6..dfbe1c2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -26,7 +26,6 @@
import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.devicepolicy.areKeyguardShortcutsDisabled
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
@@ -411,10 +410,16 @@
)
}
- private suspend fun isFeatureDisabledByDevicePolicy(): Boolean =
- withContext(backgroundDispatcher) {
- devicePolicyManager.areKeyguardShortcutsDisabled(userId = userTracker.userId)
- }
+ private suspend fun isFeatureDisabledByDevicePolicy(): Boolean {
+ val flags =
+ withContext(backgroundDispatcher) {
+ devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId)
+ }
+ val flagsToCheck =
+ DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL or
+ DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL
+ return flagsToCheck and flags != 0
+ }
companion object {
private const val TAG = "KeyguardQuickAffordanceInteractor"
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index 68d2c5c..6cf051a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -39,6 +39,7 @@
import com.android.systemui.R
import com.android.systemui.classifier.FalsingCollector
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.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -68,6 +69,7 @@
import com.android.systemui.util.traceSection
import java.io.PrintWriter
import java.util.TreeMap
+import java.util.concurrent.Executor
import javax.inject.Inject
import javax.inject.Provider
import kotlinx.coroutines.CoroutineScope
@@ -93,7 +95,8 @@
private val mediaHostStatesManager: MediaHostStatesManager,
private val activityStarter: ActivityStarter,
private val systemClock: SystemClock,
- @Main executor: DelayableExecutor,
+ @Main private val mainExecutor: DelayableExecutor,
+ @Background private val backgroundExecutor: Executor,
private val mediaManager: MediaDataManager,
configurationController: ConfigurationController,
falsingCollector: FalsingCollector,
@@ -256,7 +259,7 @@
MediaCarouselScrollHandler(
mediaCarousel,
pageIndicator,
- executor,
+ mainExecutor,
this::onSwipeToDismiss,
this::updatePageIndicatorLocation,
this::updateSeekbarListening,
@@ -618,10 +621,50 @@
MediaPlayerData.visiblePlayerKeys()
.elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
if (existingPlayer == null) {
- val newPlayer = mediaControlPanelFactory.get()
- newPlayer.attachPlayer(
- MediaViewHolder.create(LayoutInflater.from(context), mediaContent)
+ setupNewPlayer(key, data, isSsReactivated, curVisibleMediaKey)
+ } else {
+ existingPlayer.bindPlayer(data, key)
+ MediaPlayerData.addMediaPlayer(
+ key,
+ data,
+ existingPlayer,
+ systemClock,
+ isSsReactivated,
+ debugLogger
)
+ val packageName = MediaPlayerData.smartspaceMediaData?.packageName ?: String()
+ // In case of recommendations hits.
+ // Check the playing status of media player and the package name.
+ // To make sure we scroll to the right app's media player.
+ if (
+ isReorderingAllowed ||
+ shouldScrollToKey &&
+ data.isPlaying == true &&
+ packageName == data.packageName
+ ) {
+ reorderAllPlayers(curVisibleMediaKey, key)
+ } else {
+ needsReordering = true
+ }
+ updatePageIndicator()
+ mediaCarouselScrollHandler.onPlayersChanged()
+ mediaFrame.requiresRemeasuring = true
+ }
+ return existingPlayer == null
+ }
+
+ private fun setupNewPlayer(
+ key: String,
+ data: MediaData,
+ isSsReactivated: Boolean,
+ curVisibleMediaKey: MediaPlayerData.MediaSortKey?,
+ ) {
+ backgroundExecutor.execute {
+ val mediaViewHolder = createMediaViewHolderInBg()
+ // Add the new player in the main thread.
+ mainExecutor.execute {
+ val newPlayer = mediaControlPanelFactory.get()
+ newPlayer.attachPlayer(mediaViewHolder)
newPlayer.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
val lp =
LinearLayout.LayoutParams(
@@ -651,36 +694,16 @@
} else {
needsReordering = true
}
- } else {
- existingPlayer.bindPlayer(data, key)
- MediaPlayerData.addMediaPlayer(
- key,
- data,
- existingPlayer,
- systemClock,
- isSsReactivated,
- debugLogger
- )
- val packageName = MediaPlayerData.smartspaceMediaData?.packageName ?: String()
- // In case of recommendations hits.
- // Check the playing status of media player and the package name.
- // To make sure we scroll to the right app's media player.
- if (
- isReorderingAllowed ||
- shouldScrollToKey &&
- data.isPlaying == true &&
- packageName == data.packageName
- ) {
- reorderAllPlayers(curVisibleMediaKey, key)
- } else {
- needsReordering = true
- }
+ updatePageIndicator()
+ mediaCarouselScrollHandler.onPlayersChanged()
+ mediaFrame.requiresRemeasuring = true
}
- updatePageIndicator()
- mediaCarouselScrollHandler.onPlayersChanged()
- mediaFrame.requiresRemeasuring = true
- return existingPlayer == null
}
+ }
+
+ private fun createMediaViewHolderInBg(): MediaViewHolder {
+ return MediaViewHolder.create(LayoutInflater.from(context), mediaContent)
+ }
private fun addSmartspaceMediaRecommendations(
key: String,
@@ -714,15 +737,14 @@
debugLogger.logPotentialMemoryLeak(existingSmartspaceMediaKey)
}
}
-
val newRecs = mediaControlPanelFactory.get()
- newRecs.attachRecommendation(
+ val recommendationViewHolder =
RecommendationViewHolder.create(
LayoutInflater.from(context),
mediaContent,
mediaFlags.isRecommendationCardUpdateEnabled()
)
- )
+ newRecs.attachRecommendation(recommendationViewHolder)
newRecs.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
val lp =
LinearLayout.LayoutParams(
@@ -746,17 +768,6 @@
reorderAllPlayers(curVisibleMediaKey)
updatePageIndicator()
mediaFrame.requiresRemeasuring = true
- // Check postcondition: mediaContent should have the same number of children as there
- // are
- // elements in mediaPlayers.
- if (MediaPlayerData.players().size != mediaContent.childCount) {
- Log.e(
- TAG,
- "Size of players list and number of views in carousel are out of sync. " +
- "Players size is ${MediaPlayerData.players().size}. " +
- "View count is ${mediaContent.childCount}."
- )
- }
}
fun removePlayer(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 288266a..097cc3e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -227,7 +227,7 @@
private final BroadcastDialogController mBroadcastDialogController;
private boolean mIsCurrentBroadcastedApp = false;
private boolean mShowBroadcastDialogButton = false;
- private String mSwitchBroadcastApp;
+ private String mCurrentBroadcastApp;
private MultiRippleController mMultiRippleController;
private TurbulenceNoiseController mTurbulenceNoiseController;
private final FeatureFlags mFeatureFlags;
@@ -573,9 +573,8 @@
// TODO(b/233698402): Use the package name instead of app label to avoid the
// unexpected result.
mIsCurrentBroadcastedApp = device != null
- && TextUtils.equals(device.getName(),
- MediaDataUtils.getAppLabel(mContext, mPackageName, mContext.getString(
- R.string.bt_le_audio_broadcast_dialog_unknown_name)));
+ && TextUtils.equals(device.getName(),
+ mContext.getString(R.string.broadcasting_description_is_broadcasting));
useDisabledAlpha = !mIsCurrentBroadcastedApp;
// Always be enabled if the broadcast button is shown
isTapEnabled = true;
@@ -630,8 +629,8 @@
// media output dialog.
if (!mIsCurrentBroadcastedApp) {
mLogger.logOpenBroadcastDialog(mUid, mPackageName, mInstanceId);
- mSwitchBroadcastApp = device.getName().toString();
- mBroadcastDialogController.createBroadcastDialog(mSwitchBroadcastApp,
+ mCurrentBroadcastApp = device.getName().toString();
+ mBroadcastDialogController.createBroadcastDialog(mCurrentBroadcastApp,
mPackageName, true, mMediaViewHolder.getSeamlessButton());
} else {
mLogger.logOpenOutputSwitcher(mUid, mPackageName, mInstanceId);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index f1a5c3e..27e99f7 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -436,6 +436,19 @@
}
}
+ @Override
+ public void toggleTaskbar() {
+ if (mOverviewProxyService.getProxy() == null) {
+ return;
+ }
+
+ try {
+ mOverviewProxyService.getProxy().onTaskbarToggled();
+ } catch (RemoteException e) {
+ Log.e(TAG, "onTaskbarToggled() failed", e);
+ }
+ }
+
private void clearTransient() {
if (mTaskbarTransientShowing) {
mTaskbarTransientShowing = false;
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index 5702681..be615d6 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -17,21 +17,17 @@
package com.android.systemui.notetask
import android.app.KeyguardManager
-import android.app.admin.DevicePolicyManager
import android.content.ActivityNotFoundException
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
-import android.os.Build
import android.os.UserManager
import android.util.Log
import com.android.internal.logging.UiEvent
import com.android.internal.logging.UiEventLogger
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.devicepolicy.areKeyguardShortcutsDisabled
import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
-import com.android.systemui.settings.UserTracker
import com.android.systemui.util.kotlin.getOrNull
import com.android.wm.shell.bubbles.Bubbles
import java.util.Optional
@@ -53,10 +49,8 @@
private val optionalBubbles: Optional<Bubbles>,
private val optionalKeyguardManager: Optional<KeyguardManager>,
private val optionalUserManager: Optional<UserManager>,
- private val devicePolicyManager: DevicePolicyManager,
@NoteTaskEnabledKey private val isEnabled: Boolean,
private val uiEventLogger: UiEventLogger,
- private val userTracker: UserTracker,
) {
/**
@@ -86,18 +80,6 @@
// TODO(b/249954038): We should handle direct boot (isUserUnlocked). For now, we do nothing.
if (!userManager.isUserUnlocked) return
- val isKeyguardLocked = keyguardManager.isKeyguardLocked
- // KeyguardQuickAffordanceInteractor blocks the quick affordance from showing in the
- // keyguard if it is not allowed by the admin policy. Here we block any other way to show
- // note task when the screen is locked.
- if (
- isKeyguardLocked &&
- devicePolicyManager.areKeyguardShortcutsDisabled(userId = userTracker.userId)
- ) {
- logDebug { "Enterprise policy disallows launching note app when the screen is locked." }
- return
- }
-
val noteTaskInfo = resolver.resolveInfo() ?: return
uiEvent?.let { uiEventLogger.log(it, noteTaskInfo.uid, noteTaskInfo.packageName) }
@@ -105,7 +87,7 @@
// TODO(b/266686199): We should handle when app not available. For now, we log.
val intent = noteTaskInfo.toCreateNoteIntent()
try {
- if (isInMultiWindowMode || isKeyguardLocked) {
+ if (isInMultiWindowMode || keyguardManager.isKeyguardLocked) {
context.startActivity(intent)
} else {
bubbles.showOrHideAppBubble(intent)
@@ -162,7 +144,7 @@
}
companion object {
- val TAG = NoteTaskController::class.simpleName.orEmpty()
+ private val TAG = NoteTaskController::class.simpleName.orEmpty()
private fun NoteTaskInfoResolver.NoteTaskInfo.toCreateNoteIntent(): Intent {
return Intent(ACTION_CREATE_NOTE)
@@ -183,9 +165,3 @@
const val INTENT_EXTRA_USE_STYLUS_MODE = "android.intent.extra.USE_STYLUS_MODE"
}
}
-
-private inline fun logDebug(message: () -> String) {
- if (Build.IS_DEBUGGABLE) {
- Log.d(NoteTaskController.TAG, message())
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
index 8ad2f86..79167f2 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
@@ -16,7 +16,11 @@
import android.content.Context
import android.util.AttributeSet
+import android.view.Gravity.CENTER_VERTICAL
+import android.view.Gravity.END
import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.widget.ImageView
import android.widget.LinearLayout
import com.android.settingslib.Utils
@@ -35,7 +39,7 @@
private var iconSize = 0
private var iconColor = 0
- private lateinit var iconsContainer: LinearLayout
+ private val iconsContainer: LinearLayout
var privacyList = emptyList<PrivacyItem>()
set(value) {
@@ -43,11 +47,13 @@
updateView(PrivacyChipBuilder(context, field))
}
- override fun onFinishInflate() {
- super.onFinishInflate()
-
+ init {
+ inflate(context, R.layout.ongoing_privacy_chip, this)
+ id = R.id.privacy_chip
+ layoutParams = LayoutParams(WRAP_CONTENT, MATCH_PARENT, CENTER_VERTICAL or END)
+ clipChildren = true
+ clipToPadding = true
iconsContainer = requireViewById(R.id.icons_container)
-
updateResources()
}
@@ -107,6 +113,6 @@
val padding = context.resources
.getDimensionPixelSize(R.dimen.ongoing_appops_chip_side_padding)
iconsContainer.setPaddingRelative(padding, 0, padding, 0)
- iconsContainer.background = context.getDrawable(R.drawable.privacy_chip_bg)
+ iconsContainer.background = context.getDrawable(R.drawable.statusbar_privacy_chip_bg)
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt
index be93550..c70cce9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt
@@ -183,7 +183,7 @@
}
fun getRestoredTilePosition(tile: String): Int =
- restoredTiles?.get(tile)?.index ?: QSTileHost.POSITION_AT_END
+ restoredTiles?.get(tile)?.index ?: QSHost.POSITION_AT_END
/**
* Returns `true` if the tile has been auto-added before
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index 979884c..a7aac5a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -135,7 +135,7 @@
private int mNumQuickTiles;
private int mLastQQSTileHeight;
private float mLastPosition;
- private final QSTileHost mHost;
+ private final QSHost mHost;
private final Executor mExecutor;
private boolean mShowCollapsedOnKeyguard;
private int mQQSTop;
@@ -146,7 +146,7 @@
@Inject
public QSAnimator(QS qs, QuickQSPanel quickPanel, QuickStatusBarHeader quickStatusBarHeader,
QSPanelController qsPanelController,
- QuickQSPanelController quickQSPanelController, QSTileHost qsTileHost,
+ QuickQSPanelController quickQSPanelController, QSHost qsTileHost,
@Main Executor executor, TunerService tunerService,
QSExpansionPathInterpolator qsExpansionPathInterpolator) {
mQs = qs;
@@ -485,7 +485,7 @@
if (specs.isEmpty()) {
// specs should not be empty in a valid secondary page, as we scrolled to it.
// We may crash later on because there's a null animator.
- specs = mQsPanelController.getHost().mTileSpecs;
+ specs = mHost.getSpecs();
Log.e(TAG, "Trying to create animators for empty page " + page + ". Tiles: " + specs);
// return null;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
index 1da30ad..a71e6dd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
@@ -14,15 +14,48 @@
package com.android.systemui.qs;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.res.Resources;
+import android.os.Build;
+import android.provider.Settings;
import com.android.internal.logging.InstanceId;
import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.R;
+import com.android.systemui.plugins.qs.QSFactory;
import com.android.systemui.plugins.qs.QSTile;
+import com.android.systemui.plugins.qs.QSTileView;
+import com.android.systemui.util.leak.GarbageMonitor;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.List;
public interface QSHost {
+ String TILES_SETTING = Settings.Secure.QS_TILES;
+ int POSITION_AT_END = -1;
+
+ /**
+ * Returns the default QS tiles for the context.
+ * @param context the context to obtain the resources from
+ * @return a list of specs of the default tiles
+ */
+ static List<String> getDefaultSpecs(Context context) {
+ final ArrayList<String> tiles = new ArrayList();
+
+ final Resources res = context.getResources();
+ final String defaultTileList = res.getString(R.string.quick_settings_tiles_default);
+
+ tiles.addAll(Arrays.asList(defaultTileList.split(",")));
+ if (Build.IS_DEBUGGABLE
+ && GarbageMonitor.ADD_MEMORY_TILE_TO_DEFAULT_ON_DEBUGGABLE_BUILDS) {
+ tiles.add(GarbageMonitor.MemoryTile.TILE_SPEC);
+ }
+ return tiles;
+ }
+
void warn(String message, Throwable t);
void collapsePanels();
void forceCollapsePanels();
@@ -37,6 +70,44 @@
void removeTile(String tileSpec);
void removeTiles(Collection<String> specs);
+ List<String> getSpecs();
+ /**
+ * Create a view for a tile, iterating over all possible {@link QSFactory}.
+ *
+ * @see QSFactory#createTileView
+ */
+ QSTileView createTileView(Context themedContext, QSTile tile, boolean collapsedView);
+ /** Create a {@link QSTile} of a {@code tileSpec} type. */
+ QSTile createTile(String tileSpec);
+
+ /**
+ * Add a tile to the end
+ *
+ * @param spec string matching a pre-defined tilespec
+ */
+ void addTile(String spec);
+
+ /**
+ * Add a tile into the requested spot, or at the end if the position is greater than the number
+ * of tiles.
+ * @param spec string matching a pre-defined tilespec
+ * @param requestPosition -1 for end, 0 for beginning, or X for insertion at position X
+ */
+ void addTile(String spec, int requestPosition);
+ void addTile(ComponentName tile);
+
+ /**
+ * Adds a custom tile to the set of current tiles.
+ * @param tile the component name of the {@link android.service.quicksettings.TileService}
+ * @param end if true, the tile will be added at the end. If false, at the beginning.
+ */
+ void addTile(ComponentName tile, boolean end);
+ void removeTileByUser(ComponentName tile);
+ void changeTilesByUser(List<String> previousTiles, List<String> newTiles);
+
+ boolean isTileAdded(ComponentName componentName, int userId);
+ void setTileAdded(ComponentName componentName, int userId, boolean added);
+
int indexOf(String tileSpec);
InstanceId getNewInstanceId();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index a4f0bdf..b476521 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -79,7 +79,6 @@
protected boolean mExpanded;
protected boolean mListening;
- @Nullable protected QSTileHost mHost;
private final List<OnConfigurationChangedListener> mOnConfigurationChangedListeners =
new ArrayList<>();
@@ -359,11 +358,6 @@
}
}
- @Nullable
- public QSTileHost getHost() {
- return mHost;
- }
-
public void updateResources() {
updatePadding();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index b36d0fa..83b373d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -71,7 +71,7 @@
@Inject
QSPanelController(QSPanel view, TunerService tunerService,
- QSTileHost qstileHost, QSCustomizerController qsCustomizerController,
+ QSHost qsHost, QSCustomizerController qsCustomizerController,
@Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer,
@Named(QS_PANEL) MediaHost mediaHost,
QSTileRevealController.Factory qsTileRevealControllerFactory,
@@ -80,7 +80,7 @@
BrightnessSliderController.Factory brightnessSliderFactory,
FalsingManager falsingManager,
StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
- super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost,
+ super(view, qsHost, qsCustomizerController, usingMediaPlayer, mediaHost,
metricsLogger, uiEventLogger, qsLogger, dumpManager);
mTunerService = tunerService;
mQsCustomizerController = qsCustomizerController;
@@ -172,12 +172,6 @@
mBrightnessMirrorHandler.setController(brightnessMirrorController);
}
- /** Get the QSTileHost this panel uses. */
- public QSTileHost getHost() {
- return mHost;
- }
-
-
/** Update appearance of QSPanel. */
public void updateResources() {
mView.updateResources();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index bbdf6cc..2668d2e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -64,7 +64,7 @@
public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewController<T>
implements Dumpable{
private static final String TAG = "QSPanelControllerBase";
- protected final QSTileHost mHost;
+ protected final QSHost mHost;
private final QSCustomizerController mQsCustomizerController;
private final boolean mUsingMediaPlayer;
protected final MediaHost mMediaHost;
@@ -128,7 +128,7 @@
protected QSPanelControllerBase(
T view,
- QSTileHost host,
+ QSHost host,
QSCustomizerController qsCustomizerController,
@Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer,
MediaHost mediaHost,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 98af9df..0ead979 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -18,7 +18,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
-import android.os.Build;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings.Secure;
@@ -56,17 +55,14 @@
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.AutoTileManager;
import com.android.systemui.statusbar.phone.CentralSurfaces;
-import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
-import com.android.systemui.util.leak.GarbageMonitor;
import com.android.systemui.util.settings.SecureSettings;
import org.jetbrains.annotations.NotNull;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
@@ -94,16 +90,13 @@
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final int MAX_QS_INSTANCE_ID = 1 << 20;
- public static final int POSITION_AT_END = -1;
- public static final String TILES_SETTING = Secure.QS_TILES;
-
// Shared prefs that hold tile lifecycle info.
@VisibleForTesting
static final String TILES = "tiles_prefs";
private final Context mContext;
private final LinkedHashMap<String, QSTile> mTiles = new LinkedHashMap<>();
- protected final ArrayList<String> mTileSpecs = new ArrayList<>();
+ private final ArrayList<String> mTileSpecs = new ArrayList<>();
private final TunerService mTunerService;
private final PluginManager mPluginManager;
private final DumpManager mDumpManager;
@@ -117,7 +110,6 @@
private final List<Callback> mCallbacks = new ArrayList<>();
@Nullable
private AutoTileManager mAutoTiles;
- private final StatusBarIconController mIconController;
private final ArrayList<QSFactory> mQsFactories = new ArrayList<>();
private int mCurrentUser;
private final Optional<CentralSurfaces> mCentralSurfacesOptional;
@@ -135,7 +127,6 @@
@Inject
public QSTileHost(Context context,
- StatusBarIconController iconController,
QSFactory defaultFactory,
@Main Executor mainExecutor,
PluginManager pluginManager,
@@ -152,7 +143,6 @@
TileLifecycleManager.Factory tileLifecycleManagerFactory,
UserFileManager userFileManager
) {
- mIconController = iconController;
mContext = context;
mUserContext = context;
mTunerService = tunerService;
@@ -186,10 +176,6 @@
});
}
- public StatusBarIconController getIconController() {
- return mIconController;
- }
-
@Override
public InstanceId getNewInstanceId() {
return mInstanceIdSequence.newInstanceId();
@@ -438,12 +424,7 @@
addTile(spec, POSITION_AT_END);
}
- /**
- * Add a tile into the requested spot, or at the end if the position is greater than the number
- * of tiles.
- * @param spec string matching a pre-defined tilespec
- * @param requestPosition -1 for end, 0 for beginning, or X for insertion at position X
- */
+ @Override
public void addTile(String spec, int requestPosition) {
mMainExecutor.execute(() ->
changeTileSpecs(tileSpecs -> {
@@ -483,15 +464,12 @@
}
}
+ @Override
public void addTile(ComponentName tile) {
addTile(tile, /* end */ false);
}
- /**
- * Adds a custom tile to the set of current tiles.
- * @param tile the component name of the {@link android.service.quicksettings.TileService}
- * @param end if true, the tile will be added at the end. If false, at the beginning.
- */
+ @Override
public void addTile(ComponentName tile, boolean end) {
String spec = CustomTile.toSpec(tile);
addTile(spec, end ? POSITION_AT_END : 0);
@@ -501,6 +479,7 @@
* This will call through {@link #changeTilesByUser}. It should only be used when a tile is
* removed by a <b>user action</b> like {@code adb}.
*/
+ @Override
public void removeTileByUser(ComponentName tile) {
mMainExecutor.execute(() -> {
List<String> newSpecs = new ArrayList<>(mTileSpecs);
@@ -519,6 +498,7 @@
* that are removed.
*/
@MainThread
+ @Override
public void changeTilesByUser(List<String> previousTiles, List<String> newTiles) {
final List<String> copy = new ArrayList<>(previousTiles);
final int NP = copy.size();
@@ -542,8 +522,8 @@
saveTilesToSettings(newTiles);
}
- /** Create a {@link QSTile} of a {@code tileSpec} type. */
@Nullable
+ @Override
public QSTile createTile(String tileSpec) {
for (int i = 0; i < mQsFactories.size(); i++) {
QSTile t = mQsFactories.get(i).createTile(tileSpec);
@@ -554,11 +534,7 @@
return null;
}
- /**
- * Create a view for a tile, iterating over all possible {@link QSFactory}.
- *
- * @see QSFactory#createTileView
- */
+ @Override
public QSTileView createTileView(Context themedContext, QSTile tile, boolean collapsedView) {
for (int i = 0; i < mQsFactories.size(); i++) {
QSTileView view = mQsFactories.get(i)
@@ -578,6 +554,7 @@
* tile.
* @param userId the user to check
*/
+ @Override
public boolean isTileAdded(ComponentName componentName, int userId) {
return mUserFileManager
.getSharedPreferences(TILES, 0, userId)
@@ -593,6 +570,7 @@
* @param userId the user for this tile
* @param added {@code true} if the tile is being added, {@code false} otherwise
*/
+ @Override
public void setTileAdded(ComponentName componentName, int userId, boolean added) {
mUserFileManager.getSharedPreferences(TILES, 0, userId)
.edit()
@@ -600,6 +578,11 @@
.apply();
}
+ @Override
+ public List<String> getSpecs() {
+ return mTileSpecs;
+ }
+
protected static List<String> loadTileSpecs(Context context, String tileList) {
final Resources res = context.getResources();
@@ -617,7 +600,7 @@
if (tile.isEmpty()) continue;
if (tile.equals("default")) {
if (!addedDefault) {
- List<String> defaultSpecs = getDefaultSpecs(context);
+ List<String> defaultSpecs = QSHost.getDefaultSpecs(context);
for (String spec : defaultSpecs) {
if (!addedSpecs.contains(spec)) {
tiles.add(spec);
@@ -650,25 +633,6 @@
return tiles;
}
- /**
- * Returns the default QS tiles for the context.
- * @param context the context to obtain the resources from
- * @return a list of specs of the default tiles
- */
- public static List<String> getDefaultSpecs(Context context) {
- final ArrayList<String> tiles = new ArrayList<String>();
-
- final Resources res = context.getResources();
- final String defaultTileList = res.getString(R.string.quick_settings_tiles_default);
-
- tiles.addAll(Arrays.asList(defaultTileList.split(",")));
- if (Build.IS_DEBUGGABLE
- && GarbageMonitor.ADD_MEMORY_TILE_TO_DEFAULT_ON_DEBUGGABLE_BUILDS) {
- tiles.add(GarbageMonitor.MemoryTile.TILE_SPEC);
- }
- return tiles;
- }
-
@Override
public void dump(PrintWriter pw, String[] args) {
pw.println("QSTileHost:");
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index 6aabe3b..2d54313 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -48,7 +48,7 @@
private final Provider<Boolean> mUsingCollapsedLandscapeMediaProvider;
@Inject
- QuickQSPanelController(QuickQSPanel view, QSTileHost qsTileHost,
+ QuickQSPanelController(QuickQSPanel view, QSHost qsHost,
QSCustomizerController qsCustomizerController,
@Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer,
@Named(QUICK_QS_PANEL) MediaHost mediaHost,
@@ -57,7 +57,7 @@
MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
DumpManager dumpManager
) {
- super(view, qsTileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger,
+ super(view, qsHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger,
uiEventLogger, qsLogger, dumpManager);
mUsingCollapsedLandscapeMediaProvider = usingCollapsedLandscapeMediaProvider;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
index 9739011..a319fb8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
@@ -40,7 +40,7 @@
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.QSEditEvent;
import com.android.systemui.qs.QSFragment;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -57,7 +57,7 @@
@QSScope
public class QSCustomizerController extends ViewController<QSCustomizer> {
private final TileQueryHelper mTileQueryHelper;
- private final QSTileHost mQsTileHost;
+ private final QSHost mQsHost;
private final TileAdapter mTileAdapter;
private final ScreenLifecycle mScreenLifecycle;
private final KeyguardStateController mKeyguardStateController;
@@ -104,12 +104,12 @@
@Inject
protected QSCustomizerController(QSCustomizer view, TileQueryHelper tileQueryHelper,
- QSTileHost qsTileHost, TileAdapter tileAdapter, ScreenLifecycle screenLifecycle,
+ QSHost qsHost, TileAdapter tileAdapter, ScreenLifecycle screenLifecycle,
KeyguardStateController keyguardStateController, LightBarController lightBarController,
ConfigurationController configurationController, UiEventLogger uiEventLogger) {
super(view);
mTileQueryHelper = tileQueryHelper;
- mQsTileHost = qsTileHost;
+ mQsHost = qsHost;
mTileAdapter = tileAdapter;
mScreenLifecycle = screenLifecycle;
mKeyguardStateController = keyguardStateController;
@@ -175,7 +175,7 @@
private void reset() {
- mTileAdapter.resetTileSpecs(QSTileHost.getDefaultSpecs(getContext()));
+ mTileAdapter.resetTileSpecs(QSHost.getDefaultSpecs(getContext()));
}
public boolean isCustomizing() {
@@ -192,7 +192,7 @@
mView.show(x, y, mTileAdapter);
mUiEventLogger.log(QSEditEvent.QS_EDIT_OPEN);
}
- mTileQueryHelper.queryTiles(mQsTileHost);
+ mTileQueryHelper.queryTiles(mQsHost);
mKeyguardStateController.addCallback(mKeyguardCallback);
mView.updateNavColors(mLightBarController);
}
@@ -258,13 +258,13 @@
private void save() {
if (mTileQueryHelper.isFinished()) {
- mTileAdapter.saveSpecs(mQsTileHost);
+ mTileAdapter.saveSpecs(mQsHost);
}
}
private void setTileSpecs() {
List<String> specs = new ArrayList<>();
- for (QSTile tile : mQsTileHost.getTiles()) {
+ for (QSTile tile : mQsHost.getTiles()) {
specs.add(tile.getTileSpec());
}
mTileAdapter.setTileSpecs(specs);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index d84b12c..6a05684 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -45,7 +45,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
import com.android.systemui.qs.QSEditEvent;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.customize.TileAdapter.Holder;
import com.android.systemui.qs.customize.TileQueryHelper.TileInfo;
import com.android.systemui.qs.customize.TileQueryHelper.TileStateListener;
@@ -91,7 +91,7 @@
private ItemDecoration mDecoration;
private final MarginTileDecoration mMarginDecoration;
private final int mMinNumTiles;
- private final QSTileHost mHost;
+ private final QSHost mHost;
private int mEditIndex;
private int mTileDividerIndex;
private int mFocusIndex;
@@ -117,7 +117,7 @@
@Inject
public TileAdapter(
@QSThemedContext Context context,
- QSTileHost qsHost,
+ QSHost qsHost,
UiEventLogger uiEventLogger) {
mContext = context;
mHost = qsHost;
@@ -176,7 +176,7 @@
mMarginDecoration.setHalfMargin(halfMargin);
}
- public void saveSpecs(QSTileHost host) {
+ public void saveSpecs(QSHost host) {
List<String> newSpecs = new ArrayList<>();
clearAccessibilityState();
for (int i = 1; i < mTiles.size() && mTiles.get(i) != null; i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
index 32a7916..d9f4484 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -38,7 +38,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTile.State;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon;
@@ -85,7 +85,7 @@
mListener = listener;
}
- public void queryTiles(QSTileHost host) {
+ public void queryTiles(QSHost host) {
mTiles.clear();
mSpecs.clear();
mFinished = false;
@@ -97,7 +97,7 @@
return mFinished;
}
- private void addCurrentAndStockTiles(QSTileHost host) {
+ private void addCurrentAndStockTiles(QSHost host) {
String stock = mContext.getString(R.string.quick_settings_tiles_stock);
String current = Settings.Secure.getString(mContext.getContentResolver(),
Settings.Secure.QS_TILES);
@@ -153,14 +153,14 @@
private class TileCollector implements QSTile.Callback {
private final List<TilePair> mQSTileList = new ArrayList<>();
- private final QSTileHost mQSTileHost;
+ private final QSHost mQSHost;
- TileCollector(List<QSTile> tilesToAdd, QSTileHost host) {
+ TileCollector(List<QSTile> tilesToAdd, QSHost host) {
for (QSTile tile: tilesToAdd) {
TilePair pair = new TilePair(tile);
mQSTileList.add(pair);
}
- mQSTileHost = host;
+ mQSHost = host;
if (tilesToAdd.isEmpty()) {
mBgExecutor.execute(this::finished);
}
@@ -168,7 +168,7 @@
private void finished() {
notifyTilesChanged(false);
- addPackageTiles(mQSTileHost);
+ addPackageTiles(mQSHost);
}
private void startListening() {
@@ -207,7 +207,7 @@
}
}
- private void addPackageTiles(final QSTileHost host) {
+ private void addPackageTiles(final QSHost host) {
mBgExecutor.execute(() -> {
Collection<QSTile> params = host.getTiles();
PackageManager pm = mContext.getPackageManager();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
index 27ae171..431d6e8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
@@ -67,7 +67,7 @@
static AutoTileManager provideAutoTileManager(
Context context,
AutoAddTracker.Builder autoAddTrackerBuilder,
- QSTileHost host,
+ QSHost host,
@Background Handler handler,
SecureSettings secureSettings,
HotspotController hotspotController,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
index 237b66e..d9e5580 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
@@ -25,13 +25,13 @@
import android.util.Log
import androidx.annotation.VisibleForTesting
import com.android.internal.statusbar.IAddTileResultCallback
+import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.qs.QSTileHost
+import com.android.systemui.qs.QSHost
+import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.statusbar.phone.SystemUIDialog
-import com.android.systemui.R
-import com.android.systemui.statusbar.CommandQueue
import java.io.PrintWriter
import java.util.concurrent.atomic.AtomicBoolean
import java.util.function.Consumer
@@ -40,14 +40,14 @@
private const val TAG = "TileServiceRequestController"
/**
- * Controller to interface between [TileRequestDialog] and [QSTileHost].
+ * Controller to interface between [TileRequestDialog] and [QSHost].
*/
class TileServiceRequestController constructor(
- private val qsTileHost: QSTileHost,
+ private val qsHost: QSHost,
private val commandQueue: CommandQueue,
private val commandRegistry: CommandRegistry,
private val eventLogger: TileRequestDialogEventLogger,
- private val dialogCreator: () -> TileRequestDialog = { TileRequestDialog(qsTileHost.context) }
+ private val dialogCreator: () -> TileRequestDialog = { TileRequestDialog(qsHost.context) }
) {
companion object {
@@ -93,7 +93,7 @@
}
private fun addTile(componentName: ComponentName) {
- qsTileHost.addTile(componentName, true)
+ qsHost.addTile(componentName, true)
}
@VisibleForTesting
@@ -158,7 +158,7 @@
private fun isTileAlreadyAdded(componentName: ComponentName): Boolean {
val spec = CustomTile.toSpec(componentName)
- return qsTileHost.indexOf(spec) != -1
+ return qsHost.indexOf(spec) != -1
}
inner class TileServiceRequestCommand : Command {
@@ -194,13 +194,13 @@
private val commandQueue: CommandQueue,
private val commandRegistry: CommandRegistry
) {
- fun create(qsTileHost: QSTileHost): TileServiceRequestController {
+ fun create(qsHost: QSHost): TileServiceRequestController {
return TileServiceRequestController(
- qsTileHost,
+ qsHost,
commandQueue,
commandRegistry,
TileRequestDialogEventLogger()
)
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
index 84a18d8..adc7165 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -39,7 +39,7 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -68,22 +68,24 @@
private final Context mContext;
private final Handler mMainHandler;
private final Provider<Handler> mHandlerProvider;
- private final QSTileHost mHost;
+ private final QSHost mHost;
private final KeyguardStateController mKeyguardStateController;
private final BroadcastDispatcher mBroadcastDispatcher;
private final CommandQueue mCommandQueue;
private final UserTracker mUserTracker;
+ private final StatusBarIconController mStatusBarIconController;
private int mMaxBound = DEFAULT_MAX_BOUND;
@Inject
public TileServices(
- QSTileHost host,
+ QSHost host,
@Main Provider<Handler> handlerProvider,
BroadcastDispatcher broadcastDispatcher,
UserTracker userTracker,
KeyguardStateController keyguardStateController,
- CommandQueue commandQueue) {
+ CommandQueue commandQueue,
+ StatusBarIconController statusBarIconController) {
mHost = host;
mKeyguardStateController = keyguardStateController;
mContext = mHost.getContext();
@@ -92,6 +94,7 @@
mMainHandler = mHandlerProvider.get();
mUserTracker = userTracker;
mCommandQueue = commandQueue;
+ mStatusBarIconController = statusBarIconController;
mCommandQueue.addCallback(mRequestListeningCallback);
}
@@ -99,7 +102,7 @@
return mContext;
}
- public QSTileHost getHost() {
+ public QSHost getHost() {
return mHost;
}
@@ -131,8 +134,7 @@
mTiles.remove(tile.getComponent());
final String slot = tile.getComponent().getClassName();
// TileServices doesn't know how to add more than 1 icon per slot, so remove all
- mMainHandler.post(() -> mHost.getIconController()
- .removeAllIconsForExternalSlot(slot));
+ mMainHandler.post(() -> mStatusBarIconController.removeAllIconsForSlot(slot));
}
}
@@ -309,7 +311,7 @@
mMainHandler.post(new Runnable() {
@Override
public void run() {
- StatusBarIconController iconController = mHost.getIconController();
+ StatusBarIconController iconController = mStatusBarIconController;
iconController.setIcon(componentName.getClassName(), statusIcon);
iconController.setExternalIcon(componentName.getClassName());
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
index 9b4ac1b..a915ddb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
@@ -18,6 +18,7 @@
import android.content.Intent
import android.os.Handler
import android.os.Looper
+import android.provider.Settings
import android.view.View
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.MetricsLogger
@@ -72,9 +73,7 @@
}
override fun newTileState(): QSTile.State {
- val state = QSTile.State()
- state.handlesLongClick = false
- return state
+ return QSTile.State()
}
override fun handleClick(view: View?) {
@@ -98,7 +97,7 @@
}
override fun getLongClickIntent(): Intent? {
- return null
+ return Intent(Settings.ACTION_TEXT_READING_SETTINGS)
}
override fun getTileLabel(): CharSequence {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index 50f3db3..099ad94 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -193,7 +193,12 @@
private boolean mAnimatingHiddenFromCollapsed;
private boolean mVisible;
private float mExpansionHeight;
+ /**
+ * QS height when QS expansion fraction is 0 so when QS is collapsed. That state doesn't really
+ * exist for split shade so currently this value is always 0 then.
+ */
private int mMinExpansionHeight;
+ /** QS height when QS expansion fraction is 1 so qs is fully expanded */
private int mMaxExpansionHeight;
/** Expansion fraction of the notification shade */
private float mShadeExpandedFraction;
@@ -697,6 +702,7 @@
/** update Qs height state */
public void setExpansionHeight(float height) {
+ checkCorrectSplitShadeState(height);
int maxHeight = getMaxExpansionHeight();
height = Math.min(Math.max(
height, getMinExpansionHeight()), maxHeight);
@@ -718,6 +724,14 @@
}
}
+ /** TODO(b/269742565) Remove this logging */
+ private void checkCorrectSplitShadeState(float height) {
+ if (mSplitShadeEnabled && height == 0
+ && mPanelViewControllerLazy.get().isShadeFullyOpen()) {
+ Log.wtfStack(TAG, "qsExpansion set to 0 while split shade is expanding or open");
+ }
+ }
+
/** */
public void setHeightOverrideToDesiredHeight() {
if (isSizeChangeAnimationRunning() && isQsFragmentCreated()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
index 84b40e0..77d98d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
@@ -234,7 +234,7 @@
/**
* @param key
- * @return true if the entry is pinned
+ * @return true if the entry is (pinned and expanded) or (has an active remote input)
*/
public boolean isSticky(String key) {
AlertEntry alerting = mAlertEntries.get(key);
@@ -256,15 +256,6 @@
return 0;
}
- @VisibleForTesting
- public long getCalculatedEarliestRemovalTime(String key) {
- AlertEntry alerting = mAlertEntries.get(key);
- if (alerting != null) {
- return alerting.mEarliestRemovaltime;
- }
- return 0;
- }
-
protected class AlertEntry implements Comparable<AlertEntry> {
@Nullable public NotificationEntry mEntry;
public long mPostTime;
@@ -285,11 +276,6 @@
updateEntry(true /* updatePostTime */);
}
- @VisibleForTesting
- long getEarliestRemovaltime() {
- return mEarliestRemovaltime;
- }
-
/**
* Updates an entry's removal time.
* @param updatePostTime whether or not to refresh the post time
@@ -305,23 +291,26 @@
}
removeAutoRemovalCallbacks();
- final long finishTime = calculateFinishTime();
- final long timeRemaining = isSticky()
- ? finishTime - mClock.currentTimeMillis()
- : Math.max(finishTime - now, mMinimumDisplayTime);
-
- mHandler.postDelayed(mRemoveAlertRunnable, timeRemaining);
+ if (!isSticky()) {
+ final long finishTime = calculateFinishTime();
+ final long timeLeft = Math.max(finishTime - now, mMinimumDisplayTime);
+ mHandler.postDelayed(mRemoveAlertRunnable, timeLeft);
+ }
}
/**
* Whether or not the notification is "sticky" i.e. should stay on screen regardless
- * of the timer and should be removed externally.
+ * of the timer (forever) and should be removed externally.
* @return true if the notification is sticky
*/
public boolean isSticky() {
// This implementation is overridden by HeadsUpManager HeadsUpEntry #isSticky
- // but we keep this here for use by unit tests.
- return mEntry.isStickyAndNotDemoted();
+ return false;
+ }
+
+ public boolean isStickyForSomeTime() {
+ // This implementation is overridden by HeadsUpManager HeadsUpEntry #isStickyForSomeTime
+ return false;
}
/**
@@ -360,8 +349,9 @@
public void removeAsSoonAsPossible() {
if (mRemoveAlertRunnable != null) {
removeAutoRemovalCallbacks();
- mHandler.postDelayed(mRemoveAlertRunnable,
- mEarliestRemovaltime - mClock.currentTimeMillis());
+
+ final long timeLeft = mEarliestRemovaltime - mClock.currentTimeMillis();
+ mHandler.postDelayed(mRemoveAlertRunnable, timeLeft);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt
new file mode 100644
index 0000000..37140ec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt
@@ -0,0 +1,73 @@
+/*
+ * 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 android.annotation.IntRange
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.res.Configuration
+import android.util.AttributeSet
+import android.view.View
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import com.android.settingslib.Utils
+import com.android.systemui.R
+import com.android.systemui.battery.BatteryMeterView
+import com.android.systemui.statusbar.events.BackgroundAnimatableView
+
+class BatteryStatusChip @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
+ FrameLayout(context, attrs), BackgroundAnimatableView {
+
+ private val roundedContainer: LinearLayout
+ private val batteryMeterView: BatteryMeterView
+ override val contentView: View
+ get() = batteryMeterView
+
+ init {
+ inflate(context, R.layout.battery_status_chip, this)
+ roundedContainer = findViewById(R.id.rounded_container)
+ batteryMeterView = findViewById(R.id.battery_meter_view)
+ updateResources()
+ }
+
+ /**
+ * When animating as a chip in the status bar, we want to animate the width for the rounded
+ * container. We have to subtract our own top and left offset because the bounds come to us as
+ * absolute on-screen bounds.
+ */
+ override fun setBoundsForAnimation(l: Int, t: Int, r: Int, b: Int) {
+ roundedContainer.setLeftTopRightBottom(l - left, t - top, r - left, b - top)
+ }
+
+ fun setBatteryLevel(@IntRange(from = 0, to = 100) batteryLevel: Int) {
+ batteryMeterView.setForceShowPercent(true)
+ batteryMeterView.onBatteryLevelChanged(batteryLevel, true)
+ }
+
+ override fun onConfigurationChanged(newConfig: Configuration) {
+ super.onConfigurationChanged(newConfig)
+ updateResources()
+ }
+
+ @SuppressLint("UseCompatLoadingForDrawables")
+ private fun updateResources() {
+ val primaryColor =
+ Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorPrimary)
+ val textColorSecondary =
+ Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorSecondary)
+ batteryMeterView.updateColors(primaryColor, textColorSecondary, primaryColor)
+ roundedContainer.background = mContext.getDrawable(R.drawable.statusbar_chip_bg)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 2cf1f53..c435799 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -168,6 +168,7 @@
private static final int MSG_GO_TO_FULLSCREEN_FROM_SPLIT = 70 << MSG_SHIFT;
private static final int MSG_ENTER_STAGE_SPLIT_FROM_RUNNING_APP = 71 << MSG_SHIFT;
private static final int MSG_SHOW_MEDIA_OUTPUT_SWITCHER = 72 << MSG_SHIFT;
+ private static final int MSG_TOGGLE_TASKBAR = 73 << MSG_SHIFT;
public static final int FLAG_EXCLUDE_NONE = 0;
public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -229,6 +230,7 @@
@BackDispositionMode int backDisposition, boolean showImeSwitcher) { }
default void showRecentApps(boolean triggeredFromAltTab) { }
default void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) { }
+ default void toggleTaskbar() { }
default void toggleRecentApps() { }
default void toggleSplitScreen() { }
default void preloadRecentApps() { }
@@ -715,6 +717,13 @@
}
}
+ public void toggleTaskbar() {
+ synchronized (mLock) {
+ mHandler.removeMessages(MSG_TOGGLE_TASKBAR);
+ mHandler.obtainMessage(MSG_TOGGLE_TASKBAR, 0, 0, null).sendToTarget();
+ }
+ }
+
public void toggleRecentApps() {
synchronized (mLock) {
mHandler.removeMessages(MSG_TOGGLE_RECENT_APPS);
@@ -1416,6 +1425,11 @@
mCallbacks.get(i).hideRecentApps(msg.arg1 != 0, msg.arg2 != 0);
}
break;
+ case MSG_TOGGLE_TASKBAR:
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).toggleTaskbar();
+ }
+ break;
case MSG_TOGGLE_RECENT_APPS:
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).toggleRecentApps();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index f4cd985..51c5183 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -213,7 +213,8 @@
DeviceProvisionedController deviceProvisionedController,
KeyguardStateController keyguardStateController,
SecureSettings secureSettings,
- DumpManager dumpManager) {
+ DumpManager dumpManager,
+ LockPatternUtils lockPatternUtils) {
mContext = context;
mMainHandler = mainHandler;
mDevicePolicyManager = devicePolicyManager;
@@ -225,7 +226,7 @@
mClickNotifier = clickNotifier;
mOverviewProxyServiceLazy = overviewProxyServiceLazy;
statusBarStateController.addCallback(this);
- mLockPatternUtils = new LockPatternUtils(context);
+ mLockPatternUtils = lockPatternUtils;
mKeyguardManager = keyguardManager;
mBroadcastDispatcher = broadcastDispatcher;
mDeviceProvisionedController = deviceProvisionedController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
index bd5b8f0..bfc4e9c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.events
-import android.animation.Animator
+import androidx.core.animation.Animator
import android.annotation.UiThread
import android.graphics.Point
import android.graphics.Rect
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusBarEventsModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusBarEventsModule.kt
new file mode 100644
index 0000000..3d6d489
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusBarEventsModule.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.events
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.time.SystemClock
+import dagger.Module
+import dagger.Provides
+import kotlinx.coroutines.CoroutineScope
+
+@Module
+interface StatusBarEventsModule {
+
+ companion object {
+
+ @Provides
+ @SysUISingleton
+ fun provideSystemStatusAnimationScheduler(
+ featureFlags: FeatureFlags,
+ coordinator: SystemEventCoordinator,
+ chipAnimationController: SystemEventChipAnimationController,
+ statusBarWindowController: StatusBarWindowController,
+ dumpManager: DumpManager,
+ systemClock: SystemClock,
+ @Application coroutineScope: CoroutineScope,
+ @Main executor: DelayableExecutor
+ ): SystemStatusAnimationScheduler {
+ return if (featureFlags.isEnabled(Flags.PLUG_IN_STATUS_BAR_CHIP)) {
+ SystemStatusAnimationSchedulerImpl(
+ coordinator,
+ chipAnimationController,
+ statusBarWindowController,
+ dumpManager,
+ systemClock,
+ coroutineScope
+ )
+ } else {
+ SystemStatusAnimationSchedulerLegacyImpl(
+ coordinator,
+ chipAnimationController,
+ statusBarWindowController,
+ dumpManager,
+ systemClock,
+ executor
+ )
+ }
+ }
+ }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
index 4e1404d..43f78c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
@@ -16,24 +16,21 @@
package com.android.systemui.statusbar.events
+import android.annotation.IntRange
import android.annotation.SuppressLint
import android.content.Context
-import android.graphics.Color
-import android.graphics.drawable.ColorDrawable
-import android.view.LayoutInflater
import android.view.View
import android.widget.ImageView
-import com.android.settingslib.graph.ThemedBatteryDrawable
-import com.android.systemui.R
import com.android.systemui.privacy.OngoingPrivacyChip
import com.android.systemui.privacy.PrivacyItem
+import com.android.systemui.statusbar.BatteryStatusChip
typealias ViewCreator = (context: Context) -> BackgroundAnimatableView
interface StatusEvent {
val priority: Int
// Whether or not to force the status bar open and show a dot
- val forceVisible: Boolean
+ var forceVisible: Boolean
// Whether or not to show an animation for this event
val showAnimation: Boolean
val viewCreator: ViewCreator
@@ -73,17 +70,16 @@
}
}
-class BatteryEvent : StatusEvent {
+class BatteryEvent(@IntRange(from = 0, to = 100) val batteryLevel: Int) : StatusEvent {
override val priority = 50
- override val forceVisible = false
+ override var forceVisible = false
override val showAnimation = true
override var contentDescription: String? = ""
- override val viewCreator: (context: Context) -> BGImageView = { context ->
- val iv = BGImageView(context)
- iv.setImageDrawable(ThemedBatteryDrawable(context, Color.WHITE))
- iv.setBackgroundDrawable(ColorDrawable(Color.GREEN))
- iv
+ override val viewCreator: ViewCreator = { context ->
+ BatteryStatusChip(context).apply {
+ setBatteryLevel(batteryLevel)
+ }
}
override fun toString(): String {
@@ -94,13 +90,12 @@
class PrivacyEvent(override val showAnimation: Boolean = true) : StatusEvent {
override var contentDescription: String? = null
override val priority = 100
- override val forceVisible = true
+ override var forceVisible = true
var privacyItems: List<PrivacyItem> = listOf()
private var privacyChip: OngoingPrivacyChip? = null
override val viewCreator: ViewCreator = { context ->
- val v = LayoutInflater.from(context)
- .inflate(R.layout.ongoing_privacy_chip, null) as OngoingPrivacyChip
+ val v = OngoingPrivacyChip(context)
v.privacyList = privacyItems
v.contentDescription = contentDescription
privacyChip = v
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
index 8405aea..776956a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
@@ -16,10 +16,6 @@
package com.android.systemui.statusbar.events
-import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
-import android.animation.AnimatorSet
-import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Rect
import android.view.ContextThemeWrapper
@@ -30,7 +26,13 @@
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.widget.FrameLayout
+import androidx.core.animation.Animator
+import androidx.core.animation.AnimatorListenerAdapter
+import androidx.core.animation.AnimatorSet
+import androidx.core.animation.ValueAnimator
import com.android.systemui.R
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.systemui.util.animation.AnimationUtil.Companion.frames
@@ -43,7 +45,8 @@
class SystemEventChipAnimationController @Inject constructor(
private val context: Context,
private val statusBarWindowController: StatusBarWindowController,
- private val contentInsetsProvider: StatusBarContentInsetsProvider
+ private val contentInsetsProvider: StatusBarContentInsetsProvider,
+ private val featureFlags: FeatureFlags
) : SystemStatusAnimationCallback {
private lateinit var animationWindowView: FrameLayout
@@ -53,12 +56,14 @@
// Left for LTR, Right for RTL
private var animationDirection = LEFT
- private var chipRight = 0
- private var chipLeft = 0
- private var chipWidth = 0
+ private var chipBounds = Rect()
+ private val chipWidth get() = chipBounds.width()
+ private val chipRight get() = chipBounds.right
+ private val chipLeft get() = chipBounds.left
private var chipMinWidth = context.resources.getDimensionPixelSize(
R.dimen.ongoing_appops_chip_min_animation_width)
- private var dotSize = context.resources.getDimensionPixelSize(
+
+ private val dotSize = context.resources.getDimensionPixelSize(
R.dimen.ongoing_appops_dot_diameter)
// Use during animation so that multiple animators can update the drawing rect
private var animRect = Rect()
@@ -90,21 +95,26 @@
it.view.measure(
View.MeasureSpec.makeMeasureSpec(
(animationWindowView.parent as View).width, AT_MOST),
- View.MeasureSpec.makeMeasureSpec(animationWindowView.height, AT_MOST))
- chipWidth = it.chipWidth
- }
+ View.MeasureSpec.makeMeasureSpec(
+ (animationWindowView.parent as View).height, AT_MOST))
- // decide which direction we're animating from, and then set some screen coordinates
- val contentRect = contentInsetsProvider.getStatusBarContentAreaForCurrentRotation()
- when (animationDirection) {
- LEFT -> {
- chipRight = contentRect.right
- chipLeft = contentRect.right - chipWidth
+ // decide which direction we're animating from, and then set some screen coordinates
+ val contentRect = contentInsetsProvider.getStatusBarContentAreaForCurrentRotation()
+ val chipTop = ((animationWindowView.parent as View).height - it.view.measuredHeight) / 2
+ val chipBottom = chipTop + it.view.measuredHeight
+ val chipRight: Int
+ val chipLeft: Int
+ when (animationDirection) {
+ LEFT -> {
+ chipRight = contentRect.right
+ chipLeft = contentRect.right - it.chipWidth
+ }
+ else /* RIGHT */ -> {
+ chipLeft = contentRect.left
+ chipRight = contentRect.left + it.chipWidth
+ }
}
- else /* RIGHT */ -> {
- chipLeft = contentRect.left
- chipRight = contentRect.left + chipWidth
- }
+ chipBounds = Rect(chipLeft, chipTop, chipRight, chipBottom)
}
}
@@ -117,16 +127,21 @@
interpolator = null
addUpdateListener { currentAnimatedView?.view?.alpha = animatedValue as Float }
}
+ currentAnimatedView?.contentView?.alpha = 0f
+ val contentAlphaIn = ValueAnimator.ofFloat(0f, 1f).apply {
+ startDelay = 10.frames
+ duration = 10.frames
+ interpolator = null
+ addUpdateListener { currentAnimatedView?.contentView?.alpha = animatedValue as Float }
+ }
val moveIn = ValueAnimator.ofInt(chipMinWidth, chipWidth).apply {
startDelay = 7.frames
duration = 23.frames
interpolator = STATUS_BAR_X_MOVE_IN
- addUpdateListener {
- updateAnimatedViewBoundsWidth(animatedValue as Int)
- }
+ addUpdateListener { updateAnimatedViewBoundsWidth(animatedValue as Int) }
}
val animSet = AnimatorSet()
- animSet.playTogether(alphaIn, moveIn)
+ animSet.playTogether(alphaIn, contentAlphaIn, moveIn)
return animSet
}
@@ -139,7 +154,7 @@
}
finish.addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
animationWindowView.removeView(currentAnimatedView!!.view)
}
})
@@ -152,7 +167,7 @@
duration = 9.frames
interpolator = STATUS_CHIP_WIDTH_TO_DOT_KEYFRAME_1
addUpdateListener {
- updateAnimatedViewBoundsWidth(it.animatedValue as Int)
+ updateAnimatedViewBoundsWidth(animatedValue as Int)
}
}
@@ -161,7 +176,7 @@
duration = 20.frames
interpolator = STATUS_CHIP_WIDTH_TO_DOT_KEYFRAME_2
addUpdateListener {
- updateAnimatedViewBoundsWidth(it.animatedValue as Int)
+ updateAnimatedViewBoundsWidth(animatedValue as Int)
}
}
@@ -174,7 +189,7 @@
duration = 6.frames
interpolator = STATUS_CHIP_HEIGHT_TO_DOT_KEYFRAME_1
addUpdateListener {
- updateAnimatedViewBoundsHeight(it.animatedValue as Int, chipVerticalCenter)
+ updateAnimatedViewBoundsHeight(animatedValue as Int, chipVerticalCenter)
}
}
@@ -183,7 +198,7 @@
duration = 15.frames
interpolator = STATUS_CHIP_HEIGHT_TO_DOT_KEYFRAME_2
addUpdateListener {
- updateAnimatedViewBoundsHeight(it.animatedValue as Int, chipVerticalCenter)
+ updateAnimatedViewBoundsHeight(animatedValue as Int, chipVerticalCenter)
}
}
@@ -210,15 +225,32 @@
}
private fun createMoveOutAnimationDefault(): Animator {
+ val alphaOut = ValueAnimator.ofFloat(1f, 0f).apply {
+ startDelay = 6.frames
+ duration = 6.frames
+ interpolator = null
+ addUpdateListener { currentAnimatedView?.view?.alpha = animatedValue as Float }
+ }
+
+ val contentAlphaOut = ValueAnimator.ofFloat(1f, 0f).apply {
+ duration = 5.frames
+ interpolator = null
+ addUpdateListener { currentAnimatedView?.contentView?.alpha = animatedValue as Float }
+ }
+
val moveOut = ValueAnimator.ofInt(chipWidth, chipMinWidth).apply {
duration = 23.frames
+ interpolator = STATUS_BAR_X_MOVE_OUT
addUpdateListener {
currentAnimatedView?.apply {
- updateAnimatedViewBoundsWidth(it.animatedValue as Int)
+ updateAnimatedViewBoundsWidth(animatedValue as Int)
}
}
}
- return moveOut
+
+ val animSet = AnimatorSet()
+ animSet.playTogether(alphaOut, contentAlphaOut, moveOut)
+ return animSet
}
private fun init() {
@@ -239,11 +271,15 @@
it.marginEnd = marginEnd
}
- private fun initializeAnimRect() = animRect.set(
- chipLeft,
- currentAnimatedView!!.view.top,
- chipRight,
- currentAnimatedView!!.view.bottom)
+ private fun initializeAnimRect() = if (featureFlags.isEnabled(Flags.PLUG_IN_STATUS_BAR_CHIP)) {
+ animRect.set(chipBounds)
+ } else {
+ animRect.set(
+ chipLeft,
+ currentAnimatedView!!.view.top,
+ chipRight,
+ currentAnimatedView!!.view.bottom)
+ }
/**
* To be called during an animation, sets the width and updates the current animated chip view
@@ -296,6 +332,8 @@
interface BackgroundAnimatableView {
val view: View // Since this can't extend View, add a view prop
get() = this as View
+ val contentView: View? // This will be alpha faded during appear and disappear animation
+ get() = null
val chipWidth: Int
get() = view.measuredWidth
fun setBoundsForAnimation(l: Int, t: Int, r: Int, b: Int)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
index fde5d39..26fd230 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
@@ -16,11 +16,14 @@
package com.android.systemui.statusbar.events
+import android.annotation.IntRange
import android.content.Context
import android.provider.DeviceConfig
import android.provider.DeviceConfig.NAMESPACE_PRIVACY
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.privacy.PrivacyChipBuilder
import com.android.systemui.privacy.PrivacyItem
import com.android.systemui.privacy.PrivacyItemController
@@ -37,21 +40,18 @@
private val systemClock: SystemClock,
private val batteryController: BatteryController,
private val privacyController: PrivacyItemController,
- private val context: Context
+ private val context: Context,
+ private val featureFlags: FeatureFlags
) {
private lateinit var scheduler: SystemStatusAnimationScheduler
fun startObserving() {
- /* currently unused
batteryController.addCallback(batteryStateListener)
- */
privacyController.addCallback(privacyStateListener)
}
fun stopObserving() {
- /* currently unused
batteryController.removeCallback(batteryStateListener)
- */
privacyController.removeCallback(privacyStateListener)
}
@@ -59,12 +59,14 @@
this.scheduler = s
}
- fun notifyPluggedIn() {
- scheduler.onStatusEvent(BatteryEvent())
+ fun notifyPluggedIn(@IntRange(from = 0, to = 100) batteryLevel: Int) {
+ if (featureFlags.isEnabled(Flags.PLUG_IN_STATUS_BAR_CHIP)) {
+ scheduler.onStatusEvent(BatteryEvent(batteryLevel))
+ }
}
fun notifyPrivacyItemsEmpty() {
- scheduler.setShouldShowPersistentPrivacyIndicator(false)
+ scheduler.removePersistentDot()
}
fun notifyPrivacyItemsChanged(showAnimation: Boolean = true) {
@@ -79,25 +81,25 @@
}
private val batteryStateListener = object : BatteryController.BatteryStateChangeCallback {
- var plugged = false
- var stateKnown = false
+ private var plugged = false
+ private var stateKnown = false
override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
if (!stateKnown) {
stateKnown = true
plugged = pluggedIn
- notifyListeners()
+ notifyListeners(level)
return
}
if (plugged != pluggedIn) {
plugged = pluggedIn
- notifyListeners()
+ notifyListeners(level)
}
}
- private fun notifyListeners() {
+ private fun notifyListeners(@IntRange(from = 0, to = 100) batteryLevel: Int) {
// We only care about the plugged in status
- if (plugged) notifyPluggedIn()
+ if (plugged) notifyPluggedIn(batteryLevel)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
index 197cf56..2a18f1f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * 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.
@@ -16,302 +16,21 @@
package com.android.systemui.statusbar.events
-import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
-import android.animation.AnimatorSet
import android.annotation.IntDef
-import android.os.Process
-import android.provider.DeviceConfig
-import android.util.Log
-import android.view.animation.PathInterpolator
+import androidx.core.animation.Animator
+import androidx.core.animation.AnimatorSet
+import androidx.core.animation.PathInterpolator
import com.android.systemui.Dumpable
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.policy.CallbackController
-import com.android.systemui.statusbar.window.StatusBarWindowController
-import com.android.systemui.util.Assert
-import com.android.systemui.util.concurrency.DelayableExecutor
-import com.android.systemui.util.time.SystemClock
-import java.io.PrintWriter
-import javax.inject.Inject
-/**
- * Dead-simple scheduler for system status events. Obeys the following principles (all values TBD):
- * - Avoiding log spam by only allowing 12 events per minute (1event/5s)
- * - Waits 100ms to schedule any event for debouncing/prioritization
- * - Simple prioritization: Privacy > Battery > connectivity (encoded in [StatusEvent])
- * - Only schedules a single event, and throws away lowest priority events
- *
- * There are 4 basic stages of animation at play here:
- * 1. System chrome animation OUT
- * 2. Chip animation IN
- * 3. Chip animation OUT; potentially into a dot
- * 4. System chrome animation IN
- *
- * Thus we can keep all animations synchronized with two separate ValueAnimators, one for system
- * chrome and the other for the chip. These can animate from 0,1 and listeners can parameterize
- * their respective views based on the progress of the animator. Interpolation differences TBD
- */
-@SysUISingleton
-open class SystemStatusAnimationScheduler @Inject constructor(
- private val coordinator: SystemEventCoordinator,
- private val chipAnimationController: SystemEventChipAnimationController,
- private val statusBarWindowController: StatusBarWindowController,
- private val dumpManager: DumpManager,
- private val systemClock: SystemClock,
- @Main private val executor: DelayableExecutor
-) : CallbackController<SystemStatusAnimationCallback>, Dumpable {
+interface SystemStatusAnimationScheduler :
+ CallbackController<SystemStatusAnimationCallback>, Dumpable {
- companion object {
- private const val PROPERTY_ENABLE_IMMERSIVE_INDICATOR = "enable_immersive_indicator"
- }
- public fun isImmersiveIndicatorEnabled(): Boolean {
- return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
- PROPERTY_ENABLE_IMMERSIVE_INDICATOR, true)
- }
+ @SystemAnimationState fun getAnimationState(): Int
- @SystemAnimationState var animationState: Int = IDLE
- private set
+ fun onStatusEvent(event: StatusEvent)
- /** True if the persistent privacy dot should be active */
- var hasPersistentDot = false
- protected set
-
- private var scheduledEvent: StatusEvent? = null
- private var cancelExecutionRunnable: Runnable? = null
- private val listeners = mutableSetOf<SystemStatusAnimationCallback>()
-
- fun getListeners(): MutableSet<SystemStatusAnimationCallback> {
- return listeners
- }
-
- init {
- coordinator.attachScheduler(this)
- dumpManager.registerDumpable(TAG, this)
- }
-
- open fun onStatusEvent(event: StatusEvent) {
- // Ignore any updates until the system is up and running
- if (isTooEarly() || !isImmersiveIndicatorEnabled()) {
- return
- }
-
- // Don't deal with threading for now (no need let's be honest)
- Assert.isMainThread()
- if ((event.priority > scheduledEvent?.priority ?: -1) &&
- animationState != ANIMATING_OUT &&
- (animationState != SHOWING_PERSISTENT_DOT && event.forceVisible)) {
- // events can only be scheduled if a higher priority or no other event is in progress
- if (DEBUG) {
- Log.d(TAG, "scheduling event $event")
- }
-
- scheduleEvent(event)
- } else if (scheduledEvent?.shouldUpdateFromEvent(event) == true) {
- if (DEBUG) {
- Log.d(TAG, "updating current event from: $event. animationState=$animationState")
- }
- scheduledEvent?.updateFromEvent(event)
- if (event.forceVisible) {
- hasPersistentDot = true
- // If we missed the chance to show the persistent dot, do it now
- if (animationState == IDLE) {
- notifyTransitionToPersistentDot()
- }
- }
- } else {
- if (DEBUG) {
- Log.d(TAG, "ignoring event $event")
- }
- }
- }
-
- private fun clearDotIfVisible() {
- notifyHidePersistentDot()
- }
-
- fun setShouldShowPersistentPrivacyIndicator(should: Boolean) {
- if (hasPersistentDot == should || !isImmersiveIndicatorEnabled()) {
- return
- }
-
- hasPersistentDot = should
-
- if (!hasPersistentDot) {
- clearDotIfVisible()
- }
- }
-
- public fun isTooEarly(): Boolean {
- return systemClock.uptimeMillis() - Process.getStartUptimeMillis() < MIN_UPTIME
- }
-
- /**
- * Clear the scheduled event (if any) and schedule a new one
- */
- private fun scheduleEvent(event: StatusEvent) {
- scheduledEvent = event
-
- if (event.forceVisible) {
- hasPersistentDot = true
- }
-
- // If animations are turned off, we'll transition directly to the dot
- if (!event.showAnimation && event.forceVisible) {
- notifyTransitionToPersistentDot()
- scheduledEvent = null
- return
- }
-
- chipAnimationController.prepareChipAnimation(scheduledEvent!!.viewCreator)
- animationState = ANIMATION_QUEUED
- executor.executeDelayed({
- runChipAnimation()
- }, DEBOUNCE_DELAY)
- }
-
- /**
- * 1. Define a total budget for the chip animation (1500ms)
- * 2. Send out callbacks to listeners so that they can generate animations locally
- * 3. Update the scheduler state so that clients know where we are
- * 4. Maybe: provide scaffolding such as: dot location, margins, etc
- * 5. Maybe: define a maximum animation length and enforce it. Probably only doable if we
- * collect all of the animators and run them together.
- */
- private fun runChipAnimation() {
- statusBarWindowController.setForceStatusBarVisible(true)
- animationState = ANIMATING_IN
-
- val animSet = collectStartAnimations()
- if (animSet.totalDuration > 500) {
- throw IllegalStateException("System animation total length exceeds budget. " +
- "Expected: 500, actual: ${animSet.totalDuration}")
- }
- animSet.addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
- animationState = RUNNING_CHIP_ANIM
- }
- })
- animSet.start()
-
- executor.executeDelayed({
- val animSet2 = collectFinishAnimations()
- animationState = ANIMATING_OUT
- animSet2.addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
- animationState = if (hasPersistentDot) {
- SHOWING_PERSISTENT_DOT
- } else {
- IDLE
- }
-
- statusBarWindowController.setForceStatusBarVisible(false)
- }
- })
- animSet2.start()
- scheduledEvent = null
- }, DISPLAY_LENGTH)
- }
-
- private fun collectStartAnimations(): AnimatorSet {
- val animators = mutableListOf<Animator>()
- listeners.forEach { listener ->
- listener.onSystemEventAnimationBegin()?.let { anim ->
- animators.add(anim)
- }
- }
- animators.add(chipAnimationController.onSystemEventAnimationBegin())
- val animSet = AnimatorSet().also {
- it.playTogether(animators)
- }
-
- return animSet
- }
-
- private fun collectFinishAnimations(): AnimatorSet {
- val animators = mutableListOf<Animator>()
- listeners.forEach { listener ->
- listener.onSystemEventAnimationFinish(hasPersistentDot)?.let { anim ->
- animators.add(anim)
- }
- }
- animators.add(chipAnimationController.onSystemEventAnimationFinish(hasPersistentDot))
- if (hasPersistentDot) {
- val dotAnim = notifyTransitionToPersistentDot()
- if (dotAnim != null) {
- animators.add(dotAnim)
- }
- }
- val animSet = AnimatorSet().also {
- it.playTogether(animators)
- }
-
- return animSet
- }
-
- private fun notifyTransitionToPersistentDot(): Animator? {
- val anims: List<Animator> = listeners.mapNotNull {
- it.onSystemStatusAnimationTransitionToPersistentDot(scheduledEvent?.contentDescription)
- }
- if (anims.isNotEmpty()) {
- val aSet = AnimatorSet()
- aSet.playTogether(anims)
- return aSet
- }
-
- return null
- }
-
- private fun notifyHidePersistentDot(): Animator? {
- val anims: List<Animator> = listeners.mapNotNull {
- it.onHidePersistentDot()
- }
-
- if (animationState == SHOWING_PERSISTENT_DOT) {
- animationState = IDLE
- }
-
- if (anims.isNotEmpty()) {
- val aSet = AnimatorSet()
- aSet.playTogether(anims)
- return aSet
- }
-
- return null
- }
-
- override fun addCallback(listener: SystemStatusAnimationCallback) {
- Assert.isMainThread()
-
- if (listeners.isEmpty()) {
- coordinator.startObserving()
- }
- listeners.add(listener)
- }
-
- override fun removeCallback(listener: SystemStatusAnimationCallback) {
- Assert.isMainThread()
-
- listeners.remove(listener)
- if (listeners.isEmpty()) {
- coordinator.stopObserving()
- }
- }
-
- override fun dump(pw: PrintWriter, args: Array<out String>) {
- pw.println("Scheduled event: $scheduledEvent")
- pw.println("Has persistent privacy dot: $hasPersistentDot")
- pw.println("Animation state: $animationState")
- pw.println("Listeners:")
- if (listeners.isEmpty()) {
- pw.println("(none)")
- } else {
- listeners.forEach {
- pw.println(" $it")
- }
- }
- }
+ fun removePersistentDot()
}
/**
@@ -337,6 +56,7 @@
@JvmDefault fun onHidePersistentDot(): Animator? { return null }
}
+
/**
* Animation state IntDef
*/
@@ -354,7 +74,7 @@
annotation class SystemAnimationState
/** No animation is in progress */
-const val IDLE = 0
+@SystemAnimationState const val IDLE = 0
/** An animation is queued, and awaiting the debounce period */
const val ANIMATION_QUEUED = 1
/** System is animating out, and chip is animating in */
@@ -379,20 +99,16 @@
val STATUS_CHIP_HEIGHT_TO_DOT_KEYFRAME_2 = PathInterpolator(0.3f, 0f, 0f, 1f)
val STATUS_CHIP_MOVE_TO_DOT = PathInterpolator(0f, 0f, 0.05f, 1f)
-private const val TAG = "SystemStatusAnimationScheduler"
-private const val DEBOUNCE_DELAY = 100L
+internal const val DEBOUNCE_DELAY = 100L
/**
* The total time spent on the chip animation is 1500ms, broken up into 3 sections:
- * - 500ms to animate the chip in (including animating system icons away)
- * - 500ms holding the chip on screen
- * - 500ms to animate the chip away (and system icons back)
- *
- * So DISPLAY_LENGTH should be the sum of the first 2 phases, while the final 500ms accounts for
- * the actual animation
+ * - 500ms to animate the chip in (including animating system icons away)
+ * - 500ms holding the chip on screen
+ * - 500ms to animate the chip away (and system icons back)
*/
-private const val DISPLAY_LENGTH = 1000L
+internal const val APPEAR_ANIMATION_DURATION = 500L
+internal const val DISPLAY_LENGTH = 3000L
+internal const val DISAPPEAR_ANIMATION_DURATION = 500L
-private const val MIN_UPTIME: Long = 5 * 1000
-
-private const val DEBUG = false
+internal const val MIN_UPTIME: Long = 5 * 1000
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
new file mode 100644
index 0000000..f7a4fea
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
@@ -0,0 +1,425 @@
+/*
+ * 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.events
+
+import android.os.Process
+import android.provider.DeviceConfig
+import android.util.Log
+import androidx.core.animation.Animator
+import androidx.core.animation.AnimatorListenerAdapter
+import androidx.core.animation.AnimatorSet
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.util.Assert
+import com.android.systemui.util.time.SystemClock
+import java.io.PrintWriter
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.FlowPreview
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.debounce
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withTimeout
+
+/**
+ * Scheduler for system status events. Obeys the following principles:
+ * ```
+ * - Waits 100 ms to schedule any event for debouncing/prioritization
+ * - Simple prioritization: Privacy > Battery > Connectivity (encoded in [StatusEvent])
+ * - Only schedules a single event, and throws away lowest priority events
+ * ```
+ *
+ * There are 4 basic stages of animation at play here:
+ * ```
+ * 1. System chrome animation OUT
+ * 2. Chip animation IN
+ * 3. Chip animation OUT; potentially into a dot
+ * 4. System chrome animation IN
+ * ```
+ *
+ * Thus we can keep all animations synchronized with two separate ValueAnimators, one for system
+ * chrome and the other for the chip. These can animate from 0,1 and listeners can parameterize
+ * their respective views based on the progress of the animator.
+ */
+@OptIn(FlowPreview::class)
+open class SystemStatusAnimationSchedulerImpl
+@Inject
+constructor(
+ private val coordinator: SystemEventCoordinator,
+ private val chipAnimationController: SystemEventChipAnimationController,
+ private val statusBarWindowController: StatusBarWindowController,
+ dumpManager: DumpManager,
+ private val systemClock: SystemClock,
+ @Application private val coroutineScope: CoroutineScope
+) : SystemStatusAnimationScheduler {
+
+ companion object {
+ private const val PROPERTY_ENABLE_IMMERSIVE_INDICATOR = "enable_immersive_indicator"
+ }
+
+ /** Contains the StatusEvent that is going to be displayed next. */
+ private var scheduledEvent = MutableStateFlow<StatusEvent?>(null)
+
+ /**
+ * The currently displayed status event. (This is null in all states except ANIMATING_IN and
+ * CHIP_ANIMATION_RUNNING)
+ */
+ private var currentlyDisplayedEvent: StatusEvent? = null
+
+ /** StateFlow holding the current [SystemAnimationState] at any time. */
+ private var animationState = MutableStateFlow(IDLE)
+
+ /** True if the persistent privacy dot should be active */
+ var hasPersistentDot = false
+ protected set
+
+ /** Set of currently registered listeners */
+ protected val listeners = mutableSetOf<SystemStatusAnimationCallback>()
+
+ /** The job that is controlling the animators of the currently displayed status event. */
+ private var currentlyRunningAnimationJob: Job? = null
+
+ /** The job that is controlling the animators when an event is cancelled. */
+ private var eventCancellationJob: Job? = null
+
+ init {
+ coordinator.attachScheduler(this)
+ dumpManager.registerCriticalDumpable(TAG, this)
+
+ coroutineScope.launch {
+ // Wait for animationState to become ANIMATION_QUEUED and scheduledEvent to be non null.
+ // Once this combination is stable for at least DEBOUNCE_DELAY, then start a chip enter
+ // animation
+ animationState
+ .combine(scheduledEvent) { animationState, scheduledEvent ->
+ Pair(animationState, scheduledEvent)
+ }
+ .debounce(DEBOUNCE_DELAY)
+ .collect { (animationState, event) ->
+ if (animationState == ANIMATION_QUEUED && event != null) {
+ startAnimationLifecycle(event)
+ scheduledEvent.value = null
+ }
+ }
+ }
+ }
+
+ @SystemAnimationState override fun getAnimationState(): Int = animationState.value
+
+ override fun onStatusEvent(event: StatusEvent) {
+ Assert.isMainThread()
+
+ // Ignore any updates until the system is up and running
+ if (isTooEarly() || !isImmersiveIndicatorEnabled()) {
+ return
+ }
+
+ if (
+ (event.priority > (scheduledEvent.value?.priority ?: -1)) &&
+ (event.priority > (currentlyDisplayedEvent?.priority ?: -1)) &&
+ !hasPersistentDot
+ ) {
+ // a event can only be scheduled if no other event is in progress or it has a higher
+ // priority. If a persistent dot is currently displayed, don't schedule the event.
+ if (DEBUG) {
+ Log.d(TAG, "scheduling event $event")
+ }
+
+ scheduleEvent(event)
+ } else if (currentlyDisplayedEvent?.shouldUpdateFromEvent(event) == true) {
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "updating current event from: $event. animationState=${animationState.value}"
+ )
+ }
+ currentlyDisplayedEvent?.updateFromEvent(event)
+ } else if (scheduledEvent.value?.shouldUpdateFromEvent(event) == true) {
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "updating scheduled event from: $event. animationState=${animationState.value}"
+ )
+ }
+ scheduledEvent.value?.updateFromEvent(event)
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "ignoring event $event")
+ }
+ }
+ }
+
+ override fun removePersistentDot() {
+ Assert.isMainThread()
+
+ // If there is an event scheduled currently, set its forceVisible flag to false, such that
+ // it will never transform into a persistent dot
+ scheduledEvent.value?.forceVisible = false
+
+ // Nothing else to do if hasPersistentDot is already false
+ if (!hasPersistentDot) return
+ // Set hasPersistentDot to false. If the animationState is anything before ANIMATING_OUT,
+ // the disappear animation will not animate into a dot but remove the chip entirely
+ hasPersistentDot = false
+ // if we are currently showing a persistent dot, hide it
+ if (animationState.value == SHOWING_PERSISTENT_DOT) notifyHidePersistentDot()
+ // if we are currently animating into a dot, wait for the animation to finish and then hide
+ // the dot
+ if (animationState.value == ANIMATING_OUT) {
+ coroutineScope.launch {
+ withTimeout(DISAPPEAR_ANIMATION_DURATION) {
+ animationState.first { it == SHOWING_PERSISTENT_DOT || it == ANIMATION_QUEUED }
+ notifyHidePersistentDot()
+ }
+ }
+ }
+ }
+
+ protected fun isTooEarly(): Boolean {
+ return systemClock.uptimeMillis() - Process.getStartUptimeMillis() < MIN_UPTIME
+ }
+
+ protected fun isImmersiveIndicatorEnabled(): Boolean {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_ENABLE_IMMERSIVE_INDICATOR,
+ true
+ )
+ }
+
+ /** Clear the scheduled event (if any) and schedule a new one */
+ private fun scheduleEvent(event: StatusEvent) {
+ scheduledEvent.value = event
+ if (currentlyDisplayedEvent != null && eventCancellationJob?.isActive != true) {
+ // cancel the currently displayed event. As soon as the event is animated out, the
+ // scheduled event will be displayed.
+ cancelCurrentlyDisplayedEvent()
+ return
+ }
+ if (animationState.value == IDLE) {
+ // If we are in IDLE state, set it to ANIMATION_QUEUED now
+ animationState.value = ANIMATION_QUEUED
+ }
+ }
+
+ /**
+ * Cancels the currently displayed event by animating it out. This function should only be
+ * called if the animationState is ANIMATING_IN or RUNNING_CHIP_ANIM, or in other words whenever
+ * currentlyRunningEvent is not null
+ */
+ private fun cancelCurrentlyDisplayedEvent() {
+ eventCancellationJob =
+ coroutineScope.launch {
+ withTimeout(APPEAR_ANIMATION_DURATION) {
+ // wait for animationState to become RUNNING_CHIP_ANIM, then cancel the running
+ // animation job and run the disappear animation immediately
+ animationState.first { it == RUNNING_CHIP_ANIM }
+ currentlyRunningAnimationJob?.cancel()
+ runChipDisappearAnimation()
+ }
+ }
+ }
+
+ /**
+ * Takes the currently scheduled Event and (using the coroutineScope) animates it in and out
+ * again after displaying it for DISPLAY_LENGTH ms. This function should only be called if there
+ * is an event scheduled (and currentlyDisplayedEvent is null)
+ */
+ private fun startAnimationLifecycle(event: StatusEvent) {
+ Assert.isMainThread()
+ hasPersistentDot = event.forceVisible
+
+ if (!event.showAnimation && event.forceVisible) {
+ // If animations are turned off, we'll transition directly to the dot
+ animationState.value = SHOWING_PERSISTENT_DOT
+ notifyTransitionToPersistentDot()
+ return
+ }
+
+ currentlyDisplayedEvent = event
+
+ chipAnimationController.prepareChipAnimation(event.viewCreator)
+ currentlyRunningAnimationJob =
+ coroutineScope.launch {
+ runChipAppearAnimation()
+ delay(APPEAR_ANIMATION_DURATION + DISPLAY_LENGTH)
+ runChipDisappearAnimation()
+ }
+ }
+
+ /**
+ * 1. Define a total budget for the chip animation (1500ms)
+ * 2. Send out callbacks to listeners so that they can generate animations locally
+ * 3. Update the scheduler state so that clients know where we are
+ * 4. Maybe: provide scaffolding such as: dot location, margins, etc
+ * 5. Maybe: define a maximum animation length and enforce it. Probably only doable if we
+ * collect all of the animators and run them together.
+ */
+ private fun runChipAppearAnimation() {
+ Assert.isMainThread()
+ if (hasPersistentDot) {
+ statusBarWindowController.setForceStatusBarVisible(true)
+ }
+ animationState.value = ANIMATING_IN
+
+ val animSet = collectStartAnimations()
+ if (animSet.totalDuration > 500) {
+ throw IllegalStateException(
+ "System animation total length exceeds budget. " +
+ "Expected: 500, actual: ${animSet.totalDuration}"
+ )
+ }
+ animSet.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ animationState.value = RUNNING_CHIP_ANIM
+ }
+ }
+ )
+ animSet.start()
+ }
+
+ private fun runChipDisappearAnimation() {
+ Assert.isMainThread()
+ val animSet2 = collectFinishAnimations()
+ animationState.value = ANIMATING_OUT
+ animSet2.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ animationState.value =
+ when {
+ hasPersistentDot -> SHOWING_PERSISTENT_DOT
+ scheduledEvent.value != null -> ANIMATION_QUEUED
+ else -> IDLE
+ }
+ statusBarWindowController.setForceStatusBarVisible(false)
+ }
+ }
+ )
+ animSet2.start()
+
+ // currentlyDisplayedEvent is set to null before the animation has ended such that new
+ // events can be scheduled during the disappear animation. We don't want to miss e.g. a new
+ // privacy event being scheduled during the disappear animation, otherwise we could end up
+ // with e.g. an active microphone but no privacy dot being displayed.
+ currentlyDisplayedEvent = null
+ }
+
+ private fun collectStartAnimations(): AnimatorSet {
+ val animators = mutableListOf<Animator>()
+ listeners.forEach { listener ->
+ listener.onSystemEventAnimationBegin()?.let { anim -> animators.add(anim) }
+ }
+ animators.add(chipAnimationController.onSystemEventAnimationBegin())
+
+ return AnimatorSet().also { it.playTogether(animators) }
+ }
+
+ private fun collectFinishAnimations(): AnimatorSet {
+ val animators = mutableListOf<Animator>()
+ listeners.forEach { listener ->
+ listener.onSystemEventAnimationFinish(hasPersistentDot)?.let { anim ->
+ animators.add(anim)
+ }
+ }
+ animators.add(chipAnimationController.onSystemEventAnimationFinish(hasPersistentDot))
+ if (hasPersistentDot) {
+ val dotAnim = notifyTransitionToPersistentDot()
+ if (dotAnim != null) {
+ animators.add(dotAnim)
+ }
+ }
+
+ return AnimatorSet().also { it.playTogether(animators) }
+ }
+
+ private fun notifyTransitionToPersistentDot(): Animator? {
+ val anims: List<Animator> =
+ listeners.mapNotNull {
+ it.onSystemStatusAnimationTransitionToPersistentDot(
+ currentlyDisplayedEvent?.contentDescription
+ )
+ }
+ if (anims.isNotEmpty()) {
+ val aSet = AnimatorSet()
+ aSet.playTogether(anims)
+ return aSet
+ }
+
+ return null
+ }
+
+ private fun notifyHidePersistentDot(): Animator? {
+ Assert.isMainThread()
+ val anims: List<Animator> = listeners.mapNotNull { it.onHidePersistentDot() }
+
+ if (animationState.value == SHOWING_PERSISTENT_DOT) {
+ if (scheduledEvent.value != null) {
+ animationState.value = ANIMATION_QUEUED
+ } else {
+ animationState.value = IDLE
+ }
+ }
+
+ if (anims.isNotEmpty()) {
+ val aSet = AnimatorSet()
+ aSet.playTogether(anims)
+ return aSet
+ }
+
+ return null
+ }
+
+ override fun addCallback(listener: SystemStatusAnimationCallback) {
+ Assert.isMainThread()
+
+ if (listeners.isEmpty()) {
+ coordinator.startObserving()
+ }
+ listeners.add(listener)
+ }
+
+ override fun removeCallback(listener: SystemStatusAnimationCallback) {
+ Assert.isMainThread()
+
+ listeners.remove(listener)
+ if (listeners.isEmpty()) {
+ coordinator.stopObserving()
+ }
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("Scheduled event: ${scheduledEvent.value}")
+ pw.println("Currently displayed event: $currentlyDisplayedEvent")
+ pw.println("Has persistent privacy dot: $hasPersistentDot")
+ pw.println("Animation state: ${animationState.value}")
+ pw.println("Listeners:")
+ if (listeners.isEmpty()) {
+ pw.println("(none)")
+ } else {
+ listeners.forEach { pw.println(" $it") }
+ }
+ }
+}
+
+private const val DEBUG = false
+private const val TAG = "SystemStatusAnimationSchedulerImpl"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLegacyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLegacyImpl.kt
new file mode 100644
index 0000000..64b7ac9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLegacyImpl.kt
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2021 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.events
+
+import android.os.Process
+import android.provider.DeviceConfig
+import android.util.Log
+import androidx.core.animation.Animator
+import androidx.core.animation.AnimatorListenerAdapter
+import androidx.core.animation.AnimatorSet
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.util.Assert
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.time.SystemClock
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/**
+ * Dead-simple scheduler for system status events. Obeys the following principles (all values TBD):
+ * ```
+ * - Avoiding log spam by only allowing 12 events per minute (1event/5s)
+ * - Waits 100ms to schedule any event for debouncing/prioritization
+ * - Simple prioritization: Privacy > Battery > connectivity (encoded in [StatusEvent])
+ * - Only schedules a single event, and throws away lowest priority events
+ * ```
+ * There are 4 basic stages of animation at play here:
+ * ```
+ * 1. System chrome animation OUT
+ * 2. Chip animation IN
+ * 3. Chip animation OUT; potentially into a dot
+ * 4. System chrome animation IN
+ * ```
+ * Thus we can keep all animations synchronized with two separate ValueAnimators, one for system
+ * chrome and the other for the chip. These can animate from 0,1 and listeners can parameterize
+ * their respective views based on the progress of the animator. Interpolation differences TBD
+ */
+open class SystemStatusAnimationSchedulerLegacyImpl
+@Inject
+constructor(
+ private val coordinator: SystemEventCoordinator,
+ private val chipAnimationController: SystemEventChipAnimationController,
+ private val statusBarWindowController: StatusBarWindowController,
+ private val dumpManager: DumpManager,
+ private val systemClock: SystemClock,
+ @Main private val executor: DelayableExecutor
+) : SystemStatusAnimationScheduler {
+
+ companion object {
+ private const val PROPERTY_ENABLE_IMMERSIVE_INDICATOR = "enable_immersive_indicator"
+ }
+
+ fun isImmersiveIndicatorEnabled(): Boolean {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_ENABLE_IMMERSIVE_INDICATOR,
+ true
+ )
+ }
+
+ @SystemAnimationState private var animationState: Int = IDLE
+
+ /** True if the persistent privacy dot should be active */
+ var hasPersistentDot = false
+ protected set
+
+ private var scheduledEvent: StatusEvent? = null
+
+ val listeners = mutableSetOf<SystemStatusAnimationCallback>()
+
+ init {
+ coordinator.attachScheduler(this)
+ dumpManager.registerDumpable(TAG, this)
+ }
+
+ @SystemAnimationState override fun getAnimationState() = animationState
+
+ override fun onStatusEvent(event: StatusEvent) {
+ // Ignore any updates until the system is up and running
+ if (isTooEarly() || !isImmersiveIndicatorEnabled()) {
+ return
+ }
+
+ // Don't deal with threading for now (no need let's be honest)
+ Assert.isMainThread()
+ if (
+ (event.priority > (scheduledEvent?.priority ?: -1)) &&
+ animationState != ANIMATING_OUT &&
+ animationState != SHOWING_PERSISTENT_DOT
+ ) {
+ // events can only be scheduled if a higher priority or no other event is in progress
+ if (DEBUG) {
+ Log.d(TAG, "scheduling event $event")
+ }
+
+ scheduleEvent(event)
+ } else if (scheduledEvent?.shouldUpdateFromEvent(event) == true) {
+ if (DEBUG) {
+ Log.d(TAG, "updating current event from: $event. animationState=$animationState")
+ }
+ scheduledEvent?.updateFromEvent(event)
+ if (event.forceVisible) {
+ hasPersistentDot = true
+ // If we missed the chance to show the persistent dot, do it now
+ if (animationState == IDLE) {
+ notifyTransitionToPersistentDot()
+ }
+ }
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "ignoring event $event")
+ }
+ }
+ }
+
+ override fun removePersistentDot() {
+ if (!hasPersistentDot || !isImmersiveIndicatorEnabled()) {
+ return
+ }
+
+ hasPersistentDot = false
+ notifyHidePersistentDot()
+ return
+ }
+
+ fun isTooEarly(): Boolean {
+ return systemClock.uptimeMillis() - Process.getStartUptimeMillis() < MIN_UPTIME
+ }
+
+ /** Clear the scheduled event (if any) and schedule a new one */
+ private fun scheduleEvent(event: StatusEvent) {
+ scheduledEvent = event
+
+ if (event.forceVisible) {
+ hasPersistentDot = true
+ }
+
+ // If animations are turned off, we'll transition directly to the dot
+ if (!event.showAnimation && event.forceVisible) {
+ notifyTransitionToPersistentDot()
+ scheduledEvent = null
+ return
+ }
+
+ chipAnimationController.prepareChipAnimation(scheduledEvent!!.viewCreator)
+ animationState = ANIMATION_QUEUED
+ executor.executeDelayed({ runChipAnimation() }, DEBOUNCE_DELAY)
+ }
+
+ /**
+ * 1. Define a total budget for the chip animation (1500ms)
+ * 2. Send out callbacks to listeners so that they can generate animations locally
+ * 3. Update the scheduler state so that clients know where we are
+ * 4. Maybe: provide scaffolding such as: dot location, margins, etc
+ * 5. Maybe: define a maximum animation length and enforce it. Probably only doable if we
+ * collect all of the animators and run them together.
+ */
+ private fun runChipAnimation() {
+ statusBarWindowController.setForceStatusBarVisible(true)
+ animationState = ANIMATING_IN
+
+ val animSet = collectStartAnimations()
+ if (animSet.totalDuration > 500) {
+ throw IllegalStateException(
+ "System animation total length exceeds budget. " +
+ "Expected: 500, actual: ${animSet.totalDuration}"
+ )
+ }
+ animSet.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ animationState = RUNNING_CHIP_ANIM
+ }
+ }
+ )
+ animSet.start()
+
+ executor.executeDelayed(
+ {
+ val animSet2 = collectFinishAnimations()
+ animationState = ANIMATING_OUT
+ animSet2.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ animationState =
+ if (hasPersistentDot) {
+ SHOWING_PERSISTENT_DOT
+ } else {
+ IDLE
+ }
+
+ statusBarWindowController.setForceStatusBarVisible(false)
+ }
+ }
+ )
+ animSet2.start()
+ scheduledEvent = null
+ },
+ DISPLAY_LENGTH
+ )
+ }
+
+ private fun collectStartAnimations(): AnimatorSet {
+ val animators = mutableListOf<Animator>()
+ listeners.forEach { listener ->
+ listener.onSystemEventAnimationBegin()?.let { anim -> animators.add(anim) }
+ }
+ animators.add(chipAnimationController.onSystemEventAnimationBegin())
+ val animSet = AnimatorSet().also { it.playTogether(animators) }
+
+ return animSet
+ }
+
+ private fun collectFinishAnimations(): AnimatorSet {
+ val animators = mutableListOf<Animator>()
+ listeners.forEach { listener ->
+ listener.onSystemEventAnimationFinish(hasPersistentDot)?.let { anim ->
+ animators.add(anim)
+ }
+ }
+ animators.add(chipAnimationController.onSystemEventAnimationFinish(hasPersistentDot))
+ if (hasPersistentDot) {
+ val dotAnim = notifyTransitionToPersistentDot()
+ if (dotAnim != null) {
+ animators.add(dotAnim)
+ }
+ }
+ val animSet = AnimatorSet().also { it.playTogether(animators) }
+
+ return animSet
+ }
+
+ private fun notifyTransitionToPersistentDot(): Animator? {
+ val anims: List<Animator> =
+ listeners.mapNotNull {
+ it.onSystemStatusAnimationTransitionToPersistentDot(
+ scheduledEvent?.contentDescription
+ )
+ }
+ if (anims.isNotEmpty()) {
+ val aSet = AnimatorSet()
+ aSet.playTogether(anims)
+ return aSet
+ }
+
+ return null
+ }
+
+ private fun notifyHidePersistentDot(): Animator? {
+ val anims: List<Animator> = listeners.mapNotNull { it.onHidePersistentDot() }
+
+ if (animationState == SHOWING_PERSISTENT_DOT) {
+ animationState = IDLE
+ }
+
+ if (anims.isNotEmpty()) {
+ val aSet = AnimatorSet()
+ aSet.playTogether(anims)
+ return aSet
+ }
+
+ return null
+ }
+
+ override fun addCallback(listener: SystemStatusAnimationCallback) {
+ Assert.isMainThread()
+
+ if (listeners.isEmpty()) {
+ coordinator.startObserving()
+ }
+ listeners.add(listener)
+ }
+
+ override fun removeCallback(listener: SystemStatusAnimationCallback) {
+ Assert.isMainThread()
+
+ listeners.remove(listener)
+ if (listeners.isEmpty()) {
+ coordinator.stopObserving()
+ }
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("Scheduled event: $scheduledEvent")
+ pw.println("Has persistent privacy dot: $hasPersistentDot")
+ pw.println("Animation state: $animationState")
+ pw.println("Listeners:")
+ if (listeners.isEmpty()) {
+ pw.println("(none)")
+ } else {
+ listeners.forEach { pw.println(" $it") }
+ }
+ }
+}
+
+private const val DEBUG = false
+private const val TAG = "SystemStatusAnimationSchedulerLegacyImpl"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 4e9e88d..bc531da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -619,14 +619,6 @@
return row.isMediaRow();
}
- /**
- * We are a top level child if our parent is the list of notifications duh
- * @return {@code true} if we're a top level notification
- */
- public boolean isTopLevelChild() {
- return row != null && row.isTopLevelChild();
- }
-
public void resetUserExpansion() {
if (row != null) row.resetUserExpansion();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
index 67a8a63..6c84fef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
@@ -34,6 +34,7 @@
import com.android.systemui.statusbar.notification.dagger.SilentHeader;
import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -43,8 +44,8 @@
* Filters out NotificationEntries based on its Ranking and dozing state.
* Assigns alerting / silent section based on the importance of the notification entry.
* We check the NotificationEntry's Ranking for:
- * - whether the notification's app is suspended or hiding its notifications
- * - whether DND settings are hiding notifications from ambient display or the notification list
+ * - whether the notification's app is suspended or hiding its notifications
+ * - whether DND settings are hiding notifications from ambient display or the notification list
*/
@CoordinatorScope
public class RankingCoordinator implements Coordinator {
@@ -78,6 +79,8 @@
public void attach(NotifPipeline pipeline) {
mStatusBarStateController.addCallback(mStatusBarStateCallback);
mSectionStyleProvider.setMinimizedSections(Collections.singleton(mMinimizedNotifSectioner));
+ mSectionStyleProvider.setSilentSections(
+ Arrays.asList(mSilentNotifSectioner, mMinimizedNotifSectioner));
pipeline.addPreGroupFilter(mSuspendedFilter);
pipeline.addPreGroupFilter(mDndVisualEffectsFilter);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt
index 7b94830..5a3edf4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.collection.provider
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
import javax.inject.Inject
@@ -27,6 +28,7 @@
*/
@SysUISingleton
class SectionStyleProvider @Inject constructor() {
+ private lateinit var silentSections: Set<NotifSectioner>
private lateinit var lowPrioritySections: Set<NotifSectioner>
/**
@@ -38,9 +40,42 @@
}
/**
- * Determine if the given section is minimized
+ * Determine if the given section is minimized.
*/
fun isMinimizedSection(section: NotifSection): Boolean {
return lowPrioritySections.contains(section.sectioner)
}
+
+ /**
+ * Determine if the given entry is minimized.
+ */
+ @JvmOverloads
+ fun isMinimized(entry: ListEntry, ifNotInSection: Boolean = true): Boolean {
+ val section = entry.section ?: return ifNotInSection
+ return isMinimizedSection(section)
+ }
+
+ /**
+ * Feed the provider the information it needs about which sections are silent, so that it can
+ * calculate which entries are in a "silent" section.
+ */
+ fun setSilentSections(sections: Collection<NotifSectioner>) {
+ silentSections = sections.toSet()
+ }
+
+ /**
+ * Determine if the given section is silent.
+ */
+ fun isSilentSection(section: NotifSection): Boolean {
+ return silentSections.contains(section.sectioner)
+ }
+
+ /**
+ * Determine if the given entry is silent.
+ */
+ @JvmOverloads
+ fun isSilent(entry: ListEntry, ifNotInSection: Boolean = true): Boolean {
+ val section = entry.section ?: return ifNotInSection
+ return isSilentSection(section)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
index f0b221d..0de3246 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
@@ -344,7 +344,7 @@
or WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED)
}
-class ChannelEditorDialog(context: Context) : Dialog(context) {
+class ChannelEditorDialog(context: Context, theme: Int) : Dialog(context, theme) {
fun updateDoneButtonText(hasChanges: Boolean) {
findViewById<TextView>(R.id.done_button)?.setText(
if (hasChanges)
@@ -361,7 +361,7 @@
}
fun build(): ChannelEditorDialog {
- return ChannelEditorDialog(context)
+ return ChannelEditorDialog(context, R.style.Theme_SystemUI_Dialog)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 7c6efe4..2affa77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -110,7 +110,6 @@
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.SwipeableView;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -3587,10 +3586,6 @@
return mEntry.getSbn().getNotification().isMediaNotification();
}
- public boolean isTopLevelChild() {
- return getParent() instanceof NotificationStackScrollLayout;
- }
-
public boolean isGroupNotFullyVisible() {
return getClipTopAmount() > 0 || getTranslationY() < 0;
}
@@ -3734,7 +3729,9 @@
}
pw.println("Roundness: " + getRoundableState().debugString());
- if (mIsSummaryWithChildren) {
+ int transientViewCount = mChildrenContainer == null
+ ? 0 : mChildrenContainer.getTransientViewCount();
+ if (mIsSummaryWithChildren || transientViewCount > 0) {
pw.println();
pw.print("ChildrenContainer");
pw.print(" visibility: " + mChildrenContainer.getVisibility());
@@ -3742,8 +3739,7 @@
pw.print(", translationY: " + mChildrenContainer.getTranslationY());
pw.println();
List<ExpandableNotificationRow> notificationChildren = getAttachedChildren();
- pw.println("Children: " + notificationChildren.size());
- pw.print("{");
+ pw.print("Children: " + notificationChildren.size() + " {");
pw.increaseIndent();
for (ExpandableNotificationRow child : notificationChildren) {
pw.println();
@@ -3751,6 +3747,15 @@
}
pw.decreaseIndent();
pw.println("}");
+ pw.print("Transient Views: " + transientViewCount + " {");
+ pw.increaseIndent();
+ for (int i = 0; i < transientViewCount; i++) {
+ pw.println();
+ ExpandableView child = (ExpandableView) mChildrenContainer.getTransientView(i);
+ child.dump(pw, args);
+ }
+ pw.decreaseIndent();
+ pw.println("}");
} else if (mPrivateLayout != null) {
mPrivateLayout.dumpSmartReplies(pw);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index f1694ac..2dda6fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -317,7 +317,7 @@
}
mView.removeChildNotification(childView);
if (!isTransfer) {
- mListContainer.notifyGroupChildRemoved(childView, mView);
+ mListContainer.notifyGroupChildRemoved(childView, mView.getChildrenContainer());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index d2087ba6..e09b94b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -2792,7 +2792,7 @@
}
child.setOnHeightChangedListener(null);
updateScrollStateForRemovedChild(child);
- boolean animationGenerated = generateRemoveAnimation(child);
+ boolean animationGenerated = container != null && generateRemoveAnimation(child);
if (animationGenerated) {
if (!mSwipedOutViews.contains(child) || !isFullySwipedOut(child)) {
container.addTransientView(child, 0);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index 576df7a..f6d53b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -31,7 +31,7 @@
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.AutoAddTracker;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.ReduceBrightColorsController;
import com.android.systemui.qs.SettingObserver;
import com.android.systemui.qs.external.CustomTile;
@@ -75,7 +75,7 @@
private final String mSafetySpec;
protected final Context mContext;
- protected final QSTileHost mHost;
+ protected final QSHost mHost;
protected final Handler mHandler;
protected final SecureSettings mSecureSettings;
protected final AutoAddTracker mAutoTracker;
@@ -92,7 +92,7 @@
private final ArrayList<AutoAddSetting> mAutoAddSettingList = new ArrayList<>();
public AutoTileManager(Context context, AutoAddTracker.Builder autoAddTrackerBuilder,
- QSTileHost host,
+ QSHost host,
@Background Handler handler,
SecureSettings secureSettings,
HotspotController hotspotController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 6f4afe4..b8c7a1d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -50,6 +50,7 @@
import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.QSPanelController;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.CameraLauncher;
@@ -103,6 +104,7 @@
private final SystemBarAttributesListener mSystemBarAttributesListener;
private final Lazy<CameraLauncher> mCameraLauncherLazy;
private final QuickSettingsController mQsController;
+ private final QSHost mQSHost;
private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
@@ -135,7 +137,8 @@
@DisplayId int displayId,
SystemBarAttributesListener systemBarAttributesListener,
Lazy<CameraLauncher> cameraLauncherLazy,
- UserTracker userTracker) {
+ UserTracker userTracker,
+ QSHost qsHost) {
mCentralSurfaces = centralSurfaces;
mQsController = quickSettingsController;
mContext = context;
@@ -161,6 +164,7 @@
mDisplayId = displayId;
mCameraLauncherLazy = cameraLauncherLazy;
mUserTracker = userTracker;
+ mQSHost = qsHost;
mVibrateOnOpening = resources.getBoolean(R.bool.config_vibrateOnIconAnimation);
mCameraLaunchGestureVibrationEffect = getCameraGestureVibrationEffect(
@@ -181,22 +185,17 @@
@Override
public void addQsTile(ComponentName tile) {
- QSPanelController qsPanelController = mCentralSurfaces.getQSPanelController();
- if (qsPanelController != null && qsPanelController.getHost() != null) {
- qsPanelController.getHost().addTile(tile);
- }
+ mQSHost.addTile(tile);
}
@Override
public void remQsTile(ComponentName tile) {
- QSPanelController qsPanelController = mCentralSurfaces.getQSPanelController();
- if (qsPanelController != null && qsPanelController.getHost() != null) {
- qsPanelController.getHost().removeTileByUser(tile);
- }
+ mQSHost.removeTileByUser(tile);
}
@Override
public void clickTile(ComponentName tile) {
+ // Can't inject this because it changes with the QS fragment
QSPanelController qsPanelController = mCentralSurfaces.getQSPanelController();
if (qsPanelController != null) {
qsPanelController.clickTile(tile);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index cba0897..753032c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -21,9 +21,6 @@
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.ContentObserver;
@@ -36,13 +33,16 @@
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
+import androidx.core.animation.Animator;
+import androidx.core.animation.AnimatorListenerAdapter;
+import androidx.core.animation.ValueAnimator;
import com.android.keyguard.CarrierTextController;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.logging.KeyguardLogger;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
+import com.android.systemui.animation.InterpolatorsAndroidX;
import com.android.systemui.battery.BatteryMeterViewController;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.log.LogLevel;
@@ -166,7 +166,8 @@
private final ValueAnimator.AnimatorUpdateListener mAnimatorUpdateListener =
animation -> {
- mKeyguardStatusBarAnimateAlpha = (float) animation.getAnimatedValue();
+ mKeyguardStatusBarAnimateAlpha =
+ (float) ((ValueAnimator) animation).getAnimatedValue();
updateViewState();
};
@@ -434,7 +435,7 @@
ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
anim.addUpdateListener(mAnimatorUpdateListener);
anim.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
- anim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+ anim.setInterpolator(InterpolatorsAndroidX.LINEAR_OUT_SLOW_IN);
anim.start();
}
@@ -445,7 +446,7 @@
anim.addUpdateListener(mAnimatorUpdateListener);
anim.setStartDelay(startDelay);
anim.setDuration(duration);
- anim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+ anim.setInterpolator(InterpolatorsAndroidX.LINEAR_OUT_SLOW_IN);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 85590fc..eb19c0d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -1,6 +1,5 @@
package com.android.systemui.statusbar.phone;
-import android.app.NotificationManager;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Color;
@@ -39,6 +38,7 @@
import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.window.StatusBarWindowController;
import com.android.wm.shell.bubbles.Bubbles;
@@ -73,6 +73,7 @@
private final NotificationWakeUpCoordinator mWakeUpCoordinator;
private final KeyguardBypassController mBypassController;
private final DozeParameters mDozeParameters;
+ private final SectionStyleProvider mSectionStyleProvider;
private final Optional<Bubbles> mBubblesOptional;
private final StatusBarWindowController mStatusBarWindowController;
private final ScreenOffAnimationController mScreenOffAnimationController;
@@ -117,6 +118,7 @@
NotificationMediaManager notificationMediaManager,
NotificationListener notificationListener,
DozeParameters dozeParameters,
+ SectionStyleProvider sectionStyleProvider,
Optional<Bubbles> bubblesOptional,
DemoModeController demoModeController,
DarkIconDispatcher darkIconDispatcher,
@@ -128,6 +130,7 @@
mStatusBarStateController.addCallback(this);
mMediaManager = notificationMediaManager;
mDozeParameters = dozeParameters;
+ mSectionStyleProvider = sectionStyleProvider;
mWakeUpCoordinator = wakeUpCoordinator;
wakeUpCoordinator.addListener(this);
mBypassController = keyguardBypassController;
@@ -260,19 +263,13 @@
protected boolean shouldShowNotificationIcon(NotificationEntry entry,
boolean showAmbient, boolean showLowPriority, boolean hideDismissed,
boolean hideRepliedMessages, boolean hideCurrentMedia, boolean hidePulsing) {
- if (entry.getRanking().isAmbient() && !showAmbient) {
+ if (!showAmbient && mSectionStyleProvider.isMinimized(entry)) {
return false;
}
if (hideCurrentMedia && entry.getKey().equals(mMediaManager.getMediaNotificationKey())) {
return false;
}
- if (!showLowPriority && entry.getImportance() < NotificationManager.IMPORTANCE_DEFAULT) {
- return false;
- }
- if (!entry.isTopLevelChild()) {
- return false;
- }
- if (entry.getRow().getVisibility() == View.GONE) {
+ if (!showLowPriority && mSectionStyleProvider.isSilent(entry)) {
return false;
}
if (entry.isRowDismissed() && hideDismissed) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 65becf7..ed978c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -23,7 +23,6 @@
import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.IDLE;
import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.SHOWING_PERSISTENT_DOT;
-import android.animation.Animator;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.Fragment;
@@ -43,6 +42,7 @@
import android.widget.LinearLayout;
import androidx.annotation.VisibleForTesting;
+import androidx.core.animation.Animator;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Dumpable;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarSystemEventAnimator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarSystemEventAnimator.kt
index fe69f75..c04ea36 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarSystemEventAnimator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarSystemEventAnimator.kt
@@ -16,9 +16,9 @@
package com.android.systemui.statusbar.phone.fragment
-import android.animation.Animator
-import android.animation.AnimatorSet
-import android.animation.ValueAnimator
+import androidx.core.animation.Animator
+import androidx.core.animation.AnimatorSet
+import androidx.core.animation.ValueAnimator
import android.content.res.Resources
import android.view.View
import com.android.systemui.R
@@ -46,15 +46,19 @@
R.dimen.ongoing_appops_chip_animation_out_status_bar_translation_x)
override fun onSystemEventAnimationBegin(): Animator {
- val moveOut = ValueAnimator.ofFloat(0f, 1f).setDuration(23.frames)
- moveOut.interpolator = STATUS_BAR_X_MOVE_OUT
- moveOut.addUpdateListener { animation: ValueAnimator ->
- animatedView.translationX = -(translationXIn * animation.animatedValue as Float)
+ val moveOut = ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = 23.frames
+ interpolator = STATUS_BAR_X_MOVE_OUT
+ addUpdateListener {
+ animatedView.translationX = -(translationXIn * animatedValue as Float)
+ }
}
- val alphaOut = ValueAnimator.ofFloat(1f, 0f).setDuration(8.frames)
- alphaOut.interpolator = null
- alphaOut.addUpdateListener { animation: ValueAnimator ->
- animatedView.alpha = animation.animatedValue as Float
+ val alphaOut = ValueAnimator.ofFloat(1f, 0f).apply {
+ duration = 8.frames
+ interpolator = null
+ addUpdateListener {
+ animatedView.alpha = animatedValue as Float
+ }
}
val animSet = AnimatorSet()
@@ -64,17 +68,21 @@
override fun onSystemEventAnimationFinish(hasPersistentDot: Boolean): Animator {
animatedView.translationX = translationXOut.toFloat()
- val moveIn = ValueAnimator.ofFloat(1f, 0f).setDuration(28.frames)
- moveIn.startDelay = 2.frames
- moveIn.interpolator = STATUS_BAR_X_MOVE_IN
- moveIn.addUpdateListener { animation: ValueAnimator ->
- animatedView.translationX = translationXOut * animation.animatedValue as Float
+ val moveIn = ValueAnimator.ofFloat(1f, 0f).apply {
+ duration = 23.frames
+ startDelay = 7.frames
+ interpolator = STATUS_BAR_X_MOVE_IN
+ addUpdateListener {
+ animatedView.translationX = translationXOut * animatedValue as Float
+ }
}
- val alphaIn = ValueAnimator.ofFloat(0f, 1f).setDuration(10.frames)
- alphaIn.startDelay = 4.frames
- alphaIn.interpolator = null
- alphaIn.addUpdateListener { animation: ValueAnimator ->
- animatedView.alpha = animation.animatedValue as Float
+ val alphaIn = ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = 5.frames
+ startDelay = 11.frames
+ interpolator = null
+ addUpdateListener {
+ animatedView.alpha = animatedValue as Float
+ }
}
val animatorSet = AnimatorSet()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index 3263c4e..1a4a311 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -410,11 +410,14 @@
@Override
public boolean isSticky() {
- final boolean isSticky = (mEntry.isRowPinned() && expanded)
+ return (mEntry.isRowPinned() && expanded)
|| remoteInputActive
- || hasFullScreenIntent(mEntry)
- || mEntry.isStickyAndNotDemoted();
- return isSticky;
+ || hasFullScreenIntent(mEntry);
+ }
+
+ @Override
+ public boolean isStickyForSomeTime() {
+ return mEntry.isStickyAndNotDemoted();
}
@Override
@@ -476,10 +479,10 @@
*/
@Override
protected long calculateFinishTime() {
- if (isSticky()) {
- return mEntry.mCreationElapsedRealTime + mStickyDisplayTime;
- }
- return mPostTime + getRecommendedHeadsUpTimeoutMs(mAutoDismissNotificationDecay);
+ final long duration = getRecommendedHeadsUpTimeoutMs(
+ isStickyForSomeTime() ? mStickyDisplayTime : mAutoDismissNotificationDecay);
+
+ return mPostTime + duration;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
index b23d870..8cfe2ea 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
@@ -42,7 +42,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.demomode.DemoModeController;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -68,7 +68,7 @@
// Things that use the tunable infrastructure but are now real user settings and
// shouldn't be reset with tuner settings.
private static final String[] RESET_EXCEPTION_LIST = new String[] {
- QSTileHost.TILES_SETTING,
+ QSHost.TILES_SETTING,
Settings.Secure.DOZE_ALWAYS_ON,
Settings.Secure.MEDIA_CONTROLS_RESUME,
Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
index 5ea4399..0a78d896 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
@@ -54,6 +54,7 @@
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.events.StatusBarEventsModule;
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.phone.DozeServiceHost;
@@ -95,6 +96,7 @@
PowerModule.class,
QSModule.class,
ReferenceScreenshotModule.class,
+ StatusBarEventsModule.class,
VolumeModule.class,
}
)
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
new file mode 100644
index 0000000..30fed0b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
@@ -0,0 +1,112 @@
+/*
+ * 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.keyguard
+
+import android.app.ActivityTaskManager
+import android.content.pm.PackageManager
+import android.os.PowerManager
+import android.telecom.TelecomManager
+import android.telephony.TelephonyManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.shade.ShadeController
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class EmergencyButtonControllerTest : SysuiTestCase() {
+ lateinit var underTest: EmergencyButtonController
+ @Mock lateinit var emergencyButton: EmergencyButton
+ @Mock lateinit var configurationController: ConfigurationController
+ @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Mock lateinit var telephonyManager: TelephonyManager
+ @Mock lateinit var powerManager: PowerManager
+ @Mock lateinit var activityTaskManager: ActivityTaskManager
+ @Mock lateinit var shadeController: ShadeController
+ @Mock lateinit var telecomManager: TelecomManager
+ @Mock lateinit var metricsLogger: MetricsLogger
+ @Mock lateinit var lockPatternUtils: LockPatternUtils
+ @Mock lateinit var packageManager: PackageManager
+ val fakeSystemClock = FakeSystemClock()
+ val mainExecutor = FakeExecutor(fakeSystemClock)
+ val backgroundExecutor = FakeExecutor(fakeSystemClock)
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ underTest =
+ EmergencyButtonController(
+ emergencyButton,
+ configurationController,
+ keyguardUpdateMonitor,
+ telephonyManager,
+ powerManager,
+ activityTaskManager,
+ shadeController,
+ telecomManager,
+ metricsLogger,
+ lockPatternUtils,
+ mainExecutor,
+ backgroundExecutor
+ )
+ context.setMockPackageManager(packageManager)
+ Mockito.`when`(emergencyButton.context).thenReturn(context)
+ }
+
+ @Test
+ fun testUpdateEmergencyButton() {
+ Mockito.`when`(telecomManager.isInCall).thenReturn(true)
+ Mockito.`when`(lockPatternUtils.isSecure(anyInt())).thenReturn(true)
+ Mockito.`when`(packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY))
+ .thenReturn(true)
+ underTest.updateEmergencyCallButton()
+ backgroundExecutor.runAllReady()
+ verify(emergencyButton, never())
+ .updateEmergencyCallButton(
+ /* isInCall= */ any(),
+ /* hasTelephonyRadio= */ any(),
+ /* simLocked= */ any(),
+ /* isSecure= */ any()
+ )
+ mainExecutor.runAllReady()
+ verify(emergencyButton)
+ .updateEmergencyCallButton(
+ /* isInCall= */ eq(true),
+ /* hasTelephonyRadio= */ eq(true),
+ /* simLocked= */ any(),
+ /* isSecure= */ eq(true)
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index a1e4f70..395eb8f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -615,9 +615,8 @@
mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
mTestableLooper.processAllMessages();
- verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(),
- anyInt());
- verify(mFingerprintManager, never()).detectFingerprint(any(), any(), any());
+ verifyFingerprintAuthenticateCall();
+ verifyFingerprintDetectNeverCalled();
}
@Test
@@ -627,9 +626,8 @@
mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
mTestableLooper.processAllMessages();
- verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
- anyInt(), anyInt());
- verify(mFingerprintManager, never()).detectFingerprint(any(), any(), any());
+ verifyFingerprintAuthenticateNeverCalled();
+ verifyFingerprintDetectNeverCalled();
}
@Test
@@ -643,8 +641,8 @@
mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
mTestableLooper.processAllMessages();
- verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), anyInt());
- verify(mFingerprintManager).detectFingerprint(any(), any(), any());
+ verifyFingerprintAuthenticateNeverCalled();
+ verifyFingerprintDetectCall();
}
@Test
@@ -732,9 +730,7 @@
@Test
public void testTriesToAuthenticate_whenBouncer() {
setKeyguardBouncerVisibility(true);
-
- verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt());
- verify(mFaceManager, never()).hasEnrolledTemplates(anyInt());
+ verifyFaceAuthenticateCall();
}
@Test
@@ -742,7 +738,7 @@
mKeyguardUpdateMonitor.sendPrimaryBouncerChanged(
/* bouncerIsOrWillBeShowing */ true, /* bouncerFullyShown */ false);
- verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt());
+ verifyFaceAuthenticateNeverCalled();
}
@Test
@@ -750,7 +746,8 @@
keyguardIsVisible();
mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
- verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt());
+
+ verifyFaceAuthenticateCall();
verify(mUiEventLogger).logWithInstanceIdAndPosition(
eq(FaceAuthUiEvent.FACE_AUTH_UPDATED_STARTED_WAKING_UP),
eq(0),
@@ -766,7 +763,7 @@
mTestableLooper.processAllMessages();
keyguardIsVisible();
- verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt());
+ verifyFaceAuthenticateNeverCalled();
}
@Test
@@ -777,7 +774,8 @@
mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
keyguardIsVisible();
- verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt());
+
+ verifyFaceAuthenticateNeverCalled();
}
@Test
@@ -801,8 +799,8 @@
mTestableLooper.processAllMessages();
// THEN face detect and authenticate are NOT triggered
- verify(mFaceManager, never()).detectFace(any(), any(), any());
- verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt());
+ verifyFaceDetectNeverCalled();
+ verifyFaceAuthenticateNeverCalled();
// THEN biometric help message sent to callback
verify(keyguardUpdateMonitorCallback).onBiometricHelp(
@@ -823,8 +821,8 @@
mTestableLooper.processAllMessages();
// FACE detect is triggered, not authenticate
- verify(mFaceManager).detectFace(any(), any(), any());
- verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt());
+ verifyFaceDetectCall();
+ verifyFaceAuthenticateNeverCalled();
// WHEN bouncer becomes visible
setKeyguardBouncerVisibility(true);
@@ -832,8 +830,8 @@
// THEN face scanning is not run
mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
- verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt());
- verify(mFaceManager, never()).detectFace(any(), any(), any());
+ verifyFaceAuthenticateNeverCalled();
+ verifyFaceDetectNeverCalled();
}
@Test
@@ -848,8 +846,8 @@
mTestableLooper.processAllMessages();
// FACE detect and authenticate are NOT triggered
- verify(mFaceManager, never()).detectFace(any(), any(), any());
- verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt());
+ verifyFaceDetectNeverCalled();
+ verifyFaceAuthenticateNeverCalled();
}
@Test
@@ -887,7 +885,7 @@
mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
mKeyguardUpdateMonitor.setAssistantVisible(true);
- verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt());
+ verifyFaceAuthenticateCall();
}
@Test
@@ -895,11 +893,7 @@
mKeyguardUpdateMonitor.setKeyguardShowing(false, true);
mKeyguardUpdateMonitor.setAssistantVisible(true);
- verify(mFaceManager, never()).authenticate(any(),
- any(),
- any(),
- any(),
- anyInt());
+ verifyFaceAuthenticateNeverCalled();
}
@Test
@@ -910,15 +904,12 @@
// THEN fingerprint shouldn't listen
assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isFalse();
- verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
- anyInt(), anyInt());
-
+ verifyFingerprintAuthenticateNeverCalled();
// WHEN alternate bouncer is shown
mKeyguardUpdateMonitor.setAlternateBouncerShowing(true);
// THEN make sure FP listening begins
- verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(),
- anyInt());
+ verifyFingerprintAuthenticateCall();
}
@Test
@@ -930,7 +921,7 @@
KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */,
new ArrayList<>());
keyguardIsVisible();
- verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt());
+ verifyFaceAuthenticateCall();
}
@Test
@@ -938,7 +929,7 @@
mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
mKeyguardUpdateMonitor.setAssistantVisible(true);
- verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt());
+ verifyFaceAuthenticateCall();
mTestableLooper.processAllMessages();
clearInvocations(mFaceManager);
@@ -951,11 +942,7 @@
mKeyguardUpdateMonitor.handleKeyguardReset();
assertThat(mKeyguardUpdateMonitor.isFaceDetectionRunning()).isFalse();
- verify(mFaceManager, never()).authenticate(any(),
- any(),
- any(),
- any(),
- anyInt());
+ verifyFaceAuthenticateNeverCalled();
}
@Test
@@ -965,7 +952,7 @@
mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, true /* newlyUnlocked */,
KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */, new ArrayList<>());
keyguardIsVisible();
- verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt());
+ verifyFaceAuthenticateNeverCalled();
}
@Test
@@ -977,8 +964,8 @@
keyguardIsVisible();
mTestableLooper.processAllMessages();
- verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt());
- verify(mFaceManager, never()).detectFace(any(), any(), any());
+ verifyFaceAuthenticateNeverCalled();
+ verifyFaceDetectNeverCalled();
}
@Test
@@ -987,16 +974,14 @@
.onAuthenticationError(FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED, "");
// THEN doesn't authenticate immediately
- verify(mFingerprintManager, never()).authenticate(any(),
- any(), any(), any(), anyInt(), anyInt(), anyInt());
+ verifyFingerprintAuthenticateNeverCalled();
// WHEN all messages (with delays) are processed
mTestableLooper.moveTimeForward(HAL_POWER_PRESS_TIMEOUT);
mTestableLooper.processAllMessages();
// THEN fingerprint manager attempts to authenticate again
- verify(mFingerprintManager).authenticate(any(),
- any(), any(), any(), anyInt(), anyInt(), anyInt());
+ verifyFingerprintAuthenticateCall();
}
@Test
@@ -1008,7 +993,7 @@
setKeyguardBouncerVisibility(true);
mTestableLooper.processAllMessages();
- verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt());
+ verifyFaceAuthenticateNeverCalled();
}
@Test
@@ -1135,9 +1120,8 @@
mTestableLooper.processAllMessages();
keyguardIsVisible();
- verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt());
- verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(),
- anyInt());
+ verifyFaceAuthenticateCall();
+ verifyFingerprintAuthenticateCall();
when(mFingerprintManager.getLockoutModeForUser(eq(FINGERPRINT_SENSOR_ID), eq(newUser)))
.thenReturn(fingerprintLockoutMode);
@@ -1585,9 +1569,7 @@
public void testFaceDoesNotAuth_afterPinAttempt() {
mTestableLooper.processAllMessages();
mKeyguardUpdateMonitor.setCredentialAttempted();
- verify(mFingerprintManager, never()).authenticate(any(), any(), any(),
- any(), anyInt());
- verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt());
+ verifyFaceAuthenticateNeverCalled();
}
@Test
@@ -1962,9 +1944,8 @@
mTestableLooper.processAllMessages();
keyguardIsVisible();
- verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt());
- verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(),
- anyInt());
+ verifyFaceAuthenticateCall();
+ verifyFingerprintAuthenticateCall();
mKeyguardUpdateMonitor.onFaceAuthenticated(0, false);
// Make sure keyguard is going away after face auth attempt, and that it calls
@@ -1990,8 +1971,7 @@
mKeyguardUpdateMonitor.dispatchDreamingStopped();
mTestableLooper.processAllMessages();
- verify(mFaceManager, never()).authenticate(
- any(), any(), any(), any(), anyInt());
+ verifyFaceAuthenticateNeverCalled();
}
@Test
@@ -2004,15 +1984,14 @@
mTestableLooper.processAllMessages();
// THEN face auth isn't triggered
- verify(mFaceManager, never()).authenticate(
- any(), any(), any(), any(), anyInt());
+ verifyFaceAuthenticateNeverCalled();
// WHEN device wakes up from the power button
mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
// THEN face auth is triggered
- verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt());
+ verifyFaceAuthenticateCall();
}
@Test
@@ -2182,7 +2161,7 @@
mTestableLooper.processAllMessages();
keyguardIsVisible();
- verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt());
+ verifyFaceAuthenticateCall();
verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(),
anyInt());
@@ -2215,7 +2194,7 @@
mTestableLooper.processAllMessages();
keyguardIsVisible();
- verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt());
+ verifyFaceAuthenticateCall();
final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal);
mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel;
@@ -2486,6 +2465,40 @@
eq(false));
}
+ private void verifyFingerprintAuthenticateNeverCalled() {
+ verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
+ anyInt(), anyInt());
+ }
+
+ private void verifyFingerprintAuthenticateCall() {
+ verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(),
+ anyInt());
+ }
+
+ private void verifyFingerprintDetectNeverCalled() {
+ verify(mFingerprintManager, never()).detectFingerprint(any(), any(), any());
+ }
+
+ private void verifyFingerprintDetectCall() {
+ verify(mFingerprintManager).detectFingerprint(any(), any(), any());
+ }
+
+ private void verifyFaceAuthenticateNeverCalled() {
+ verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt());
+ }
+
+ private void verifyFaceAuthenticateCall() {
+ verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt());
+ }
+
+ private void verifyFaceDetectNeverCalled() {
+ verify(mFaceManager, never()).detectFace(any(), any(), any());
+ }
+
+ private void verifyFaceDetectCall() {
+ verify(mFaceManager).detectFace(any(), any(), any());
+ }
+
private void userDeviceLockDown() {
when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
when(mStrongAuthTracker.getStrongAuthForUser(mCurrentUserId))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt
index 777dd4e..ca6f426 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt
@@ -19,7 +19,7 @@
import android.provider.Settings
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
-import android.widget.ImageView
+import android.view.ViewGroup
import android.widget.SeekBar
import androidx.test.filters.SmallTest
import com.android.systemui.R
@@ -68,14 +68,14 @@
fun progressIsZero_clickIconEnd_seekBarProgressIncreaseOne_fontSizeScaled() {
fontScalingDialog.show()
- val iconEnd: ImageView = fontScalingDialog.findViewById(R.id.icon_end)!!
+ val iconEndFrame: ViewGroup = fontScalingDialog.findViewById(R.id.icon_end_frame)!!
val seekBarWithIconButtonsView: SeekBarWithIconButtonsView =
fontScalingDialog.findViewById(R.id.font_scaling_slider)!!
val seekBar: SeekBar = fontScalingDialog.findViewById(R.id.seekbar)!!
seekBarWithIconButtonsView.setProgress(0)
- iconEnd.performClick()
+ iconEndFrame.performClick()
val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def = */ 1.0f)
assertThat(seekBar.getProgress()).isEqualTo(1)
@@ -88,14 +88,14 @@
fun progressIsMax_clickIconStart_seekBarProgressDecreaseOne_fontSizeScaled() {
fontScalingDialog.show()
- val iconStart: ImageView = fontScalingDialog.findViewById(R.id.icon_start)!!
+ val iconStartFrame: ViewGroup = fontScalingDialog.findViewById(R.id.icon_start_frame)!!
val seekBarWithIconButtonsView: SeekBarWithIconButtonsView =
fontScalingDialog.findViewById(R.id.font_scaling_slider)!!
val seekBar: SeekBar = fontScalingDialog.findViewById(R.id.seekbar)!!
seekBarWithIconButtonsView.setProgress(fontSizeValueArray.size - 1)
- iconStart.performClick()
+ iconStartFrame.performClick()
val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def = */ 1.0f)
assertThat(seekBar.getProgress()).isEqualTo(fontSizeValueArray.size - 2)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt
index bce98cf..0574838 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt
@@ -15,27 +15,49 @@
*/
package com.android.systemui.biometrics
+import android.content.Context
import android.hardware.biometrics.BiometricAuthenticator
+import android.hardware.biometrics.SensorLocationInternal
+import android.hardware.biometrics.SensorProperties
+import android.hardware.display.DisplayManagerGlobal
+import android.hardware.fingerprint.FingerprintManager
+import android.hardware.fingerprint.FingerprintSensorProperties
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.os.Bundle
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
+import android.view.Display
+import android.view.DisplayAdjustments
+import android.view.DisplayInfo
+import android.view.Surface
import android.view.View
+import android.view.ViewGroup
import androidx.test.filters.SmallTest
+import com.airbnb.lottie.LottieAnimationView
import com.android.systemui.R
+import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATING_ANIMATING_IN
import com.android.systemui.SysuiTestCase
+import com.android.systemui.SysuiTestableContext
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
+import org.mockito.Mockito.mock
import org.mockito.Mockito.never
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
+import org.mockito.Mockito.`when` as whenEver
+
+private const val DISPLAY_ID = 2
+private const val SENSOR_ID = 1
@RunWith(AndroidTestingRunner::class)
@RunWithLooper(setAsMainLooper = true)
@@ -50,9 +72,22 @@
private lateinit var callback: AuthBiometricView.Callback
@Mock
+ private lateinit var fingerprintManager: FingerprintManager
+
+ @Mock
+ private lateinit var iconView: LottieAnimationView
+
+ @Mock
+ private lateinit var iconViewOverlay: LottieAnimationView
+
+ @Mock
+ private lateinit var iconLayoutParamSize: Pair<Int, Int>
+
+ @Mock
private lateinit var panelController: AuthPanelController
private lateinit var biometricView: AuthBiometricView
+ private lateinit var iconController: AuthBiometricFingerprintIconController
private fun createView(allowDeviceCredential: Boolean = false): AuthBiometricFingerprintView {
val view: AuthBiometricFingerprintView =
@@ -277,5 +312,186 @@
verify(callback).onAction(AuthBiometricView.Callback.ACTION_USE_DEVICE_CREDENTIAL)
}
+ private fun testWithSfpsDisplay(
+ isReverseDefaultRotation: Boolean = false,
+ inRearDisplayMode: Boolean = false,
+ isFolded: Boolean = false,
+ initInfo: DisplayInfo.() -> Unit = {},
+ block: () -> Unit
+ ) {
+ val displayInfo = DisplayInfo()
+ displayInfo.initInfo()
+
+ val dmGlobal = mock(DisplayManagerGlobal::class.java)
+ val display = Display(dmGlobal, DISPLAY_ID, displayInfo,
+ DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS)
+
+ whenEver(dmGlobal.getDisplayInfo(eq(DISPLAY_ID))).thenReturn(displayInfo)
+
+ val iconControllerContext = context.createDisplayContext(display) as SysuiTestableContext
+ iconControllerContext.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_reverseDefaultRotation,
+ isReverseDefaultRotation
+ )
+
+ val rearDisplayDeviceStates = if (inRearDisplayMode) intArrayOf(3) else intArrayOf()
+ iconControllerContext.orCreateTestableResources.addOverride(
+ com.android.internal.R.array.config_rearDisplayDeviceStates,
+ rearDisplayDeviceStates
+ )
+
+ val layoutParams = mock(ViewGroup.LayoutParams::class.java)
+ whenEver(iconView.layoutParams).thenReturn(layoutParams)
+ whenEver(iconViewOverlay.layoutParams).thenReturn(layoutParams)
+
+ var locations = listOf(SensorLocationInternal("", 2500, 0, 0))
+ whenEver(fingerprintManager.sensorPropertiesInternal)
+ .thenReturn(
+ listOf(
+ FingerprintSensorPropertiesInternal(
+ SENSOR_ID,
+ SensorProperties.STRENGTH_STRONG,
+ 5 /* maxEnrollmentsPerUser */,
+ listOf() /* componentInfo */,
+ FingerprintSensorProperties.TYPE_POWER_BUTTON,
+ true /* halControlsIllumination */,
+ true /* resetLockoutRequiresHardwareAuthToken */,
+ locations
+ )
+ )
+ )
+ iconControllerContext.addMockSystemService(Context.FINGERPRINT_SERVICE, fingerprintManager)
+
+ iconController = AuthBiometricFingerprintIconController(
+ iconControllerContext,
+ iconView,
+ iconViewOverlay
+ )
+ iconController.onFoldUpdated(isFolded)
+
+ biometricView.mIconController = iconController
+ block()
+ }
+
+ @Test
+ fun sfpsRearDisplay_showsCorrectAnimationAssetsAcrossRotations() {
+ testWithSfpsDisplay(
+ isReverseDefaultRotation = false,
+ inRearDisplayMode = true,
+ isFolded = false,
+ { rotation = Surface.ROTATION_0 }
+ ) { biometricView.updateState(STATE_AUTHENTICATING_ANIMATING_IN) }
+ testWithSfpsDisplay(
+ isReverseDefaultRotation = false,
+ inRearDisplayMode = true,
+ isFolded = false,
+ { rotation = Surface.ROTATION_90 }
+ ) { biometricView.updateState(STATE_AUTHENTICATING_ANIMATING_IN) }
+ testWithSfpsDisplay(
+ isReverseDefaultRotation = false,
+ inRearDisplayMode = true,
+ isFolded = false,
+ { rotation = Surface.ROTATION_180 }
+ ) { biometricView.updateState(STATE_AUTHENTICATING_ANIMATING_IN) }
+ testWithSfpsDisplay(
+ isReverseDefaultRotation = false,
+ inRearDisplayMode = true,
+ isFolded = false,
+ { rotation = Surface.ROTATION_270 }
+ ) { biometricView.updateState(STATE_AUTHENTICATING_ANIMATING_IN) }
+ val expectedLottieAssetOrder: List<Int> = listOf(
+ R.raw.biometricprompt_rear_landscape_base,
+ R.raw.biometricprompt_rear_portrait_reverse_base,
+ R.raw.biometricprompt_rear_landscape_base,
+ R.raw.biometricprompt_rear_portrait_base,
+ )
+
+ val lottieAssetCaptor: ArgumentCaptor<Int> = ArgumentCaptor.forClass(Int::class.java)
+ verify(iconView, times(4)).setAnimation(lottieAssetCaptor.capture())
+ val observedLottieAssetOrder: List<Int> = lottieAssetCaptor.getAllValues()
+ assertThat(observedLottieAssetOrder).containsExactlyElementsIn(expectedLottieAssetOrder)
+ .inOrder()
+ }
+
+ @Test
+ fun sfpsDefaultDisplayFolded_showsAnimationsAssetsCorrectlyAcrossRotations() {
+ testWithSfpsDisplay(
+ isReverseDefaultRotation = false,
+ inRearDisplayMode = false,
+ isFolded = true,
+ { rotation = Surface.ROTATION_0 }
+ ) { biometricView.updateState(STATE_AUTHENTICATING_ANIMATING_IN) }
+ testWithSfpsDisplay(
+ isReverseDefaultRotation = false,
+ inRearDisplayMode = false,
+ isFolded = true,
+ { rotation = Surface.ROTATION_90 }
+ ) { biometricView.updateState(STATE_AUTHENTICATING_ANIMATING_IN); }
+ testWithSfpsDisplay(
+ isReverseDefaultRotation = false,
+ inRearDisplayMode = false,
+ isFolded = true,
+ { rotation = Surface.ROTATION_180 }
+ ) { biometricView.updateState(STATE_AUTHENTICATING_ANIMATING_IN); }
+ testWithSfpsDisplay(
+ isReverseDefaultRotation = false,
+ inRearDisplayMode = false,
+ isFolded = true,
+ { rotation = Surface.ROTATION_270 }
+ ) { biometricView.updateState(STATE_AUTHENTICATING_ANIMATING_IN); }
+ val expectedLottieAssetOrder: List<Int> = listOf(
+ R.raw.biometricprompt_folded_base_default,
+ R.raw.biometricprompt_folded_base_topleft,
+ R.raw.biometricprompt_folded_base_default,
+ R.raw.biometricprompt_folded_base_bottomright,
+ )
+
+ val lottieAssetCaptor: ArgumentCaptor<Int> = ArgumentCaptor.forClass(Int::class.java)
+ verify(iconView, times(4)).setAnimation(lottieAssetCaptor.capture())
+ val observedLottieAssetOrder: List<Int> = lottieAssetCaptor.getAllValues()
+ assertThat(observedLottieAssetOrder).containsExactlyElementsIn(expectedLottieAssetOrder)
+ .inOrder()
+ }
+
+ @Test
+ fun sfpsDefaultDisplayUnfolded_showsAnimationsAssetsCorrectlyAcrossRotations() {
+ testWithSfpsDisplay(
+ isReverseDefaultRotation = false,
+ inRearDisplayMode = false,
+ isFolded = false,
+ { rotation = Surface.ROTATION_0 }
+ ) { biometricView.updateState(STATE_AUTHENTICATING_ANIMATING_IN) }
+ testWithSfpsDisplay(
+ isReverseDefaultRotation = false,
+ inRearDisplayMode = false,
+ isFolded = false,
+ { rotation = Surface.ROTATION_90 }
+ ) { biometricView.updateState(STATE_AUTHENTICATING_ANIMATING_IN) }
+ testWithSfpsDisplay(
+ isReverseDefaultRotation = false,
+ inRearDisplayMode = false,
+ isFolded = false,
+ { rotation = Surface.ROTATION_180 }
+ ) { biometricView.updateState(STATE_AUTHENTICATING_ANIMATING_IN) }
+ testWithSfpsDisplay(
+ isReverseDefaultRotation = false,
+ inRearDisplayMode = false,
+ isFolded = false,
+ { rotation = Surface.ROTATION_270 }
+ ) { biometricView.updateState(STATE_AUTHENTICATING_ANIMATING_IN) }
+ val expectedLottieAssetOrder: List<Int> = listOf(
+ R.raw.biometricprompt_landscape_base,
+ R.raw.biometricprompt_portrait_base_topleft,
+ R.raw.biometricprompt_landscape_base,
+ R.raw.biometricprompt_portrait_base_bottomright,
+ )
+
+ val lottieAssetCaptor: ArgumentCaptor<Int> = ArgumentCaptor.forClass(Int::class.java)
+ verify(iconView, times(4)).setAnimation(lottieAssetCaptor.capture())
+ val observedLottieAssetOrder: List<Int> = lottieAssetCaptor.getAllValues()
+ assertThat(observedLottieAssetOrder).containsExactlyElementsIn(expectedLottieAssetOrder)
+ .inOrder()
+ }
+
override fun waitForIdleSync() = TestableLooper.get(this).processAllMessages()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
index 612e557..6333a68 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
@@ -63,6 +63,7 @@
import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.recents.OverviewProxyService
+import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -97,6 +98,7 @@
@JvmField @Rule var rule = MockitoJUnit.rule()
+ @Mock lateinit var keyguardStateController: KeyguardStateController
@Mock lateinit var layoutInflater: LayoutInflater
@Mock lateinit var fingerprintManager: FingerprintManager
@Mock lateinit var windowManager: WindowManager
@@ -136,6 +138,7 @@
keyguardBouncerRepository = FakeKeyguardBouncerRepository()
alternateBouncerInteractor =
AlternateBouncerInteractor(
+ keyguardStateController,
keyguardBouncerRepository,
FakeBiometricSettingsRepository(),
FakeDeviceEntryFingerprintAuthRepository(),
@@ -169,6 +172,7 @@
isReverseDefaultRotation: Boolean = false,
initInfo: DisplayInfo.() -> Unit = {},
windowInsets: WindowInsets = insetsForSmallNavbar(),
+ inRearDisplayMode: Boolean = false,
block: () -> Unit
) {
this.deviceConfig = deviceConfig
@@ -229,6 +233,12 @@
isReverseDefaultRotation
)
+ val rearDisplayDeviceStates = if (inRearDisplayMode) intArrayOf(3) else intArrayOf()
+ sideFpsControllerContext.orCreateTestableResources.addOverride(
+ com.android.internal.R.array.config_rearDisplayDeviceStates,
+ rearDisplayDeviceStates
+ )
+
sideFpsController =
SideFpsController(
sideFpsControllerContext,
@@ -542,10 +552,62 @@
{ rotation = Surface.ROTATION_270 }
) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) }
+ @Test
+ fun verifiesSfpsIndicatorNotAddedInRearDisplayMode_0() =
+ testWithDisplay(
+ deviceConfig = DeviceConfig.Y_ALIGNED,
+ isReverseDefaultRotation = false,
+ { rotation = Surface.ROTATION_0 },
+ inRearDisplayMode = true,
+ ) {
+ verifySfpsIndicator_notAdded_InRearDisplayMode()
+ }
+
+ @Test
+ fun verifiesSfpsIndicatorNotAddedInRearDisplayMode_90() =
+ testWithDisplay(
+ deviceConfig = DeviceConfig.Y_ALIGNED,
+ isReverseDefaultRotation = false,
+ { rotation = Surface.ROTATION_90 },
+ inRearDisplayMode = true,
+ ) {
+ verifySfpsIndicator_notAdded_InRearDisplayMode()
+ }
+
+ @Test
+ fun verifiesSfpsIndicatorNotAddedInRearDisplayMode_180() =
+ testWithDisplay(
+ deviceConfig = DeviceConfig.Y_ALIGNED,
+ isReverseDefaultRotation = false,
+ { rotation = Surface.ROTATION_180 },
+ inRearDisplayMode = true,
+ ) {
+ verifySfpsIndicator_notAdded_InRearDisplayMode()
+ }
+
+ @Test
+ fun verifiesSfpsIndicatorNotAddedInRearDisplayMode_270() =
+ testWithDisplay(
+ deviceConfig = DeviceConfig.Y_ALIGNED,
+ isReverseDefaultRotation = false,
+ { rotation = Surface.ROTATION_270 },
+ inRearDisplayMode = true,
+ ) {
+ verifySfpsIndicator_notAdded_InRearDisplayMode()
+ }
+
private fun verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible: Boolean) {
sideFpsController.overlayOffsets = sensorLocation
}
+ private fun verifySfpsIndicator_notAdded_InRearDisplayMode() {
+ sideFpsController.overlayOffsets = sensorLocation
+ overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+ executor.runAllReady()
+
+ verify(windowManager, never()).addView(any(), any())
+ }
+
fun alternateBouncerVisibility_showAndHideSideFpsUI() = testWithDisplay {
// WHEN alternate bouncer is visible
keyguardBouncerRepository.setAlternateVisible(true)
@@ -582,7 +644,7 @@
* in other rotations have been omitted.
*/
@Test
- fun verifiesIndicatorPlacementForXAlignedSensor_0() {
+ fun verifiesIndicatorPlacementForXAlignedSensor_0() =
testWithDisplay(
deviceConfig = DeviceConfig.X_ALIGNED,
isReverseDefaultRotation = false,
@@ -599,7 +661,6 @@
assertThat(overlayViewParamsCaptor.value.x).isEqualTo(sensorLocation.sensorLocationX)
assertThat(overlayViewParamsCaptor.value.y).isEqualTo(0)
}
- }
/**
* {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_270
@@ -608,7 +669,7 @@
* correctly, tests for indicator placement in other rotations have been omitted.
*/
@Test
- fun verifiesIndicatorPlacementForXAlignedSensor_InReverseDefaultRotation_270() {
+ fun verifiesIndicatorPlacementForXAlignedSensor_InReverseDefaultRotation_270() =
testWithDisplay(
deviceConfig = DeviceConfig.X_ALIGNED,
isReverseDefaultRotation = true,
@@ -625,7 +686,6 @@
assertThat(overlayViewParamsCaptor.value.x).isEqualTo(sensorLocation.sensorLocationX)
assertThat(overlayViewParamsCaptor.value.y).isEqualTo(0)
}
- }
/**
* {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_0,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
index 54c9d39..86fb279 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
@@ -37,6 +37,7 @@
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.util.time.SystemClock
import kotlinx.coroutines.Dispatchers
@@ -92,6 +93,7 @@
)
mAlternateBouncerInteractor =
AlternateBouncerInteractor(
+ mock(KeyguardStateController::class.java),
keyguardBouncerRepository,
mock(BiometricSettingsRepository::class.java),
mock(DeviceEntryFingerprintAuthRepository::class.java),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogTest.java
index a61cecb..3503902 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogTest.java
@@ -20,14 +20,14 @@
import static org.mockito.Mockito.mock;
-import androidx.test.filters.SmallTest;
-
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
+import androidx.test.filters.SmallTest;
+
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
@@ -44,18 +44,21 @@
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class BroadcastDialogTest extends SysuiTestCase {
- private static final String SWITCH_APP = "Music";
+ private static final String CURRENT_BROADCAST_APP = "Music";
+ private static final String SWITCH_APP = "Files by Google";
private static final String TEST_PACKAGE = "com.google.android.apps.nbu.files";
private BroadcastDialog mBroadcastDialog;
private View mDialogView;
+ private TextView mTitle;
private TextView mSubTitle;
+ private Button mSwitchBroadcastAppButton;
private Button mChangeOutputButton;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mBroadcastDialog = new BroadcastDialog(mContext, mock(MediaOutputDialogFactory.class),
- SWITCH_APP, TEST_PACKAGE, mock(UiEventLogger.class));
+ CURRENT_BROADCAST_APP, TEST_PACKAGE, mock(UiEventLogger.class));
mBroadcastDialog.show();
mDialogView = mBroadcastDialog.mDialogView;
}
@@ -66,7 +69,15 @@
}
@Test
- public void onCreate_withCurrentApp_checkSwitchAppContent() {
+ public void onCreate_withCurrentApp_titleIsCurrentAppName() {
+ mTitle = mDialogView.requireViewById(R.id.dialog_title);
+
+ assertThat(mTitle.getText().toString()).isEqualTo(mContext.getString(
+ R.string.bt_le_audio_broadcast_dialog_title, CURRENT_BROADCAST_APP));
+ }
+
+ @Test
+ public void onCreate_withCurrentApp_subTitleIsSwitchAppName() {
mSubTitle = mDialogView.requireViewById(R.id.dialog_subtitle);
assertThat(mSubTitle.getText()).isEqualTo(
@@ -74,6 +85,14 @@
}
@Test
+ public void onCreate_withCurrentApp_switchBtnIsSwitchAppName() {
+ mSwitchBroadcastAppButton = mDialogView.requireViewById(R.id.switch_broadcast);
+
+ assertThat(mSwitchBroadcastAppButton.getText().toString()).isEqualTo(
+ mContext.getString(R.string.bt_le_audio_broadcast_dialog_switch_app, SWITCH_APP));
+ }
+
+ @Test
public void onClick_withChangeOutput_dismissBroadcastDialog() {
mChangeOutputButton = mDialogView.requireViewById(R.id.change_output);
mChangeOutputButton.performClick();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
index 2ed0346..eafe727 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
@@ -20,6 +20,7 @@
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.SeekBar;
@@ -42,6 +43,8 @@
private ImageView mIconStart;
private ImageView mIconEnd;
+ private ViewGroup mIconStartFrame;
+ private ViewGroup mIconEndFrame;
private SeekBar mSeekbar;
private SeekBarWithIconButtonsView mIconDiscreteSliderLinearLayout;
@@ -50,6 +53,8 @@
mIconDiscreteSliderLinearLayout = new SeekBarWithIconButtonsView(mContext);
mIconStart = mIconDiscreteSliderLinearLayout.findViewById(R.id.icon_start);
mIconEnd = mIconDiscreteSliderLinearLayout.findViewById(R.id.icon_end);
+ mIconStartFrame = mIconDiscreteSliderLinearLayout.findViewById(R.id.icon_start_frame);
+ mIconEndFrame = mIconDiscreteSliderLinearLayout.findViewById(R.id.icon_end_frame);
mSeekbar = mIconDiscreteSliderLinearLayout.findViewById(R.id.seekbar);
}
@@ -59,6 +64,8 @@
assertThat(mIconStart.isEnabled()).isFalse();
assertThat(mIconEnd.isEnabled()).isTrue();
+ assertThat(mIconStartFrame.isEnabled()).isFalse();
+ assertThat(mIconEndFrame.isEnabled()).isTrue();
}
@Test
@@ -67,6 +74,8 @@
assertThat(mIconEnd.isEnabled()).isFalse();
assertThat(mIconStart.isEnabled()).isTrue();
+ assertThat(mIconEndFrame.isEnabled()).isFalse();
+ assertThat(mIconStartFrame.isEnabled()).isTrue();
}
@Test
@@ -77,12 +86,15 @@
assertThat(mIconStart.isEnabled()).isTrue();
assertThat(mIconEnd.isEnabled()).isTrue();
+ assertThat(mIconStartFrame.isEnabled()).isTrue();
+ assertThat(mIconEndFrame.isEnabled()).isTrue();
}
@Test
public void clickIconEnd_currentProgressIsOneToMax_reachesMax() {
mIconDiscreteSliderLinearLayout.setProgress(mSeekbar.getMax() - 1);
- mIconEnd.performClick();
+
+ mIconEndFrame.performClick();
assertThat(mSeekbar.getProgress()).isEqualTo(mSeekbar.getMax());
}
@@ -90,7 +102,8 @@
@Test
public void clickIconStart_currentProgressIsOne_reachesZero() {
mIconDiscreteSliderLinearLayout.setProgress(1);
- mIconStart.performClick();
+
+ mIconStartFrame.performClick();
assertThat(mSeekbar.getProgress()).isEqualTo(0);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/devicepolicy/DevicePolicyManagerExtTest.kt b/packages/SystemUI/tests/src/com/android/systemui/devicepolicy/DevicePolicyManagerExtTest.kt
deleted file mode 100644
index 34d5661..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/devicepolicy/DevicePolicyManagerExtTest.kt
+++ /dev/null
@@ -1,93 +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.devicepolicy
-
-import android.app.admin.DevicePolicyManager
-import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FACE
-import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL
-import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE
-import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA
-import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL
-import androidx.test.filters.SmallTest
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(JUnit4::class)
-class DevicePolicyManagerExtTest {
-
- @Mock lateinit var devicePolicyManager: DevicePolicyManager
- @Mock private lateinit var userTracker: UserTracker
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
-
- whenever(userTracker.userId).thenReturn(CURRENT_USER_ID)
- }
-
- // region areKeyguardShortcutsDisabled
- @Test
- fun areKeyguardShortcutsDisabled_noDisabledKeyguardFeature_shouldReturnFalse() {
- whenever(devicePolicyManager.getKeyguardDisabledFeatures(eq(null), anyInt()))
- .thenReturn(KEYGUARD_DISABLE_FEATURES_NONE)
-
- assertThat(devicePolicyManager.areKeyguardShortcutsDisabled(userId = CURRENT_USER_ID))
- .isFalse()
- }
-
- @Test
- fun areKeyguardShortcutsDisabled_otherDisabledKeyguardFeatures_shouldReturnFalse() {
- whenever(devicePolicyManager.getKeyguardDisabledFeatures(eq(null), anyInt()))
- .thenReturn(KEYGUARD_DISABLE_SECURE_CAMERA or KEYGUARD_DISABLE_FACE)
-
- assertThat(devicePolicyManager.areKeyguardShortcutsDisabled(userId = CURRENT_USER_ID))
- .isFalse()
- }
-
- @Test
- fun areKeyguardShortcutsDisabled_disabledShortcutsKeyguardFeature_shouldReturnTrue() {
- whenever(devicePolicyManager.getKeyguardDisabledFeatures(eq(null), anyInt()))
- .thenReturn(KEYGUARD_DISABLE_SHORTCUTS_ALL)
-
- assertThat(devicePolicyManager.areKeyguardShortcutsDisabled(userId = CURRENT_USER_ID))
- .isTrue()
- }
-
- @Test
- fun areKeyguardShortcutsDisabled_disabledAllKeyguardFeatures_shouldReturnTrue() {
- whenever(devicePolicyManager.getKeyguardDisabledFeatures(eq(null), anyInt()))
- .thenReturn(KEYGUARD_DISABLE_FEATURES_ALL)
-
- assertThat(devicePolicyManager.areKeyguardShortcutsDisabled(userId = CURRENT_USER_ID))
- .isTrue()
- }
- // endregion
-
- private companion object {
- const val CURRENT_USER_ID = 123
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index 9d4bef6..9e70c27 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -20,6 +20,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -231,6 +232,31 @@
verify(mWindowManager).addView(any(), any());
}
+ // Validates that {@link DreamOverlayService} properly handles the case where the dream's
+ // window is no longer valid by the time start is called.
+ @Test
+ public void testInvalidWindowAddStart() throws Exception {
+ final IDreamOverlayClient client = getClient();
+
+ doThrow(new WindowManager.BadTokenException()).when(mWindowManager).addView(any(), any());
+ // Inform the overlay service of dream starting.
+ client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+ false /*shouldShowComplication*/);
+ mMainExecutor.runAllReady();
+
+ verify(mWindowManager).addView(any(), any());
+
+ verify(mStateController).setOverlayActive(false);
+ verify(mStateController).setLowLightActive(false);
+ verify(mStateController).setEntryAnimationsFinished(false);
+
+ verify(mStateController, never()).setOverlayActive(true);
+ verify(mUiEventLogger, never()).log(
+ DreamOverlayService.DreamOverlayEvent.DREAM_OVERLAY_COMPLETE_START);
+
+ verify(mDreamOverlayCallbackController, never()).onStartDream();
+ }
+
@Test
public void testDreamOverlayContainerViewControllerInitialized() throws Exception {
final IDreamOverlayClient client = getClient();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
index 18e80ea..1365132 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
@@ -28,6 +28,8 @@
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepositoryImpl
import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.util.time.SystemClock
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -50,6 +52,7 @@
private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
private lateinit var deviceEntryFingerprintAuthRepository:
FakeDeviceEntryFingerprintAuthRepository
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var systemClock: SystemClock
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var bouncerLogger: TableLogBuffer
@@ -70,6 +73,7 @@
featureFlags = FakeFeatureFlags().apply { this.set(Flags.MODERN_ALTERNATE_BOUNCER, true) }
underTest =
AlternateBouncerInteractor(
+ keyguardStateController,
bouncerRepository,
biometricSettingsRepository,
deviceEntryFingerprintAuthRepository,
@@ -134,6 +138,14 @@
}
@Test
+ fun canShowAlternateBouncerForFingerprint_butCanDismissLockScreen() {
+ givenCanShowAlternateBouncer()
+ whenever(keyguardStateController.isUnlocked).thenReturn(true)
+
+ assertFalse(underTest.canShowAlternateBouncerForFingerprint())
+ }
+
+ @Test
fun show_whenCannotShow() {
givenCannotShowAlternateBouncer()
@@ -163,6 +175,7 @@
biometricSettingsRepository.setStrongBiometricAllowed(true)
biometricSettingsRepository.setFingerprintEnabledByDevicePolicy(true)
deviceEntryFingerprintAuthRepository.setLockedOut(false)
+ whenever(keyguardStateController.isUnlocked).thenReturn(false)
}
private fun givenCannotShowAlternateBouncer() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
index a72634b..7f57077 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.media.controls.ui
import android.app.PendingIntent
+import android.content.res.ColorStateList
import android.content.res.Configuration
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -26,9 +27,9 @@
import com.android.internal.logging.InstanceId
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -49,7 +50,7 @@
import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
@@ -89,7 +90,6 @@
@Mock lateinit var mediaHostStatesManager: MediaHostStatesManager
@Mock lateinit var mediaHostState: MediaHostState
@Mock lateinit var activityStarter: ActivityStarter
- @Mock @Main private lateinit var executor: DelayableExecutor
@Mock lateinit var mediaDataManager: MediaDataManager
@Mock lateinit var configurationController: ConfigurationController
@Mock lateinit var falsingCollector: FalsingCollector
@@ -113,11 +113,15 @@
private val clock = FakeSystemClock()
private lateinit var mediaCarouselController: MediaCarouselController
+ private lateinit var mainExecutor: FakeExecutor
+ private lateinit var backgroundExecutor: FakeExecutor
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
transitionRepository = FakeKeyguardTransitionRepository()
+ mainExecutor = FakeExecutor(clock)
+ backgroundExecutor = FakeExecutor(clock)
mediaCarouselController =
MediaCarouselController(
context,
@@ -126,7 +130,8 @@
mediaHostStatesManager,
activityStarter,
clock,
- executor,
+ mainExecutor,
+ backgroundExecutor,
mediaDataManager,
configurationController,
falsingCollector,
@@ -401,6 +406,7 @@
resumption = true
)
)
+ runAllReady()
assertEquals(
MediaPlayerData.getMediaPlayerIndex("paused local"),
@@ -510,6 +516,8 @@
false
)
mediaCarouselController.shouldScrollToKey = true
+ runAllReady()
+
// switching between media players.
listener.value.onMediaDataLoaded(
"playing local",
@@ -531,6 +539,7 @@
resumption = false
)
)
+ runAllReady()
assertEquals(
MediaPlayerData.getMediaPlayerIndex("paused local"),
@@ -555,6 +564,7 @@
resumption = false
)
)
+ runAllReady()
var playerIndex = MediaPlayerData.getMediaPlayerIndex("playing local")
assertEquals(
@@ -577,6 +587,8 @@
packageName = "PACKAGE_NAME"
)
)
+ runAllReady()
+
playerIndex = MediaPlayerData.getMediaPlayerIndex("playing local")
assertEquals(playerIndex, 0)
}
@@ -674,6 +686,8 @@
@Test
fun testOnConfigChanged_playersAreAddedBack() {
+ mediaCarouselController.pageIndicator = pageIndicator
+
listener.value.onMediaDataLoaded(
"playing local",
null,
@@ -694,11 +708,15 @@
resumption = false
)
)
+ runAllReady()
val playersSize = MediaPlayerData.players().size
configListener.value.onConfigChanged(Configuration())
+ runAllReady()
+ verify(pageIndicator).tintList =
+ ColorStateList.valueOf(context.getColor(R.color.media_paging_indicator))
assertEquals(playersSize, MediaPlayerData.players().size)
assertEquals(
MediaPlayerData.getMediaPlayerIndex("playing local"),
@@ -707,6 +725,93 @@
}
@Test
+ fun testOnUiModeChanged_playersAreAddedBack() {
+ mediaCarouselController.pageIndicator = pageIndicator
+
+ listener.value.onMediaDataLoaded(
+ "paused local",
+ null,
+ DATA.copy(
+ active = true,
+ isPlaying = false,
+ playbackLocation = MediaData.PLAYBACK_LOCAL,
+ resumption = false
+ )
+ )
+ runAllReady()
+
+ val playersSize = MediaPlayerData.players().size
+ configListener.value.onUiModeChanged()
+ runAllReady()
+
+ verify(pageIndicator).tintList =
+ ColorStateList.valueOf(context.getColor(R.color.media_paging_indicator))
+ assertEquals(playersSize, MediaPlayerData.players().size)
+ assertEquals(
+ MediaPlayerData.getMediaPlayerIndex("paused local"),
+ mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
+ )
+ }
+
+ @Test
+ fun testOnDensityOrFontScaleChanged_playersAreAddedBack() {
+ mediaCarouselController.pageIndicator = pageIndicator
+
+ listener.value.onMediaDataLoaded(
+ "paused local",
+ null,
+ DATA.copy(
+ active = true,
+ isPlaying = false,
+ playbackLocation = MediaData.PLAYBACK_LOCAL,
+ resumption = false
+ )
+ )
+ runAllReady()
+
+ val playersSize = MediaPlayerData.players().size
+ configListener.value.onDensityOrFontScaleChanged()
+ runAllReady()
+
+ verify(pageIndicator).tintList =
+ ColorStateList.valueOf(context.getColor(R.color.media_paging_indicator))
+ assertEquals(playersSize, MediaPlayerData.players().size)
+ assertEquals(
+ MediaPlayerData.getMediaPlayerIndex("paused local"),
+ mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
+ )
+ }
+
+ @Test
+ fun testOnThemeChanged_playersAreAddedBack() {
+ mediaCarouselController.pageIndicator = pageIndicator
+
+ listener.value.onMediaDataLoaded(
+ "paused local",
+ null,
+ DATA.copy(
+ active = true,
+ isPlaying = false,
+ playbackLocation = MediaData.PLAYBACK_LOCAL,
+ resumption = false
+ )
+ )
+ runAllReady()
+
+ val playersSize = MediaPlayerData.players().size
+ configListener.value.onThemeChanged()
+ runAllReady()
+
+ verify(pageIndicator).tintList =
+ ColorStateList.valueOf(context.getColor(R.color.media_paging_indicator))
+ assertEquals(playersSize, MediaPlayerData.players().size)
+ assertEquals(
+ MediaPlayerData.getMediaPlayerIndex("paused local"),
+ mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
+ )
+ }
+
+ @Test
fun testRecommendation_persistentEnabled_newSmartspaceLoaded_updatesSort() {
testRecommendation_persistentEnabled_inactiveSmartspaceDataLoaded_isAdded()
@@ -832,4 +937,9 @@
// Verify that seekbar listening attribute in media control panel is set to false.
verify(panel, times(MediaPlayerData.players().size)).listening = false
}
+
+ private fun runAllReady() {
+ backgroundExecutor.runAllReady()
+ mainExecutor.runAllReady()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index 52b29ac..39c4e06 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -16,7 +16,6 @@
package com.android.systemui.notetask
import android.app.KeyguardManager
-import android.app.admin.DevicePolicyManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
@@ -30,7 +29,6 @@
import com.android.systemui.notetask.NoteTaskController.ShowNoteTaskUiEvent
import com.android.systemui.notetask.NoteTaskInfoResolver.NoteTaskInfo
import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
-import com.android.systemui.settings.UserTracker
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
@@ -41,7 +39,6 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
@@ -67,8 +64,6 @@
@Mock lateinit var optionalUserManager: Optional<UserManager>
@Mock lateinit var userManager: UserManager
@Mock lateinit var uiEventLogger: UiEventLogger
- @Mock private lateinit var userTracker: UserTracker
- @Mock private lateinit var devicePolicyManager: DevicePolicyManager
@Before
fun setUp() {
@@ -80,13 +75,6 @@
whenever(optionalKeyguardManager.orElse(null)).thenReturn(keyguardManager)
whenever(optionalUserManager.orElse(null)).thenReturn(userManager)
whenever(userManager.isUserUnlocked).thenReturn(true)
- whenever(
- devicePolicyManager.getKeyguardDisabledFeatures(
- /* admin= */ eq(null),
- /* userHandle= */ anyInt()
- )
- )
- .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE)
}
private fun createNoteTaskController(isEnabled: Boolean = true): NoteTaskController {
@@ -96,10 +84,8 @@
optionalBubbles = optionalBubbles,
optionalKeyguardManager = optionalKeyguardManager,
optionalUserManager = optionalUserManager,
- devicePolicyManager = devicePolicyManager,
isEnabled = isEnabled,
uiEventLogger = uiEventLogger,
- userTracker = userTracker,
)
}
@@ -305,86 +291,6 @@
}
// endregion
- // region keyguard policy
- @Test
- fun showNoteTask_keyguardLocked_keyguardDisableShortcutsAll_shouldDoNothing() {
- whenever(keyguardManager.isKeyguardLocked).thenReturn(true)
- whenever(
- devicePolicyManager.getKeyguardDisabledFeatures(
- /* admin= */ eq(null),
- /* userHandle= */ anyInt()
- )
- )
- .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL)
-
- createNoteTaskController().showNoteTask(isInMultiWindowMode = false, uiEvent = null)
-
- verifyZeroInteractions(context, bubbles, uiEventLogger)
- }
-
- @Test
- fun showNoteTask_keyguardLocked_keyguardDisableFeaturesAll_shouldDoNothing() {
- whenever(keyguardManager.isKeyguardLocked).thenReturn(true)
- whenever(
- devicePolicyManager.getKeyguardDisabledFeatures(
- /* admin= */ eq(null),
- /* userHandle= */ anyInt()
- )
- )
- .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL)
-
- createNoteTaskController().showNoteTask(isInMultiWindowMode = false, uiEvent = null)
-
- verifyZeroInteractions(context, bubbles, uiEventLogger)
- }
-
- @Test
- fun showNoteTask_keyguardUnlocked_keyguardDisableShortcutsAll_shouldStartBubble() {
- whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
- whenever(
- devicePolicyManager.getKeyguardDisabledFeatures(
- /* admin= */ eq(null),
- /* userHandle= */ anyInt()
- )
- )
- .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL)
-
- createNoteTaskController().showNoteTask(isInMultiWindowMode = false, uiEvent = null)
-
- val intentCaptor = argumentCaptor<Intent>()
- verify(bubbles).showOrHideAppBubble(capture(intentCaptor))
- intentCaptor.value.let { intent ->
- assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE)
- assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
- assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
- assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue()
- }
- }
-
- @Test
- fun showNoteTask_keyguardUnlocked_keyguardDisableFeaturesAll_shouldStartBubble() {
- whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
- whenever(
- devicePolicyManager.getKeyguardDisabledFeatures(
- /* admin= */ eq(null),
- /* userHandle= */ anyInt()
- )
- )
- .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL)
-
- createNoteTaskController().showNoteTask(isInMultiWindowMode = false, uiEvent = null)
-
- val intentCaptor = argumentCaptor<Intent>()
- verify(bubbles).showOrHideAppBubble(capture(intentCaptor))
- intentCaptor.value.let { intent ->
- assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE)
- assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
- assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
- assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue()
- }
- }
- // endregion
-
private companion object {
const val NOTES_PACKAGE_NAME = "com.android.note.app"
const val NOTES_UID = 123456
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index 5058373..3d55c51 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -71,7 +71,7 @@
@Mock
private QSPanel mQSPanel;
@Mock
- private QSTileHost mQSTileHost;
+ private QSHost mQSHost;
@Mock
private QSCustomizerController mQSCustomizerController;
@Mock
@@ -105,7 +105,7 @@
/** Implementation needed to ensure we have a reflectively-available class name. */
private class TestableQSPanelControllerBase extends QSPanelControllerBase<QSPanel> {
- protected TestableQSPanelControllerBase(QSPanel view, QSTileHost host,
+ protected TestableQSPanelControllerBase(QSPanel view, QSHost host,
QSCustomizerController qsCustomizerController, MediaHost mediaHost,
MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
DumpManager dumpManager) {
@@ -130,8 +130,8 @@
when(mQSPanel.getOrCreateTileLayout()).thenReturn(mPagedTileLayout);
when(mQSPanel.getTileLayout()).thenReturn(mPagedTileLayout);
when(mQSTile.getTileSpec()).thenReturn("dnd");
- when(mQSTileHost.getTiles()).thenReturn(Collections.singleton(mQSTile));
- when(mQSTileHost.createTileView(any(), eq(mQSTile), anyBoolean())).thenReturn(mQSTileView);
+ when(mQSHost.getTiles()).thenReturn(Collections.singleton(mQSTile));
+ when(mQSHost.createTileView(any(), eq(mQSTile), anyBoolean())).thenReturn(mQSTileView);
when(mQSTileRevealControllerFactory.create(any(), any()))
.thenReturn(mQSTileRevealController);
when(mMediaHost.getDisappearParameters()).thenReturn(new DisappearParameters());
@@ -142,7 +142,7 @@
return null;
}).when(mQSPanel).setListening(anyBoolean());
- mController = new TestableQSPanelControllerBase(mQSPanel, mQSTileHost,
+ mController = new TestableQSPanelControllerBase(mQSPanel, mQSHost,
mQSCustomizerController, mMediaHost,
mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager);
@@ -155,7 +155,7 @@
mController.onViewDetached();
QSPanelControllerBase<QSPanel> controller = new TestableQSPanelControllerBase(mQSPanel,
- mQSTileHost, mQSCustomizerController, mMediaHost,
+ mQSHost, mQSCustomizerController, mMediaHost,
mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager) {
@Override
protected QSTileRevealController createTileRevealController() {
@@ -250,7 +250,7 @@
when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(false);
when(mQSPanel.getDumpableTag()).thenReturn("QSPanelLandscape");
- mController = new TestableQSPanelControllerBase(mQSPanel, mQSTileHost,
+ mController = new TestableQSPanelControllerBase(mQSPanel, mQSHost,
mQSCustomizerController, mMediaHost,
mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager);
mController.init();
@@ -259,7 +259,7 @@
when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(true);
when(mQSPanel.getDumpableTag()).thenReturn("QSPanelPortrait");
- mController = new TestableQSPanelControllerBase(mQSPanel, mQSTileHost,
+ mController = new TestableQSPanelControllerBase(mQSPanel, mQSHost,
mQSCustomizerController, mMediaHost,
mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager);
mController.init();
@@ -291,7 +291,7 @@
@Test
public void testRefreshAllTilesDoesntRefreshListeningTiles() {
- when(mQSTileHost.getTiles()).thenReturn(List.of(mQSTile, mOtherTile));
+ when(mQSHost.getTiles()).thenReturn(List.of(mQSTile, mOtherTile));
mController.setTiles();
when(mQSTile.isListening()).thenReturn(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
index 5c5fbc9..a0d8f98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -40,7 +40,7 @@
@Mock private lateinit var qsPanel: QSPanel
@Mock private lateinit var tunerService: TunerService
- @Mock private lateinit var qsTileHost: QSTileHost
+ @Mock private lateinit var qsHost: QSHost
@Mock private lateinit var qsCustomizerController: QSCustomizerController
@Mock private lateinit var qsTileRevealControllerFactory: QSTileRevealController.Factory
@Mock private lateinit var dumpManager: DumpManager
@@ -79,7 +79,7 @@
controller = QSPanelController(
qsPanel,
tunerService,
- qsTileHost,
+ qsHost,
qsCustomizerController,
/* usingMediaPlayer= */ true,
mediaHost,
@@ -109,7 +109,7 @@
@Test
fun testSetListeningDoesntRefreshListeningTiles() {
- whenever(qsTileHost.getTiles()).thenReturn(listOf(tile, otherTile))
+ whenever(qsHost.getTiles()).thenReturn(listOf(tile, otherTile))
controller.setTiles()
whenever(tile.isListening()).thenReturn(false)
whenever(otherTile.isListening()).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index fb1a720..34d2b14 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -69,7 +69,6 @@
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.AutoTileManager;
import com.android.systemui.statusbar.phone.CentralSurfaces;
-import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.FakeSharedPreferences;
import com.android.systemui.util.concurrency.FakeExecutor;
@@ -100,11 +99,9 @@
private static ComponentName CUSTOM_TILE =
ComponentName.unflattenFromString("TEST_PKG/.TEST_CLS");
private static final String CUSTOM_TILE_SPEC = CustomTile.toSpec(CUSTOM_TILE);
- private static final String SETTING = QSTileHost.TILES_SETTING;
+ private static final String SETTING = QSHost.TILES_SETTING;
@Mock
- private StatusBarIconController mIconController;
- @Mock
private QSFactory mDefaultFactory;
@Mock
private PluginManager mPluginManager;
@@ -167,7 +164,7 @@
mSecureSettings = new FakeSettings();
saveSetting("");
- mQSTileHost = new TestQSTileHost(mContext, mIconController, mDefaultFactory, mMainExecutor,
+ mQSTileHost = new TestQSTileHost(mContext, mDefaultFactory, mMainExecutor,
mPluginManager, mTunerService, mAutoTiles, mDumpManager, mCentralSurfaces,
mQSLogger, mUiEventLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister,
mTileServiceRequestControllerBuilder, mTileLifecycleManagerFactory,
@@ -248,44 +245,44 @@
public void testRemoveWifiAndCellularWithoutInternet() {
saveSetting("wifi, spec1, cell, spec2");
- assertEquals("internet", mQSTileHost.mTileSpecs.get(0));
- assertEquals("spec1", mQSTileHost.mTileSpecs.get(1));
- assertEquals("spec2", mQSTileHost.mTileSpecs.get(2));
+ assertEquals("internet", mQSTileHost.getSpecs().get(0));
+ assertEquals("spec1", mQSTileHost.getSpecs().get(1));
+ assertEquals("spec2", mQSTileHost.getSpecs().get(2));
}
@Test
public void testRemoveWifiAndCellularWithInternet() {
saveSetting("wifi, spec1, cell, spec2, internet");
- assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
- assertEquals("spec2", mQSTileHost.mTileSpecs.get(1));
- assertEquals("internet", mQSTileHost.mTileSpecs.get(2));
+ assertEquals("spec1", mQSTileHost.getSpecs().get(0));
+ assertEquals("spec2", mQSTileHost.getSpecs().get(1));
+ assertEquals("internet", mQSTileHost.getSpecs().get(2));
}
@Test
public void testRemoveWifiWithoutInternet() {
saveSetting("spec1, wifi, spec2");
- assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
- assertEquals("internet", mQSTileHost.mTileSpecs.get(1));
- assertEquals("spec2", mQSTileHost.mTileSpecs.get(2));
+ assertEquals("spec1", mQSTileHost.getSpecs().get(0));
+ assertEquals("internet", mQSTileHost.getSpecs().get(1));
+ assertEquals("spec2", mQSTileHost.getSpecs().get(2));
}
@Test
public void testRemoveCellWithInternet() {
saveSetting("spec1, spec2, cell, internet");
- assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
- assertEquals("spec2", mQSTileHost.mTileSpecs.get(1));
- assertEquals("internet", mQSTileHost.mTileSpecs.get(2));
+ assertEquals("spec1", mQSTileHost.getSpecs().get(0));
+ assertEquals("spec2", mQSTileHost.getSpecs().get(1));
+ assertEquals("internet", mQSTileHost.getSpecs().get(2));
}
@Test
public void testNoWifiNoCellularNoInternet() {
saveSetting("spec1,spec2");
- assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
- assertEquals("spec2", mQSTileHost.mTileSpecs.get(1));
+ assertEquals("spec1", mQSTileHost.getSpecs().get(0));
+ assertEquals("spec2", mQSTileHost.getSpecs().get(1));
}
@Test
@@ -332,9 +329,9 @@
mQSTileHost.addTile("spec1");
- assertEquals(2, mQSTileHost.mTileSpecs.size());
- assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
- assertEquals("spec2", mQSTileHost.mTileSpecs.get(1));
+ assertEquals(2, mQSTileHost.getSpecs().size());
+ assertEquals("spec1", mQSTileHost.getSpecs().get(0));
+ assertEquals("spec2", mQSTileHost.getSpecs().get(1));
}
@Test
@@ -346,10 +343,10 @@
mQSTileHost.addTile("spec2", 1);
mMainExecutor.runAllReady();
- assertEquals(3, mQSTileHost.mTileSpecs.size());
- assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
- assertEquals("spec2", mQSTileHost.mTileSpecs.get(1));
- assertEquals("spec3", mQSTileHost.mTileSpecs.get(2));
+ assertEquals(3, mQSTileHost.getSpecs().size());
+ assertEquals("spec1", mQSTileHost.getSpecs().get(0));
+ assertEquals("spec2", mQSTileHost.getSpecs().get(1));
+ assertEquals("spec3", mQSTileHost.getSpecs().get(2));
}
@Test
@@ -361,10 +358,10 @@
mQSTileHost.addTile("spec2", 100);
mMainExecutor.runAllReady();
- assertEquals(3, mQSTileHost.mTileSpecs.size());
- assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
- assertEquals("spec3", mQSTileHost.mTileSpecs.get(1));
- assertEquals("spec2", mQSTileHost.mTileSpecs.get(2));
+ assertEquals(3, mQSTileHost.getSpecs().size());
+ assertEquals("spec1", mQSTileHost.getSpecs().get(0));
+ assertEquals("spec3", mQSTileHost.getSpecs().get(1));
+ assertEquals("spec2", mQSTileHost.getSpecs().get(2));
}
@Test
@@ -376,10 +373,10 @@
mQSTileHost.addTile("spec2", QSTileHost.POSITION_AT_END);
mMainExecutor.runAllReady();
- assertEquals(3, mQSTileHost.mTileSpecs.size());
- assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
- assertEquals("spec3", mQSTileHost.mTileSpecs.get(1));
- assertEquals("spec2", mQSTileHost.mTileSpecs.get(2));
+ assertEquals(3, mQSTileHost.getSpecs().size());
+ assertEquals("spec1", mQSTileHost.getSpecs().get(0));
+ assertEquals("spec3", mQSTileHost.getSpecs().get(1));
+ assertEquals("spec2", mQSTileHost.getSpecs().get(2));
}
@Test
@@ -389,8 +386,8 @@
mQSTileHost.addTile(CUSTOM_TILE, /* end */ false);
mMainExecutor.runAllReady();
- assertEquals(1, mQSTileHost.mTileSpecs.size());
- assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.mTileSpecs.get(0));
+ assertEquals(1, mQSTileHost.getSpecs().size());
+ assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.getSpecs().get(0));
}
@Test
@@ -400,8 +397,8 @@
mQSTileHost.addTile(CUSTOM_TILE);
mMainExecutor.runAllReady();
- assertEquals(2, mQSTileHost.mTileSpecs.size());
- assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.mTileSpecs.get(0));
+ assertEquals(2, mQSTileHost.getSpecs().size());
+ assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.getSpecs().get(0));
}
@Test
@@ -411,8 +408,8 @@
mQSTileHost.addTile(CUSTOM_TILE, /* end */ false);
mMainExecutor.runAllReady();
- assertEquals(2, mQSTileHost.mTileSpecs.size());
- assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.mTileSpecs.get(0));
+ assertEquals(2, mQSTileHost.getSpecs().size());
+ assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.getSpecs().get(0));
}
@Test
@@ -422,8 +419,8 @@
mQSTileHost.addTile(CUSTOM_TILE, /* end */ true);
mMainExecutor.runAllReady();
- assertEquals(2, mQSTileHost.mTileSpecs.size());
- assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.mTileSpecs.get(1));
+ assertEquals(2, mQSTileHost.getSpecs().size());
+ assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.getSpecs().get(1));
}
@Test
@@ -478,7 +475,7 @@
mQSTileHost.removeTiles(List.of("spec1", "spec2"));
mMainExecutor.runAllReady();
- assertEquals(List.of("spec3"), mQSTileHost.mTileSpecs);
+ assertEquals(List.of("spec3"), mQSTileHost.getSpecs());
}
@Test
@@ -488,7 +485,7 @@
mQSTileHost.removeTile("spec3");
mMainExecutor.runAllReady();
- assertEquals(List.of("spec2"), mQSTileHost.mTileSpecs);
+ assertEquals(List.of("spec2"), mQSTileHost.getSpecs());
assertEquals("spec2", getSetting());
}
@@ -497,10 +494,10 @@
saveSetting("spec1,spec2");
mQSTileHost.addTile("spec3");
- assertEquals(List.of("spec1", "spec2"), mQSTileHost.mTileSpecs);
+ assertEquals(List.of("spec1", "spec2"), mQSTileHost.getSpecs());
mMainExecutor.runAllReady();
- assertEquals(List.of("spec1", "spec2", "spec3"), mQSTileHost.mTileSpecs);
+ assertEquals(List.of("spec1", "spec2", "spec3"), mQSTileHost.getSpecs());
}
@Test
@@ -508,10 +505,10 @@
saveSetting("spec1,spec2");
mQSTileHost.removeTile("spec1");
- assertEquals(List.of("spec1", "spec2"), mQSTileHost.mTileSpecs);
+ assertEquals(List.of("spec1", "spec2"), mQSTileHost.getSpecs());
mMainExecutor.runAllReady();
- assertEquals(List.of("spec2"), mQSTileHost.mTileSpecs);
+ assertEquals(List.of("spec2"), mQSTileHost.getSpecs());
}
@Test
@@ -519,10 +516,10 @@
saveSetting("spec1,spec2,spec3");
mQSTileHost.removeTiles(List.of("spec3", "spec1"));
- assertEquals(List.of("spec1", "spec2", "spec3"), mQSTileHost.mTileSpecs);
+ assertEquals(List.of("spec1", "spec2", "spec3"), mQSTileHost.getSpecs());
mMainExecutor.runAllReady();
- assertEquals(List.of("spec2"), mQSTileHost.mTileSpecs);
+ assertEquals(List.of("spec2"), mQSTileHost.getSpecs());
}
@Test
@@ -530,17 +527,17 @@
saveSetting("spec1," + CUSTOM_TILE_SPEC);
mQSTileHost.removeTileByUser(CUSTOM_TILE);
- assertEquals(List.of("spec1", CUSTOM_TILE_SPEC), mQSTileHost.mTileSpecs);
+ assertEquals(List.of("spec1", CUSTOM_TILE_SPEC), mQSTileHost.getSpecs());
mMainExecutor.runAllReady();
- assertEquals(List.of("spec1"), mQSTileHost.mTileSpecs);
+ assertEquals(List.of("spec1"), mQSTileHost.getSpecs());
}
@Test
public void testNonValidTileNotStoredInSettings() {
saveSetting("spec1,not-valid");
- assertEquals(List.of("spec1"), mQSTileHost.mTileSpecs);
+ assertEquals(List.of("spec1"), mQSTileHost.getSpecs());
assertEquals("spec1", getSetting());
}
@@ -548,14 +545,14 @@
public void testNotAvailableTileNotStoredInSettings() {
saveSetting("spec1,na");
- assertEquals(List.of("spec1"), mQSTileHost.mTileSpecs);
+ assertEquals(List.of("spec1"), mQSTileHost.getSpecs());
assertEquals("spec1", getSetting());
}
@Test
public void testIsTileAdded_true() {
int user = mUserTracker.getUserId();
- getSharedPreferenecesForUser(user)
+ getSharedPreferencesForUser(user)
.edit()
.putBoolean(CUSTOM_TILE.flattenToString(), true)
.apply();
@@ -566,7 +563,7 @@
@Test
public void testIsTileAdded_false() {
int user = mUserTracker.getUserId();
- getSharedPreferenecesForUser(user)
+ getSharedPreferencesForUser(user)
.edit()
.putBoolean(CUSTOM_TILE.flattenToString(), false)
.apply();
@@ -597,7 +594,7 @@
int user = mUserTracker.getUserId();
mQSTileHost.setTileAdded(CUSTOM_TILE, user, true);
- assertTrue(getSharedPreferenecesForUser(user)
+ assertTrue(getSharedPreferencesForUser(user)
.getBoolean(CUSTOM_TILE.flattenToString(), false));
}
@@ -606,7 +603,7 @@
int user = mUserTracker.getUserId();
mQSTileHost.setTileAdded(CUSTOM_TILE, user, false);
- assertFalse(getSharedPreferenecesForUser(user)
+ assertFalse(getSharedPreferencesForUser(user)
.getBoolean(CUSTOM_TILE.flattenToString(), false));
}
@@ -615,7 +612,7 @@
int user = mUserTracker.getUserId();
mQSTileHost.setTileAdded(CUSTOM_TILE, user, true);
- assertFalse(getSharedPreferenecesForUser(user + 1)
+ assertFalse(getSharedPreferencesForUser(user + 1)
.getBoolean(CUSTOM_TILE.flattenToString(), false));
}
@@ -627,8 +624,8 @@
// This will be done by TileServiceManager
mQSTileHost.setTileAdded(CUSTOM_TILE, user, true);
- mQSTileHost.changeTilesByUser(mQSTileHost.mTileSpecs, List.of("spec1"));
- assertFalse(getSharedPreferenecesForUser(user)
+ mQSTileHost.changeTilesByUser(mQSTileHost.getSpecs(), List.of("spec1"));
+ assertFalse(getSharedPreferencesForUser(user)
.getBoolean(CUSTOM_TILE.flattenToString(), false));
}
@@ -642,7 +639,7 @@
mQSTileHost.removeTileByUser(CUSTOM_TILE);
mMainExecutor.runAllReady();
- assertFalse(getSharedPreferenecesForUser(user)
+ assertFalse(getSharedPreferencesForUser(user)
.getBoolean(CUSTOM_TILE.flattenToString(), false));
}
@@ -656,7 +653,7 @@
mQSTileHost.removeTile(CUSTOM_TILE_SPEC);
mMainExecutor.runAllReady();
- assertFalse(getSharedPreferenecesForUser(user)
+ assertFalse(getSharedPreferencesForUser(user)
.getBoolean(CUSTOM_TILE.flattenToString(), false));
}
@@ -681,12 +678,12 @@
assertEquals(CUSTOM_TILE.getClassName(), proto.tiles[1].getComponentName().className);
}
- private SharedPreferences getSharedPreferenecesForUser(int user) {
+ private SharedPreferences getSharedPreferencesForUser(int user) {
return mUserFileManager.getSharedPreferences(QSTileHost.TILES, 0, user);
}
private class TestQSTileHost extends QSTileHost {
- TestQSTileHost(Context context, StatusBarIconController iconController,
+ TestQSTileHost(Context context,
QSFactory defaultFactory, Executor mainExecutor,
PluginManager pluginManager, TunerService tunerService,
Provider<AutoTileManager> autoTiles, DumpManager dumpManager,
@@ -696,7 +693,7 @@
TileServiceRequestController.Builder tileServiceRequestControllerBuilder,
TileLifecycleManager.Factory tileLifecycleManagerFactory,
UserFileManager userFileManager) {
- super(context, iconController, defaultFactory, mainExecutor, pluginManager,
+ super(context, defaultFactory, mainExecutor, pluginManager,
tunerService, autoTiles, dumpManager, Optional.of(centralSurfaces), qsLogger,
uiEventLogger, userTracker, secureSettings, customTileStatePersister,
tileServiceRequestControllerBuilder, tileLifecycleManagerFactory,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
index f53e997..71ea831 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
@@ -50,7 +50,7 @@
class QuickQSPanelControllerTest : SysuiTestCase() {
@Mock private lateinit var quickQSPanel: QuickQSPanel
- @Mock private lateinit var qsTileHost: QSTileHost
+ @Mock private lateinit var qsHost: QSHost
@Mock private lateinit var qsCustomizerController: QSCustomizerController
@Mock private lateinit var mediaHost: MediaHost
@Mock private lateinit var metricsLogger: MetricsLogger
@@ -75,12 +75,12 @@
whenever(quickQSPanel.isAttachedToWindow).thenReturn(true)
whenever(quickQSPanel.dumpableTag).thenReturn("")
whenever(quickQSPanel.resources).thenReturn(mContext.resources)
- whenever(qsTileHost.createTileView(any(), any(), anyBoolean())).thenReturn(tileView)
+ whenever(qsHost.createTileView(any(), any(), anyBoolean())).thenReturn(tileView)
controller =
TestQuickQSPanelController(
quickQSPanel,
- qsTileHost,
+ qsHost,
qsCustomizerController,
/* usingMediaPlayer = */ false,
mediaHost,
@@ -102,7 +102,7 @@
fun testTileSublistWithFewerTiles_noCrash() {
whenever(quickQSPanel.numQuickTiles).thenReturn(3)
- whenever(qsTileHost.tiles).thenReturn(listOf(tile, tile))
+ whenever(qsHost.tiles).thenReturn(listOf(tile, tile))
controller.setTiles()
}
@@ -111,7 +111,7 @@
fun testTileSublistWithTooManyTiles() {
val limit = 3
whenever(quickQSPanel.numQuickTiles).thenReturn(limit)
- whenever(qsTileHost.tiles).thenReturn(listOf(tile, tile, tile, tile))
+ whenever(qsHost.tiles).thenReturn(listOf(tile, tile, tile, tile))
controller.setTiles()
@@ -147,7 +147,7 @@
class TestQuickQSPanelController(
view: QuickQSPanel,
- qsTileHost: QSTileHost,
+ qsHost: QSHost,
qsCustomizerController: QSCustomizerController,
usingMediaPlayer: Boolean,
mediaHost: MediaHost,
@@ -159,7 +159,7 @@
) :
QuickQSPanelController(
view,
- qsTileHost,
+ qsHost,
qsCustomizerController,
usingMediaPlayer,
mediaHost,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java
index d42cbe3..c041cb6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java
@@ -25,7 +25,7 @@
import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
import org.junit.Before;
import org.junit.Test;
@@ -42,19 +42,19 @@
private TileAdapter mTileAdapter;
@Mock
- private QSTileHost mQSTileHost;
+ private QSHost mQSHost;
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
TestableLooper.get(this).runWithLooper(() -> mTileAdapter =
- new TileAdapter(mContext, mQSTileHost, new UiEventLoggerFake()));
+ new TileAdapter(mContext, mQSHost, new UiEventLoggerFake()));
}
@Test
public void testResetNotifiesHost() {
mTileAdapter.resetTileSpecs(Collections.emptyList());
- verify(mQSTileHost).changeTilesByUser(any(), any());
+ verify(mQSHost).changeTilesByUser(any(), any());
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
index 040af70..78a0258 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
@@ -55,7 +55,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.qs.QSIconView;
import com.android.systemui.plugins.qs.QSTile;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -102,7 +102,7 @@
@Mock
private TileQueryHelper.TileStateListener mListener;
@Mock
- private QSTileHost mQSTileHost;
+ private QSHost mQSHost;
@Mock
private PackageManager mPackageManager;
@Mock
@@ -131,7 +131,7 @@
return null;
}
}
- ).when(mQSTileHost).createTile(anyString());
+ ).when(mQSHost).createTile(anyString());
FakeSystemClock clock = new FakeSystemClock();
mMainExecutor = new FakeExecutor(clock);
mBgExecutor = new FakeExecutor(clock);
@@ -147,7 +147,7 @@
@Test
public void testIsFinished_trueAfterQuerying() {
- mTileQueryHelper.queryTiles(mQSTileHost);
+ mTileQueryHelper.queryTiles(mQSHost);
FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
@@ -156,7 +156,7 @@
@Test
public void testQueryTiles_callsListenerTwice() {
- mTileQueryHelper.queryTiles(mQSTileHost);
+ mTileQueryHelper.queryTiles(mQSHost);
FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
@@ -170,7 +170,7 @@
return null;
}).when(mListener).onTilesChanged(any());
- mTileQueryHelper.queryTiles(mQSTileHost);
+ mTileQueryHelper.queryTiles(mQSHost);
FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
@@ -184,7 +184,7 @@
mContext.getOrCreateTestableResources().addOverride(R.string.quick_settings_tiles_stock,
STOCK_TILES);
- mTileQueryHelper.queryTiles(mQSTileHost);
+ mTileQueryHelper.queryTiles(mQSHost);
FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
@@ -204,7 +204,7 @@
mContext.getOrCreateTestableResources().addOverride(R.string.quick_settings_tiles_stock,
STOCK_TILES);
- mTileQueryHelper.queryTiles(mQSTileHost);
+ mTileQueryHelper.queryTiles(mQSHost);
FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
@@ -224,7 +224,7 @@
mContext.getOrCreateTestableResources().addOverride(R.string.quick_settings_tiles_stock,
STOCK_TILES);
- mTileQueryHelper.queryTiles(mQSTileHost);
+ mTileQueryHelper.queryTiles(mQSHost);
FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
@@ -240,9 +240,9 @@
public void testCustomTileNotCreated() {
Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.QS_TILES,
CUSTOM_TILE);
- mTileQueryHelper.queryTiles(mQSTileHost);
+ mTileQueryHelper.queryTiles(mQSHost);
FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
- verify(mQSTileHost, never()).createTile(CUSTOM_TILE);
+ verify(mQSHost, never()).createTile(CUSTOM_TILE);
}
@Test
@@ -264,7 +264,7 @@
mContext.getOrCreateTestableResources().addOverride(R.string.quick_settings_tiles_stock,
"");
- mTileQueryHelper.queryTiles(mQSTileHost);
+ mTileQueryHelper.queryTiles(mQSHost);
FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
verify(mListener, atLeastOnce()).onTilesChanged(mCaptor.capture());
@@ -278,7 +278,7 @@
Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.QS_TILES, null);
mContext.getOrCreateTestableResources().addOverride(R.string.quick_settings_tiles_stock,
STOCK_TILES);
- mTileQueryHelper.queryTiles(mQSTileHost);
+ mTileQueryHelper.queryTiles(mQSHost);
}
@Test
@@ -286,12 +286,12 @@
Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.QS_TILES, null);
QSTile t = mock(QSTile.class);
- when(mQSTileHost.createTile("hotspot")).thenReturn(t);
+ when(mQSHost.createTile("hotspot")).thenReturn(t);
mContext.getOrCreateTestableResources().addOverride(R.string.quick_settings_tiles_stock,
"hotspot");
- mTileQueryHelper.queryTiles(mQSTileHost);
+ mTileQueryHelper.queryTiles(mQSHost);
FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
InOrder verifier = inOrder(t);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
index 8aa625a..46af89e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
@@ -39,7 +39,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
import com.android.systemui.settings.UserTracker;
import org.junit.After;
@@ -61,7 +61,7 @@
@Mock
private UserTracker mUserTracker;
@Mock
- private QSTileHost mQSTileHost;
+ private QSHost mQSHost;
@Mock
private Context mMockContext;
@@ -80,7 +80,7 @@
when(mUserTracker.getUserHandle()).thenReturn(UserHandle.SYSTEM);
when(mTileServices.getContext()).thenReturn(mMockContext);
- when(mTileServices.getHost()).thenReturn(mQSTileHost);
+ when(mTileServices.getHost()).thenReturn(mQSHost);
when(mTileLifecycle.getUserId()).thenAnswer(invocation -> mUserTracker.getUserId());
when(mTileLifecycle.isActiveTile()).thenReturn(false);
@@ -98,28 +98,28 @@
@Test
public void testSetTileAddedIfNotAdded() {
- when(mQSTileHost.isTileAdded(eq(mComponentName), anyInt())).thenReturn(false);
+ when(mQSHost.isTileAdded(eq(mComponentName), anyInt())).thenReturn(false);
mTileServiceManager.startLifecycleManagerAndAddTile();
- verify(mQSTileHost).setTileAdded(mComponentName, mUserTracker.getUserId(), true);
+ verify(mQSHost).setTileAdded(mComponentName, mUserTracker.getUserId(), true);
}
@Test
public void testNotSetTileAddedIfAdded() {
- when(mQSTileHost.isTileAdded(eq(mComponentName), anyInt())).thenReturn(true);
+ when(mQSHost.isTileAdded(eq(mComponentName), anyInt())).thenReturn(true);
mTileServiceManager.startLifecycleManagerAndAddTile();
- verify(mQSTileHost, never()).setTileAdded(eq(mComponentName), anyInt(), eq(true));
+ verify(mQSHost, never()).setTileAdded(eq(mComponentName), anyInt(), eq(true));
}
@Test
public void testSetTileAddedCorrectUser() {
int user = 10;
when(mUserTracker.getUserId()).thenReturn(user);
- when(mQSTileHost.isTileAdded(eq(mComponentName), anyInt())).thenReturn(false);
+ when(mQSHost.isTileAdded(eq(mComponentName), anyInt())).thenReturn(false);
mTileServiceManager.startLifecycleManagerAndAddTile();
- verify(mQSTileHost).setTileAdded(mComponentName, user, true);
+ verify(mQSHost).setTileAdded(mComponentName, user, true);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt
index bdfbca4..ccfb5cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt
@@ -27,27 +27,27 @@
import com.android.internal.statusbar.IAddTileResultCallback
import com.android.systemui.InstanceIdSequenceFake
import com.android.systemui.SysuiTestCase
-import com.android.systemui.qs.QSTileHost
+import com.android.systemui.qs.QSHost
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import com.google.common.truth.Truth.assertThat
+import java.util.function.Consumer
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.anyString
import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
-import java.util.function.Consumer
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -62,7 +62,7 @@
@Mock
private lateinit var tileRequestDialog: TileRequestDialog
@Mock
- private lateinit var qsTileHost: QSTileHost
+ private lateinit var qsHost: QSHost
@Mock
private lateinit var commandRegistry: CommandRegistry
@Mock
@@ -82,10 +82,10 @@
`when`(logger.newInstanceId()).thenReturn(instanceIdSequence.newInstanceId())
// Tile not present by default
- `when`(qsTileHost.indexOf(anyString())).thenReturn(-1)
+ `when`(qsHost.indexOf(anyString())).thenReturn(-1)
controller = TileServiceRequestController(
- qsTileHost,
+ qsHost,
commandQueue,
commandRegistry,
logger
@@ -107,18 +107,18 @@
@Test
fun tileAlreadyAdded_correctResult() {
- `when`(qsTileHost.indexOf(CustomTile.toSpec(TEST_COMPONENT))).thenReturn(2)
+ `when`(qsHost.indexOf(CustomTile.toSpec(TEST_COMPONENT))).thenReturn(2)
val callback = Callback()
controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback)
assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.TILE_ALREADY_ADDED)
- verify(qsTileHost, never()).addTile(any(ComponentName::class.java), anyBoolean())
+ verify(qsHost, never()).addTile(any(ComponentName::class.java), anyBoolean())
}
@Test
fun tileAlreadyAdded_logged() {
- `when`(qsTileHost.indexOf(CustomTile.toSpec(TEST_COMPONENT))).thenReturn(2)
+ `when`(qsHost.indexOf(CustomTile.toSpec(TEST_COMPONENT))).thenReturn(2)
controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
@@ -157,7 +157,7 @@
cancelListenerCaptor.value.onCancel(tileRequestDialog)
assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.DISMISSED)
- verify(qsTileHost, never()).addTile(any(ComponentName::class.java), anyBoolean())
+ verify(qsHost, never()).addTile(any(ComponentName::class.java), anyBoolean())
}
@Test
@@ -191,7 +191,7 @@
clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_POSITIVE)
assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.ADD_TILE)
- verify(qsTileHost).addTile(TEST_COMPONENT, /* end */ true)
+ verify(qsHost).addTile(TEST_COMPONENT, /* end */ true)
}
@Test
@@ -225,7 +225,7 @@
clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_NEGATIVE)
assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.DONT_ADD_TILE)
- verify(qsTileHost, never()).addTile(any(ComponentName::class.java), anyBoolean())
+ verify(qsHost, never()).addTile(any(ComponentName::class.java), anyBoolean())
}
@Test
@@ -266,7 +266,7 @@
@Test
fun commandQueueCallback_callbackCalled() {
- `when`(qsTileHost.indexOf(CustomTile.toSpec(TEST_COMPONENT))).thenReturn(2)
+ `when`(qsHost.indexOf(CustomTile.toSpec(TEST_COMPONENT))).thenReturn(2)
val captor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
verify(commandQueue, atLeastOnce()).addCallback(capture(captor))
val c = Callback()
@@ -365,4 +365,4 @@
accept(r)
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index 172c87f..64e9a3e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -30,7 +30,6 @@
import android.content.ComponentName;
import android.content.Intent;
import android.os.Handler;
-import android.os.HandlerExecutor;
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.quicksettings.IQSTileService;
@@ -39,24 +38,13 @@
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
-import com.android.internal.logging.UiEventLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.PluginManager;
-import com.android.systemui.qs.QSTileHost;
-import com.android.systemui.qs.logging.QSLogger;
-import com.android.systemui.qs.tileimpl.QSFactoryImpl;
-import com.android.systemui.settings.UserFileManager;
+import com.android.systemui.qs.QSHost;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.phone.AutoTileManager;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.StatusBarIconController;
-import com.android.systemui.statusbar.policy.BluetoothController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.tuner.TunerService;
-import com.android.systemui.util.settings.SecureSettings;
import org.junit.After;
import org.junit.Assert;
@@ -68,8 +56,6 @@
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
-import java.util.Optional;
-import java.util.concurrent.Executor;
import javax.inject.Provider;
@@ -92,26 +78,8 @@
@Mock
private StatusBarIconController mStatusBarIconController;
@Mock
- private QSFactoryImpl mQSFactory;
- @Mock
- private PluginManager mPluginManager;
- @Mock
- private TunerService mTunerService;
- @Mock
- private AutoTileManager mAutoTileManager;
- @Mock
- private DumpManager mDumpManager;
- @Mock
- private CentralSurfaces mCentralSurfaces;
- @Mock
- private QSLogger mQSLogger;
- @Mock
- private UiEventLogger mUiEventLogger;
- @Mock
private UserTracker mUserTracker;
@Mock
- private SecureSettings mSecureSettings;
- @Mock
private TileServiceRequestController.Builder mTileServiceRequestControllerBuilder;
@Mock
private TileServiceRequestController mTileServiceRequestController;
@@ -122,12 +90,11 @@
@Mock
private TileLifecycleManager mTileLifecycleManager;
@Mock
- private UserFileManager mUserFileManager;
+ private QSHost mQSHost;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mDependency.injectMockDependency(BluetoothController.class);
mManagers = new ArrayList<>();
mTestableLooper = TestableLooper.get(this);
@@ -135,34 +102,16 @@
.thenReturn(mTileServiceRequestController);
when(mTileLifecycleManagerFactory.create(any(Intent.class), any(UserHandle.class)))
.thenReturn(mTileLifecycleManager);
+ when(mQSHost.getContext()).thenReturn(mContext);
Provider<Handler> provider = () -> new Handler(mTestableLooper.getLooper());
- Executor executor = new HandlerExecutor(provider.get());
- QSTileHost host = new QSTileHost(mContext,
- mStatusBarIconController,
- mQSFactory,
- executor,
- mPluginManager,
- mTunerService,
- () -> mAutoTileManager,
- mDumpManager,
- Optional.of(mCentralSurfaces),
- mQSLogger,
- mUiEventLogger,
- mUserTracker,
- mSecureSettings,
- mock(CustomTileStatePersister.class),
- mTileServiceRequestControllerBuilder,
- mTileLifecycleManagerFactory,
- mUserFileManager);
- mTileService = new TestTileServices(host, provider, mBroadcastDispatcher,
- mUserTracker, mKeyguardStateController, mCommandQueue);
+ mTileService = new TestTileServices(mQSHost, provider, mBroadcastDispatcher,
+ mUserTracker, mKeyguardStateController, mCommandQueue, mStatusBarIconController);
}
@After
public void tearDown() throws Exception {
- mTileService.getHost().destroy();
mTileService.destroy();
TestableLooper.get(this).processAllMessages();
}
@@ -274,11 +223,12 @@
}
private class TestTileServices extends TileServices {
- TestTileServices(QSTileHost host, Provider<Handler> handlerProvider,
+ TestTileServices(QSHost host, Provider<Handler> handlerProvider,
BroadcastDispatcher broadcastDispatcher, UserTracker userTracker,
- KeyguardStateController keyguardStateController, CommandQueue commandQueue) {
+ KeyguardStateController keyguardStateController, CommandQueue commandQueue,
+ StatusBarIconController statusBarIconController) {
super(host, handlerProvider, broadcastDispatcher, userTracker, keyguardStateController,
- commandQueue);
+ commandQueue, statusBarIconController);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
index ba49f3f..36549fb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
@@ -69,7 +69,6 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSEvent;
import com.android.systemui.qs.QSHost;
-import com.android.systemui.qs.QSTileHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.statusbar.StatusBarState;
@@ -97,7 +96,7 @@
private TestableLooper mTestableLooper;
private TileImpl mTile;
@Mock
- private QSTileHost mHost;
+ private QSHost mHost;
@Mock
private MetricsLogger mMetricsLogger;
private final FalsingManagerFake mFalsingManager = new FalsingManagerFake();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
index d65901777..bf172f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
@@ -17,7 +17,7 @@
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.qs.QSTileHost
+import com.android.systemui.qs.QSHost
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.statusbar.policy.BluetoothController
@@ -40,7 +40,7 @@
@Mock
private lateinit var qsLogger: QSLogger
@Mock
- private lateinit var qsHost: QSTileHost
+ private lateinit var qsHost: QSHost
@Mock
private lateinit var metricsLogger: MetricsLogger
private val falsingManager = FalsingManagerFake()
@@ -135,7 +135,7 @@
}
private class FakeBluetoothTile(
- qsTileHost: QSTileHost,
+ qsHost: QSHost,
backgroundLooper: Looper,
mainHandler: Handler,
falsingManager: FalsingManager,
@@ -145,7 +145,7 @@
qsLogger: QSLogger,
bluetoothController: BluetoothController
) : BluetoothTile(
- qsTileHost,
+ qsHost,
backgroundLooper,
mainHandler,
falsingManager,
@@ -187,4 +187,4 @@
`when`(bluetoothController.isBluetoothConnected).thenReturn(false)
`when`(bluetoothController.isBluetoothConnecting).thenReturn(true)
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
index b40a20c..18f891c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
@@ -42,7 +42,7 @@
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.statusbar.connectivity.IconState;
import com.android.systemui.statusbar.connectivity.NetworkController;
@@ -78,7 +78,7 @@
@Mock
private NetworkController mNetworkController;
@Mock
- private QSTileHost mHost;
+ private QSHost mHost;
@Mock
SignalCallback mSignalCallback;
@Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java
index debe41c..fdb63ca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java
@@ -37,7 +37,7 @@
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.settings.FakeSettings;
@@ -56,7 +56,7 @@
public class ColorCorrectionTileTest extends SysuiTestCase {
@Mock
- private QSTileHost mHost;
+ private QSHost mHost;
@Mock
private MetricsLogger mMetricsLogger;
@Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java
index 3fd2501..60c1a33 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java
@@ -39,7 +39,7 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.settings.UserTracker;
@@ -61,7 +61,7 @@
private static final Integer COLOR_INVERSION_ENABLED = 1;
@Mock
- private QSTileHost mHost;
+ private QSHost mHost;
@Mock
private MetricsLogger mMetricsLogger;
@Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java
index 15545a4..71d8aa4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java
@@ -47,7 +47,7 @@
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.settings.UserTracker;
@@ -69,7 +69,7 @@
@Mock
private ActivityStarter mActivityStarter;
@Mock
- private QSTileHost mHost;
+ private QSHost mHost;
@Mock
private MetricsLogger mMetricsLogger;
@Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt
index d0f851b..c7aba1a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt
@@ -13,7 +13,7 @@
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.qs.QSTileHost
+import com.android.systemui.qs.QSHost
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.statusbar.policy.FlashlightController
@@ -34,7 +34,7 @@
@Mock private lateinit var qsLogger: QSLogger
- @Mock private lateinit var qsHost: QSTileHost
+ @Mock private lateinit var qsHost: QSHost
@Mock private lateinit var metricsLogger: MetricsLogger
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
index 257d42a..9d62cfd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
@@ -15,7 +15,9 @@
*/
package com.android.systemui.qs.tiles
+import android.content.Intent
import android.os.Handler
+import android.provider.Settings
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
@@ -113,4 +115,11 @@
verify(dialogLaunchAnimator).showFromView(any(), eq(view), nullable(), anyBoolean())
}
+
+ @Test
+ fun getLongClickIntent_getExpectedIntent() {
+ val intent: Intent? = fontScalingTile.getLongClickIntent()
+
+ assertThat(intent!!.action).isEqualTo(Settings.ACTION_TEXT_READING_SETTINGS)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java
index 451e911..4a2ac96 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java
@@ -37,7 +37,7 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.policy.DataSaverController;
@@ -60,7 +60,7 @@
@Rule
public MockitoRule mRule = MockitoJUnit.rule();
@Mock
- private QSTileHost mHost;
+ private QSHost mHost;
@Mock
private HotspotController mHotspotController;
@Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
index addca9d..abd9094 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
@@ -21,7 +21,6 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-
import android.os.Handler;
import android.service.quicksettings.Tile;
import android.testing.AndroidTestingRunner;
@@ -35,7 +34,7 @@
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
@@ -56,7 +55,7 @@
public class InternetTileTest extends SysuiTestCase {
@Mock
- private QSTileHost mHost;
+ private QSHost mHost;
@Mock
private NetworkController mNetworkController;
@Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
index d2bbc8c..08d10fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
@@ -29,7 +29,7 @@
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.qs.QSTileHost
+import com.android.systemui.qs.QSHost
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -52,7 +52,7 @@
@Mock
private lateinit var qsLogger: QSLogger
@Mock
- private lateinit var qsHost: QSTileHost
+ private lateinit var qsHost: QSHost
@Mock
private lateinit var metricsLogger: MetricsLogger
private val falsingManager = FalsingManagerFake()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java
index cfd3735..9638a45 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java
@@ -36,7 +36,7 @@
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.logging.QSLogger;
import org.junit.Before;
@@ -60,7 +60,7 @@
@Mock
private ActivityStarter mActivityStarter;
@Mock
- private QSTileHost mHost;
+ private QSHost mHost;
@Mock
private MetricsLogger mMetricsLogger;
@Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java
index 8031875..3344a17 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java
@@ -32,7 +32,7 @@
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.settings.SecureSettings;
@@ -53,7 +53,7 @@
@Mock
private ActivityStarter mActivityStarter;
@Mock
- private QSTileHost mHost;
+ private QSHost mHost;
@Mock
private MetricsLogger mMetricsLogger;
@Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
index a1be2f3..24287ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
@@ -39,7 +39,7 @@
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qrcodescanner.controller.QRCodeScannerController;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -54,7 +54,7 @@
@SmallTest
public class QRCodeScannerTileTest extends SysuiTestCase {
@Mock
- private QSTileHost mHost;
+ private QSHost mHost;
@Mock
private MetricsLogger mMetricsLogger;
@Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
index 4f6475f..4722c8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
@@ -66,7 +66,7 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -99,7 +99,7 @@
.setComponent(new ComponentName(mContext.getPackageName(), "WalletActivity"));
@Mock
- private QSTileHost mHost;
+ private QSHost mHost;
@Mock
private MetricsLogger mMetricsLogger;
@Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
index 04b372c..99e5564 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
@@ -38,7 +38,7 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.ReduceBrightColorsController;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -57,7 +57,7 @@
@Ignore("b/269171747")
public class ReduceBrightColorsTileTest extends SysuiTestCase {
@Mock
- private QSTileHost mHost;
+ private QSHost mHost;
@Mock
private MetricsLogger mMetricsLogger;
@Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
index e9dfd3e..c7eb2f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
@@ -38,7 +38,7 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.policy.BatteryController;
@@ -71,7 +71,7 @@
@Mock
private ActivityStarter mActivityStarter;
@Mock
- private QSTileHost mHost;
+ private QSHost mHost;
@Mock
private MetricsLogger mMetricsLogger;
@Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
index 30debdf..21acc08 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
@@ -43,7 +43,7 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.screenrecord.RecordingController;
@@ -65,7 +65,7 @@
@Mock
private RecordingController mController;
@Mock
- private QSTileHost mHost;
+ private QSHost mHost;
@Mock
private KeyguardDismissUtil mKeyguardDismissUtil;
@Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt
index 0c070da..3d9f650 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt
@@ -32,7 +32,7 @@
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.qs.QSTileHost
+import com.android.systemui.qs.QSHost
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.statusbar.policy.BatteryController
@@ -55,7 +55,7 @@
@Mock private lateinit var uiModeManager: UiModeManager
@Mock private lateinit var resources: Resources
@Mock private lateinit var qsLogger: QSLogger
- @Mock private lateinit var qsHost: QSTileHost
+ @Mock private lateinit var qsHost: QSHost
@Mock private lateinit var metricsLogger: MetricsLogger
@Mock private lateinit var statusBarStateController: StatusBarStateController
@Mock private lateinit var activityStarter: ActivityStarter
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 452606d..8ee1ea8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -44,6 +44,7 @@
import androidx.test.filters.SmallTest;
+import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -354,7 +355,8 @@
mDeviceProvisionedController,
mKeyguardStateController,
mSettings,
- mock(DumpManager.class));
+ mock(DumpManager.class),
+ mock(LockPatternUtils.class));
}
public BroadcastReceiver getBaseBroadcastReceiverForTest() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/FakeStatusEvent.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/FakeStatusEvent.kt
new file mode 100644
index 0000000..cd06465
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/FakeStatusEvent.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.events
+
+/**
+ * This is a freely configurable implementation of [StatusEvent]. It is intended to be used in
+ * tests.
+ */
+class FakeStatusEvent(
+ override val viewCreator: ViewCreator,
+ override val priority: Int = 50,
+ override var forceVisible: Boolean = false,
+ override val showAnimation: Boolean = true,
+ override var contentDescription: String? = "",
+) : StatusEvent
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
new file mode 100644
index 0000000..08a9f31
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
@@ -0,0 +1,470 @@
+/*
+ * 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.events
+
+import android.graphics.Rect
+import android.os.Process
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.View
+import android.widget.FrameLayout
+import androidx.core.animation.AnimatorTestRule
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.privacy.OngoingPrivacyChip
+import com.android.systemui.statusbar.BatteryStatusChip
+import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
+import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import junit.framework.Assert.assertEquals
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() {
+
+ @Mock private lateinit var systemEventCoordinator: SystemEventCoordinator
+ @Mock private lateinit var statusBarWindowController: StatusBarWindowController
+ @Mock private lateinit var statusBarContentInsetProvider: StatusBarContentInsetsProvider
+ @Mock private lateinit var dumpManager: DumpManager
+ @Mock private lateinit var listener: SystemStatusAnimationCallback
+
+ private lateinit var systemClock: FakeSystemClock
+ private lateinit var chipAnimationController: SystemEventChipAnimationController
+ private lateinit var systemStatusAnimationScheduler: SystemStatusAnimationScheduler
+ private val fakeFeatureFlags = FakeFeatureFlags()
+
+ @get:Rule val animatorTestRule = AnimatorTestRule()
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ fakeFeatureFlags.set(Flags.PLUG_IN_STATUS_BAR_CHIP, true)
+
+ systemClock = FakeSystemClock()
+ chipAnimationController =
+ SystemEventChipAnimationController(
+ mContext,
+ statusBarWindowController,
+ statusBarContentInsetProvider,
+ fakeFeatureFlags
+ )
+
+ // ensure that isTooEarly() check in SystemStatusAnimationScheduler does not return true
+ systemClock.advanceTime(Process.getStartUptimeMillis() + MIN_UPTIME)
+
+ // StatusBarContentInsetProvider is mocked. Ensure that it returns some mocked values.
+ whenever(statusBarContentInsetProvider.getStatusBarContentInsetsForCurrentRotation())
+ .thenReturn(android.util.Pair(10, 10))
+ whenever(statusBarContentInsetProvider.getStatusBarContentAreaForCurrentRotation())
+ .thenReturn(Rect(10, 0, 990, 100))
+
+ // StatusBarWindowController is mocked. The addViewToWindow function needs to be mocked to
+ // ensure that the chip view is added to a parent view
+ whenever(statusBarWindowController.addViewToWindow(any(), any())).then {
+ val statusbarFake = FrameLayout(mContext)
+ statusbarFake.layout(0, 0, 1000, 100)
+ statusbarFake.addView(
+ it.arguments[0] as View,
+ it.arguments[1] as FrameLayout.LayoutParams
+ )
+ }
+ }
+
+ @Test
+ fun testBatteryStatusEvent_standardAnimationLifecycle() = runTest {
+ // Instantiate class under test with TestScope from runTest
+ initializeSystemStatusAnimationScheduler(testScope = this)
+
+ val batteryChip = createAndScheduleFakeBatteryEvent()
+
+ // assert that animation is queued
+ assertEquals(ANIMATION_QUEUED, systemStatusAnimationScheduler.getAnimationState())
+
+ // skip debounce delay
+ advanceTimeBy(DEBOUNCE_DELAY + 1)
+ // status chip starts animating in after debounce delay
+ assertEquals(ANIMATING_IN, systemStatusAnimationScheduler.getAnimationState())
+ assertEquals(0f, batteryChip.contentView.alpha)
+ assertEquals(0f, batteryChip.view.alpha)
+ verify(listener, times(1)).onSystemEventAnimationBegin()
+
+ // skip appear animation
+ animatorTestRule.advanceTimeBy(APPEAR_ANIMATION_DURATION)
+ advanceTimeBy(APPEAR_ANIMATION_DURATION)
+ // assert that status chip is visible
+ assertEquals(RUNNING_CHIP_ANIM, systemStatusAnimationScheduler.getAnimationState())
+ assertEquals(1f, batteryChip.contentView.alpha)
+ assertEquals(1f, batteryChip.view.alpha)
+
+ // skip status chip display time
+ advanceTimeBy(DISPLAY_LENGTH + 1)
+ // assert that it is still visible but switched to the ANIMATING_OUT state
+ assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState())
+ assertEquals(1f, batteryChip.contentView.alpha)
+ assertEquals(1f, batteryChip.view.alpha)
+ verify(listener, times(1)).onSystemEventAnimationFinish(false)
+
+ // skip disappear animation
+ animatorTestRule.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION)
+ // assert that it is not visible anymore
+ assertEquals(IDLE, systemStatusAnimationScheduler.getAnimationState())
+ assertEquals(0f, batteryChip.contentView.alpha)
+ assertEquals(0f, batteryChip.view.alpha)
+ }
+
+ @Test
+ fun testPrivacyStatusEvent_standardAnimationLifecycle() = runTest {
+ // Instantiate class under test with TestScope from runTest
+ initializeSystemStatusAnimationScheduler(testScope = this)
+
+ val privacyChip = createAndScheduleFakePrivacyEvent()
+
+ // assert that animation is queued
+ assertEquals(ANIMATION_QUEUED, systemStatusAnimationScheduler.getAnimationState())
+
+ // skip debounce delay
+ advanceTimeBy(DEBOUNCE_DELAY + 1)
+ // status chip starts animating in after debounce delay
+ assertEquals(ANIMATING_IN, systemStatusAnimationScheduler.getAnimationState())
+ assertEquals(0f, privacyChip.view.alpha)
+ verify(listener, times(1)).onSystemEventAnimationBegin()
+
+ // skip appear animation
+ animatorTestRule.advanceTimeBy(APPEAR_ANIMATION_DURATION)
+ advanceTimeBy(APPEAR_ANIMATION_DURATION + 1)
+ // assert that status chip is visible
+ assertEquals(RUNNING_CHIP_ANIM, systemStatusAnimationScheduler.getAnimationState())
+ assertEquals(1f, privacyChip.view.alpha)
+
+ // skip status chip display time
+ advanceTimeBy(DISPLAY_LENGTH + 1)
+ // assert that it is still visible but switched to the ANIMATING_OUT state
+ assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState())
+ assertEquals(1f, privacyChip.view.alpha)
+ verify(listener, times(1)).onSystemEventAnimationFinish(true)
+ verify(listener, times(1)).onSystemStatusAnimationTransitionToPersistentDot(any())
+
+ // skip transition to persistent dot
+ advanceTimeBy(DISAPPEAR_ANIMATION_DURATION + 1)
+ animatorTestRule.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION)
+ // assert that it the dot is now visible
+ assertEquals(SHOWING_PERSISTENT_DOT, systemStatusAnimationScheduler.getAnimationState())
+ assertEquals(1f, privacyChip.view.alpha)
+
+ // notify SystemStatusAnimationScheduler to remove persistent dot
+ systemStatusAnimationScheduler.removePersistentDot()
+ // assert that IDLE state is entered
+ assertEquals(IDLE, systemStatusAnimationScheduler.getAnimationState())
+ verify(listener, times(1)).onHidePersistentDot()
+ }
+
+ @Test
+ fun testHighPriorityEvent_takesPrecedenceOverScheduledLowPriorityEvent() = runTest {
+ // Instantiate class under test with TestScope from runTest
+ initializeSystemStatusAnimationScheduler(testScope = this)
+
+ // create and schedule low priority event
+ val batteryChip = createAndScheduleFakeBatteryEvent()
+ batteryChip.view.alpha = 0f
+
+ // assert that animation is queued
+ assertEquals(ANIMATION_QUEUED, systemStatusAnimationScheduler.getAnimationState())
+
+ // create and schedule high priority event
+ val privacyChip = createAndScheduleFakePrivacyEvent()
+
+ // assert that animation is queued
+ assertEquals(ANIMATION_QUEUED, systemStatusAnimationScheduler.getAnimationState())
+
+ // skip debounce delay and appear animation duration
+ fastForwardAnimationToState(RUNNING_CHIP_ANIM)
+
+ // high priority status chip is visible while low priority status chip is not visible
+ assertEquals(RUNNING_CHIP_ANIM, systemStatusAnimationScheduler.getAnimationState())
+ assertEquals(1f, privacyChip.view.alpha)
+ assertEquals(0f, batteryChip.view.alpha)
+ }
+
+ @Test
+ fun testHighPriorityEvent_cancelsCurrentlyDisplayedLowPriorityEvent() = runTest {
+ // Instantiate class under test with TestScope from runTest
+ initializeSystemStatusAnimationScheduler(testScope = this)
+
+ // create and schedule low priority event
+ val batteryChip = createAndScheduleFakeBatteryEvent()
+
+ // fast forward to RUNNING_CHIP_ANIM state
+ fastForwardAnimationToState(RUNNING_CHIP_ANIM)
+
+ // assert that chip is displayed
+ assertEquals(RUNNING_CHIP_ANIM, systemStatusAnimationScheduler.getAnimationState())
+ assertEquals(1f, batteryChip.view.alpha)
+
+ // create and schedule high priority event
+ val privacyChip = createAndScheduleFakePrivacyEvent()
+
+ // ensure that the event cancellation coroutine is started by the test scope
+ testScheduler.runCurrent()
+
+ // assert that currently displayed chip is immediately animated out
+ assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState())
+
+ // skip disappear animation
+ animatorTestRule.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION)
+
+ // assert that high priority privacy chip animation is queued
+ assertEquals(ANIMATION_QUEUED, systemStatusAnimationScheduler.getAnimationState())
+
+ // skip debounce delay and appear animation
+ advanceTimeBy(DEBOUNCE_DELAY + APPEAR_ANIMATION_DURATION + 1)
+ animatorTestRule.advanceTimeBy(APPEAR_ANIMATION_DURATION)
+
+ // high priority status chip is visible while low priority status chip is not visible
+ assertEquals(RUNNING_CHIP_ANIM, systemStatusAnimationScheduler.getAnimationState())
+ assertEquals(1f, privacyChip.view.alpha)
+ assertEquals(0f, batteryChip.view.alpha)
+ }
+
+ @Test
+ fun testHighPriorityEvent_cancelsCurrentlyAnimatedLowPriorityEvent() = runTest {
+ // Instantiate class under test with TestScope from runTest
+ initializeSystemStatusAnimationScheduler(testScope = this)
+
+ // create and schedule low priority event
+ val batteryChip = createAndScheduleFakeBatteryEvent()
+
+ // skip debounce delay
+ advanceTimeBy(DEBOUNCE_DELAY + 1)
+
+ // assert that chip is animated in
+ assertEquals(ANIMATING_IN, systemStatusAnimationScheduler.getAnimationState())
+
+ // create and schedule high priority event
+ val privacyChip = createAndScheduleFakePrivacyEvent()
+
+ // ensure that the event cancellation coroutine is started by the test scope
+ testScheduler.runCurrent()
+
+ // assert that currently animated chip keeps animating
+ assertEquals(ANIMATING_IN, systemStatusAnimationScheduler.getAnimationState())
+
+ // skip appear animation
+ animatorTestRule.advanceTimeBy(APPEAR_ANIMATION_DURATION)
+ advanceTimeBy(APPEAR_ANIMATION_DURATION + 1)
+
+ // assert that low priority chip is animated out immediately after finishing the appear
+ // animation
+ assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState())
+
+ // skip disappear animation
+ animatorTestRule.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION)
+
+ // assert that high priority privacy chip animation is queued
+ assertEquals(ANIMATION_QUEUED, systemStatusAnimationScheduler.getAnimationState())
+
+ // skip debounce delay and appear animation
+ advanceTimeBy(DEBOUNCE_DELAY + APPEAR_ANIMATION_DURATION + 1)
+ animatorTestRule.advanceTimeBy(APPEAR_ANIMATION_DURATION)
+
+ // high priority status chip is visible while low priority status chip is not visible
+ assertEquals(RUNNING_CHIP_ANIM, systemStatusAnimationScheduler.getAnimationState())
+ assertEquals(1f, privacyChip.view.alpha)
+ assertEquals(0f, batteryChip.view.alpha)
+ }
+
+ @Test
+ fun testHighPriorityEvent_isNotReplacedByLowPriorityEvent() = runTest {
+ // Instantiate class under test with TestScope from runTest
+ initializeSystemStatusAnimationScheduler(testScope = this)
+
+ // create and schedule high priority event
+ val privacyChip = createAndScheduleFakePrivacyEvent()
+
+ // create and schedule low priority event
+ val batteryChip = createAndScheduleFakeBatteryEvent()
+ batteryChip.view.alpha = 0f
+
+ // skip debounce delay and appear animation
+ advanceTimeBy(DEBOUNCE_DELAY + APPEAR_ANIMATION_DURATION + 1)
+ animatorTestRule.advanceTimeBy(APPEAR_ANIMATION_DURATION)
+
+ // high priority status chip is visible while low priority status chip is not visible
+ assertEquals(RUNNING_CHIP_ANIM, systemStatusAnimationScheduler.getAnimationState())
+ assertEquals(1f, privacyChip.view.alpha)
+ assertEquals(0f, batteryChip.view.alpha)
+ }
+
+ @Test
+ fun testPrivacyDot_isRemoved() = runTest {
+ // Instantiate class under test with TestScope from runTest
+ initializeSystemStatusAnimationScheduler(testScope = this)
+
+ // create and schedule high priority event
+ createAndScheduleFakePrivacyEvent()
+
+ // skip chip animation lifecycle and fast forward to SHOWING_PERSISTENT_DOT state
+ fastForwardAnimationToState(SHOWING_PERSISTENT_DOT)
+ assertEquals(SHOWING_PERSISTENT_DOT, systemStatusAnimationScheduler.getAnimationState())
+ verify(listener, times(1)).onSystemStatusAnimationTransitionToPersistentDot(any())
+
+ // remove persistent dot and verify that animationState changes to IDLE
+ systemStatusAnimationScheduler.removePersistentDot()
+ assertEquals(IDLE, systemStatusAnimationScheduler.getAnimationState())
+ verify(listener, times(1)).onHidePersistentDot()
+ }
+
+ @Test
+ fun testPrivacyDot_isRemovedDuringChipAnimation() = runTest {
+ // Instantiate class under test with TestScope from runTest
+ initializeSystemStatusAnimationScheduler(testScope = this)
+
+ // create and schedule high priority event
+ createAndScheduleFakePrivacyEvent()
+
+ // skip chip animation lifecycle and fast forward to RUNNING_CHIP_ANIM state
+ fastForwardAnimationToState(RUNNING_CHIP_ANIM)
+ assertEquals(RUNNING_CHIP_ANIM, systemStatusAnimationScheduler.getAnimationState())
+
+ // request removal of persistent dot
+ systemStatusAnimationScheduler.removePersistentDot()
+
+ // skip display time and verify that disappear animation is run
+ advanceTimeBy(DISPLAY_LENGTH + 1)
+ assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState())
+
+ // skip disappear animation and verify that animationState changes to IDLE instead of
+ // SHOWING_PERSISTENT_DOT
+ animatorTestRule.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION)
+ assertEquals(IDLE, systemStatusAnimationScheduler.getAnimationState())
+ // verify that the persistent dot callbacks are not invoked
+ verify(listener, never()).onSystemStatusAnimationTransitionToPersistentDot(any())
+ verify(listener, never()).onHidePersistentDot()
+ }
+
+ @Test
+ fun testNewEvent_isScheduled_whenPostedDuringRemovalAnimation() = runTest {
+ // Instantiate class under test with TestScope from runTest
+ initializeSystemStatusAnimationScheduler(testScope = this)
+
+ // create and schedule high priority event
+ createAndScheduleFakePrivacyEvent()
+
+ // skip chip animation lifecycle and fast forward to ANIMATING_OUT state
+ fastForwardAnimationToState(ANIMATING_OUT)
+ assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState())
+ verify(listener, times(1)).onSystemStatusAnimationTransitionToPersistentDot(any())
+
+ // request removal of persistent dot
+ systemStatusAnimationScheduler.removePersistentDot()
+ testScheduler.runCurrent()
+
+ // schedule another high priority event while the event is animating out
+ createAndScheduleFakePrivacyEvent()
+
+ // verify that the state is still ANIMATING_OUT
+ assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState())
+
+ // skip disappear animation duration and verify that new state is ANIMATION_QUEUED
+ animatorTestRule.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION)
+ testScheduler.runCurrent()
+ assertEquals(ANIMATION_QUEUED, systemStatusAnimationScheduler.getAnimationState())
+ // also verify that onHidePersistentDot callback is called
+ verify(listener, times(1)).onHidePersistentDot()
+ }
+
+ private fun TestScope.fastForwardAnimationToState(@SystemAnimationState animationState: Int) {
+ // this function should only be called directly after posting a status event
+ assertEquals(ANIMATION_QUEUED, systemStatusAnimationScheduler.getAnimationState())
+ if (animationState == IDLE || animationState == ANIMATION_QUEUED) return
+ // skip debounce delay
+ advanceTimeBy(DEBOUNCE_DELAY + 1)
+
+ // status chip starts animating in after debounce delay
+ assertEquals(ANIMATING_IN, systemStatusAnimationScheduler.getAnimationState())
+ verify(listener, times(1)).onSystemEventAnimationBegin()
+ if (animationState == ANIMATING_IN) return
+
+ // skip appear animation
+ animatorTestRule.advanceTimeBy(APPEAR_ANIMATION_DURATION)
+ advanceTimeBy(APPEAR_ANIMATION_DURATION)
+ assertEquals(RUNNING_CHIP_ANIM, systemStatusAnimationScheduler.getAnimationState())
+ if (animationState == RUNNING_CHIP_ANIM) return
+
+ // skip status chip display time
+ advanceTimeBy(DISPLAY_LENGTH + 1)
+ assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState())
+ verify(listener, times(1)).onSystemEventAnimationFinish(anyBoolean())
+ if (animationState == ANIMATING_OUT) return
+
+ // skip disappear animation
+ animatorTestRule.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION)
+ }
+
+ private fun createAndScheduleFakePrivacyEvent(): OngoingPrivacyChip {
+ val privacyChip = OngoingPrivacyChip(mContext)
+ val fakePrivacyStatusEvent =
+ FakeStatusEvent(viewCreator = { privacyChip }, priority = 100, forceVisible = true)
+ systemStatusAnimationScheduler.onStatusEvent(fakePrivacyStatusEvent)
+ return privacyChip
+ }
+
+ private fun createAndScheduleFakeBatteryEvent(): BatteryStatusChip {
+ val batteryChip = BatteryStatusChip(mContext)
+ val fakeBatteryEvent =
+ FakeStatusEvent(viewCreator = { batteryChip }, priority = 50, forceVisible = false)
+ systemStatusAnimationScheduler.onStatusEvent(fakeBatteryEvent)
+ return batteryChip
+ }
+
+ private fun initializeSystemStatusAnimationScheduler(testScope: TestScope) {
+ systemStatusAnimationScheduler =
+ SystemStatusAnimationSchedulerImpl(
+ systemEventCoordinator,
+ chipAnimationController,
+ statusBarWindowController,
+ dumpManager,
+ systemClock,
+ CoroutineScope(StandardTestDispatcher(testScope.testScheduler))
+ )
+ // add a mock listener
+ systemStatusAnimationScheduler.addCallback(listener)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/FakeNodeController.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/FakeNodeController.kt
new file mode 100644
index 0000000..2de21ae
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/FakeNodeController.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.notification.collection.render
+
+import android.view.View
+
+class FakeNodeController(
+ override val view: View,
+ override val nodeLabel: String = "fakeNodeController"
+) : NodeController {
+ override fun offerToKeepInParentForAnimation(): Boolean = false
+ override fun removeFromParentIfKeptForAnimation(): Boolean = false
+ override fun resetKeepInParentForAnimation() = Unit
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
index 5b6c8c6..fb3aba1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
@@ -31,15 +31,19 @@
import com.android.systemui.statusbar.NotificationMediaManager
import com.android.systemui.statusbar.SmartReplyController
import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider
+import com.android.systemui.statusbar.notification.collection.render.FakeNodeController
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
import com.android.systemui.statusbar.notification.logging.NotificationLogger
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.SmartReplyConstants
import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.time.SystemClock
import com.android.systemui.wmshell.BubblesManager
@@ -68,6 +72,7 @@
private val metricsLogger: MetricsLogger = mock()
private val logBufferLogger: NotificationRowLogger = mock()
private val listContainer: NotificationListContainer = mock()
+ private val childrenContainer: NotificationChildrenContainer = mock()
private val mediaManager: NotificationMediaManager = mock()
private val smartReplyConstants: SmartReplyConstants = mock()
private val smartReplyController: SmartReplyController = mock()
@@ -129,6 +134,7 @@
dragController,
dismissibilityProvider
)
+ whenever(view.childrenContainer).thenReturn(childrenContainer)
}
@After
@@ -173,4 +179,32 @@
Assert.assertFalse(controller.removeFromParentIfKeptForAnimation())
Mockito.verifyNoMoreInteractions(parentView)
}
+
+ @Test
+ fun removeChild_whenTransfer() {
+ val childView: ExpandableNotificationRow = mock()
+ val childNodeController = FakeNodeController(childView)
+
+ // GIVEN a child is removed for transfer
+ controller.removeChild(childNodeController, /* isTransfer= */ true)
+
+ // VERIFY the listContainer is not notified
+ Mockito.verify(childView).isChangingPosition = eq(true)
+ Mockito.verify(view).removeChildNotification(eq(childView))
+ Mockito.verify(listContainer, never()).notifyGroupChildRemoved(any(), any())
+ }
+
+ @Test
+ fun removeChild_whenNotTransfer() {
+ val childView: ExpandableNotificationRow = mock()
+ val childNodeController = FakeNodeController(childView)
+
+ // GIVEN a child is removed for real
+ controller.removeChild(childNodeController, /* isTransfer= */ false)
+
+ // VERIFY the listContainer is passed the childrenContainer for transient animations
+ Mockito.verify(childView, never()).isChangingPosition = any()
+ Mockito.verify(view).removeChildNotification(eq(childView))
+ Mockito.verify(listContainer).notifyGroupChildRemoved(eq(childView), eq(childrenContainer))
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
index f568547..e680a4e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
@@ -53,7 +53,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.AutoAddTracker;
-import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.ReduceBrightColorsController;
import com.android.systemui.qs.SettingObserver;
import com.android.systemui.qs.external.CustomTile;
@@ -104,7 +104,7 @@
private static final int USER = 0;
- @Mock private QSTileHost mQsTileHost;
+ @Mock private QSHost mQsHost;
@Mock private AutoAddTracker mAutoAddTracker;
@Mock private CastController mCastController;
@Mock private HotspotController mHotspotController;
@@ -144,7 +144,7 @@
R.string.safety_quick_settings_tile_class, TEST_CUSTOM_SAFETY_CLASS);
when(mAutoAddTrackerBuilder.build()).thenReturn(mAutoAddTracker);
- when(mQsTileHost.getUserContext()).thenReturn(mUserContext);
+ when(mQsHost.getUserContext()).thenReturn(mUserContext);
when(mUserContext.getUser()).thenReturn(UserHandle.of(USER));
mPackageManager = Mockito.spy(mContext.getPackageManager());
when(mPackageManager.getPermissionControllerPackageName())
@@ -174,7 +174,7 @@
WalletController walletController,
SafetyController safetyController,
@Named(RBC_AVAILABLE) boolean isReduceBrightColorsAvailable) {
- return new AutoTileManager(context, autoAddTrackerBuilder, mQsTileHost,
+ return new AutoTileManager(context, autoAddTrackerBuilder, mQsHost,
Handler.createAsync(TestableLooper.get(this).getLooper()),
mSecureSettings,
hotspotController,
@@ -359,7 +359,7 @@
return;
}
mAutoTileManager.mNightDisplayCallback.onActivated(true);
- verify(mQsTileHost).addTile("night");
+ verify(mQsHost).addTile("night");
}
@Test
@@ -368,7 +368,7 @@
return;
}
mAutoTileManager.mNightDisplayCallback.onActivated(false);
- verify(mQsTileHost, never()).addTile("night");
+ verify(mQsHost, never()).addTile("night");
}
@Test
@@ -378,7 +378,7 @@
}
mAutoTileManager.mNightDisplayCallback.onAutoModeChanged(
ColorDisplayManager.AUTO_MODE_TWILIGHT);
- verify(mQsTileHost).addTile("night");
+ verify(mQsHost).addTile("night");
}
@Test
@@ -388,7 +388,7 @@
}
mAutoTileManager.mNightDisplayCallback.onAutoModeChanged(
ColorDisplayManager.AUTO_MODE_CUSTOM_TIME);
- verify(mQsTileHost).addTile("night");
+ verify(mQsHost).addTile("night");
}
@Test
@@ -398,19 +398,19 @@
}
mAutoTileManager.mNightDisplayCallback.onAutoModeChanged(
ColorDisplayManager.AUTO_MODE_DISABLED);
- verify(mQsTileHost, never()).addTile("night");
+ verify(mQsHost, never()).addTile("night");
}
@Test
public void reduceBrightColorsTileAdded_whenActivated() {
mAutoTileManager.mReduceBrightColorsCallback.onActivated(true);
- verify(mQsTileHost).addTile("reduce_brightness");
+ verify(mQsHost).addTile("reduce_brightness");
}
@Test
public void reduceBrightColorsTileNotAdded_whenDeactivated() {
mAutoTileManager.mReduceBrightColorsCallback.onActivated(false);
- verify(mQsTileHost, never()).addTile("reduce_brightness");
+ verify(mQsHost, never()).addTile("reduce_brightness");
}
private static List<CastDevice> buildFakeCastDevice(boolean isCasting) {
@@ -423,28 +423,28 @@
public void castTileAdded_whenDeviceIsCasting() {
doReturn(buildFakeCastDevice(true)).when(mCastController).getCastDevices();
mAutoTileManager.mCastCallback.onCastDevicesChanged();
- verify(mQsTileHost).addTile("cast");
+ verify(mQsHost).addTile("cast");
}
@Test
public void castTileNotAdded_whenDeviceIsNotCasting() {
doReturn(buildFakeCastDevice(false)).when(mCastController).getCastDevices();
mAutoTileManager.mCastCallback.onCastDevicesChanged();
- verify(mQsTileHost, never()).addTile("cast");
+ verify(mQsHost, never()).addTile("cast");
}
@Test
public void testSettingTileAdded_onChanged() {
changeValue(TEST_SETTING, 1);
verify(mAutoAddTracker).setTileAdded(TEST_SPEC);
- verify(mQsTileHost).addTile(TEST_SPEC);
+ verify(mQsHost).addTile(TEST_SPEC);
}
@Test
public void testSettingTileAddedComponentAtEnd_onChanged() {
changeValue(TEST_SETTING_COMPONENT, 1);
verify(mAutoAddTracker).setTileAdded(TEST_CUSTOM_SPEC);
- verify(mQsTileHost).addTile(ComponentName.unflattenFromString(TEST_COMPONENT)
+ verify(mQsHost).addTile(ComponentName.unflattenFromString(TEST_COMPONENT)
, /* end */ true);
}
@@ -453,14 +453,14 @@
changeValue(TEST_SETTING, 1);
changeValue(TEST_SETTING, 2);
verify(mAutoAddTracker).setTileAdded(TEST_SPEC);
- verify(mQsTileHost).addTile(TEST_SPEC);
+ verify(mQsHost).addTile(TEST_SPEC);
}
@Test
public void testSettingTileNotAdded_onChangedTo0() {
changeValue(TEST_SETTING, 0);
verify(mAutoAddTracker, never()).setTileAdded(TEST_SPEC);
- verify(mQsTileHost, never()).addTile(TEST_SPEC);
+ verify(mQsHost, never()).addTile(TEST_SPEC);
}
@Test
@@ -469,27 +469,27 @@
changeValue(TEST_SETTING, 1);
verify(mAutoAddTracker, never()).setTileAdded(TEST_SPEC);
- verify(mQsTileHost, never()).addTile(TEST_SPEC);
+ verify(mQsHost, never()).addTile(TEST_SPEC);
}
@Test
public void testSafetyTileNotAdded_ifPreviouslyAdded() {
ComponentName safetyComponent = CustomTile.getComponentFromSpec(TEST_CUSTOM_SAFETY_SPEC);
mAutoTileManager.init();
- verify(mQsTileHost, times(1)).addTile(safetyComponent, true);
+ verify(mQsHost, times(1)).addTile(safetyComponent, true);
when(mAutoAddTracker.isAdded(TEST_CUSTOM_SAFETY_SPEC)).thenReturn(true);
mAutoTileManager.init();
- verify(mQsTileHost, times(1)).addTile(safetyComponent, true);
+ verify(mQsHost, times(1)).addTile(safetyComponent, true);
}
@Test
public void testSafetyTileAdded_onUserChange() {
ComponentName safetyComponent = CustomTile.getComponentFromSpec(TEST_CUSTOM_SAFETY_SPEC);
mAutoTileManager.init();
- verify(mQsTileHost, times(1)).addTile(safetyComponent, true);
+ verify(mQsHost, times(1)).addTile(safetyComponent, true);
when(mAutoAddTracker.isAdded(TEST_CUSTOM_SAFETY_SPEC)).thenReturn(false);
mAutoTileManager.changeUser(UserHandle.of(USER + 1));
- verify(mQsTileHost, times(2)).addTile(safetyComponent, true);
+ verify(mQsHost, times(2)).addTile(safetyComponent, true);
}
@Test
@@ -498,17 +498,17 @@
mAutoTileManager.init();
when(mAutoAddTracker.isAdded(TEST_CUSTOM_SAFETY_SPEC)).thenReturn(true);
mAutoTileManager.mSafetyCallback.onSafetyCenterEnableChanged(false);
- verify(mQsTileHost, times(1)).removeTile(TEST_CUSTOM_SAFETY_SPEC);
+ verify(mQsHost, times(1)).removeTile(TEST_CUSTOM_SAFETY_SPEC);
}
@Test
public void testSafetyTileAdded_onSafetyCenterEnable() {
ComponentName safetyComponent = CustomTile.getComponentFromSpec(TEST_CUSTOM_SAFETY_SPEC);
mAutoTileManager.init();
- verify(mQsTileHost, times(1)).addTile(safetyComponent, true);
+ verify(mQsHost, times(1)).addTile(safetyComponent, true);
mAutoTileManager.mSafetyCallback.onSafetyCenterEnableChanged(false);
mAutoTileManager.mSafetyCallback.onSafetyCenterEnableChanged(true);
- verify(mQsTileHost, times(2)).addTile(safetyComponent, true);
+ verify(mQsHost, times(2)).addTile(safetyComponent, true);
}
@Test
@@ -525,7 +525,7 @@
mManagedProfileCallback.onManagedProfileChanged();
- verify(mQsTileHost, times(1)).addTile(eq("work"), eq(2));
+ verify(mQsHost, times(1)).addTile(eq("work"), eq(2));
verify(mAutoAddTracker, times(1)).setTileAdded(eq("work"));
}
@@ -542,7 +542,7 @@
mManagedProfileCallback.onManagedProfileChanged();
- verify(mQsTileHost, times(1)).removeTile(eq("work"));
+ verify(mQsHost, times(1)).removeTile(eq("work"));
verify(mAutoAddTracker, times(1)).setTileRemoved(eq("work"));
}
@@ -550,7 +550,7 @@
public void testAddControlsTileIfNotPresent() {
String spec = DEVICE_CONTROLS;
when(mAutoAddTracker.isAdded(eq(spec))).thenReturn(false);
- when(mQsTileHost.getTiles()).thenReturn(new ArrayList<>());
+ when(mQsHost.getTiles()).thenReturn(new ArrayList<>());
mAutoTileManager.init();
ArgumentCaptor<DeviceControlsController.Callback> captor =
@@ -559,7 +559,7 @@
verify(mDeviceControlsController).setCallback(captor.capture());
captor.getValue().onControlsUpdate(3);
- verify(mQsTileHost).addTile(spec, 3);
+ verify(mQsHost).addTile(spec, 3);
verify(mAutoAddTracker).setTileAdded(spec);
}
@@ -567,7 +567,7 @@
public void testDontAddControlsTileIfPresent() {
String spec = DEVICE_CONTROLS;
when(mAutoAddTracker.isAdded(eq(spec))).thenReturn(false);
- when(mQsTileHost.getTiles()).thenReturn(new ArrayList<>());
+ when(mQsHost.getTiles()).thenReturn(new ArrayList<>());
mAutoTileManager.init();
ArgumentCaptor<DeviceControlsController.Callback> captor =
@@ -576,7 +576,7 @@
verify(mDeviceControlsController).setCallback(captor.capture());
captor.getValue().removeControlsAutoTracker();
- verify(mQsTileHost, never()).addTile(spec, 3);
+ verify(mQsHost, never()).addTile(spec, 3);
verify(mAutoAddTracker, never()).setTileAdded(spec);
verify(mAutoAddTracker).setTileRemoved(spec);
}
@@ -587,7 +587,7 @@
when(mAutoAddTracker.isAdded(eq(spec))).thenReturn(true);
QSTile mockTile = mock(QSTile.class);
when(mockTile.getTileSpec()).thenReturn(spec);
- when(mQsTileHost.getTiles()).thenReturn(List.of(mockTile));
+ when(mQsHost.getTiles()).thenReturn(List.of(mockTile));
mAutoTileManager.init();
ArgumentCaptor<DeviceControlsController.Callback> captor =
@@ -596,7 +596,7 @@
verify(mDeviceControlsController).setCallback(captor.capture());
captor.getValue().onControlsUpdate(3);
- verify(mQsTileHost, never()).addTile(spec, 3);
+ verify(mQsHost, never()).addTile(spec, 3);
verify(mAutoAddTracker, never()).setTileAdded(spec);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index 1e2fe35..31a1e4f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -43,6 +43,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.qs.QSHost;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.CameraLauncher;
import com.android.systemui.shade.NotificationPanelViewController;
@@ -94,6 +95,7 @@
@Mock private SystemBarAttributesListener mSystemBarAttributesListener;
@Mock private Lazy<CameraLauncher> mCameraLauncherLazy;
@Mock private UserTracker mUserTracker;
+ @Mock private QSHost mQSHost;
CentralSurfacesCommandQueueCallbacks mSbcqCallbacks;
@@ -128,7 +130,8 @@
DEFAULT_DISPLAY,
mSystemBarAttributesListener,
mCameraLauncherLazy,
- mUserTracker);
+ mUserTracker,
+ mQSHost);
when(mUserTracker.getUserHandle()).thenReturn(
UserHandle.of(ActivityManager.getCurrentUser()));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java
index 1582cee..7d9c091 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java
@@ -32,7 +32,7 @@
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
+import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider;
import com.android.systemui.statusbar.window.StatusBarWindowController;
import com.android.wm.shell.bubbles.Bubbles;
@@ -62,7 +62,7 @@
@Mock
DozeParameters mDozeParameters;
@Mock
- CommonNotifCollection mNotifCollection;
+ SectionStyleProvider mSectionStyleProvider;
@Mock
DarkIconDispatcher mDarkIconDispatcher;
@Mock
@@ -87,6 +87,7 @@
mNotificationMediaManager,
mListener,
mDozeParameters,
+ mSectionStyleProvider,
Optional.of(mBubbles),
mDemoModeController,
mDarkIconDispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 85e8c34..64545b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -32,7 +32,6 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-import android.animation.Animator;
import android.app.Fragment;
import android.app.StatusBarManager;
import android.content.Context;
@@ -45,6 +44,7 @@
import android.view.ViewPropertyAnimator;
import android.widget.FrameLayout;
+import androidx.core.animation.Animator;
import androidx.test.filters.SmallTest;
import com.android.keyguard.KeyguardUpdateMonitor;
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 0926f8a..a359216 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -142,7 +142,7 @@
protected final SystemSupport mSystemSupport;
protected final WindowManagerInternal mWindowManagerService;
private final SystemActionPerformer mSystemActionPerformer;
- private final AccessibilityWindowManager mA11yWindowManager;
+ final AccessibilityWindowManager mA11yWindowManager;
private final DisplayManager mDisplayManager;
private final PowerManager mPowerManager;
private final IPlatformCompat mIPlatformCompat;
@@ -2006,15 +2006,14 @@
return accessibilityWindowId;
}
- private int resolveAccessibilityWindowIdForFindFocusLocked(int windowId, int focusType) {
+ int resolveAccessibilityWindowIdForFindFocusLocked(int windowId, int focusType) {
if (windowId == AccessibilityWindowInfo.ANY_WINDOW_ID) {
final int focusedWindowId = mA11yWindowManager.getFocusedWindowId(focusType);
// If the caller is a proxy and the found window doesn't belong to a proxy display
// (or vice versa), then return null. This doesn't work if there are multiple active
- // proxys, but in the future this code shouldn't be needed if input and a11y focus are
+ // proxys, but in the future this code shouldn't be needed if input focus are
// properly split. (so we will deal with the issues if we see them).
- //TODO(254545943): Remove this when there is user and proxy separation of input and a11y
- // focus
+ //TODO(254545943): Remove this when there is user and proxy separation of input focus
if (!mA11yWindowManager.windowIdBelongsToDisplayType(focusedWindowId, mDisplayTypes)) {
return AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index cde820a..1f8a779 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -3823,7 +3823,7 @@
try {
mProxyManager.registerProxy(client, displayId, mContext,
sIdCounter++, mMainHandler, mSecurityPolicy, this, getTraceManager(),
- mWindowManagerService, mA11yWindowManager);
+ mWindowManagerService);
synchronized (mLock) {
notifyClearAccessibilityCacheLocked();
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index f0c6c4f..094053e 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -19,6 +19,7 @@
import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION;
import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
+import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import static com.android.server.accessibility.AbstractAccessibilityServiceConnection.DISPLAY_TYPE_DEFAULT;
@@ -104,6 +105,8 @@
private boolean mTouchInteractionInProgress;
+ private boolean mHasProxy;
+
/** List of Display Windows Observer, mapping from displayId -> DisplayWindowsObserver. */
private final SparseArray<DisplayWindowsObserver> mDisplayWindowsObservers =
new SparseArray<>();
@@ -159,6 +162,9 @@
* Returns {@code true} if the window belongs to a display of {@code displayTypes}.
*/
public boolean windowIdBelongsToDisplayType(int focusedWindowId, int displayTypes) {
+ if (!mHasProxy) {
+ return true;
+ }
// UIAutomation wants focus from any display type.
final int displayTypeMask = DISPLAY_TYPE_PROXY | DISPLAY_TYPE_DEFAULT;
if ((displayTypes & displayTypeMask) == displayTypeMask) {
@@ -195,6 +201,7 @@
private List<AccessibilityWindowInfo> mWindows;
private boolean mTrackingWindows = false;
private boolean mHasWatchOutsideTouchWindow;
+ private int mProxyDisplayAccessibilityFocusedWindow;
private boolean mIsProxy;
/**
@@ -608,8 +615,11 @@
final int windowCount = windows.size();
final boolean isTopFocusedDisplay = mDisplayId == mTopFocusedDisplayId;
+ // A proxy with an a11y-focused window is a11y-focused should use the proxy focus id.
final boolean isAccessibilityFocusedDisplay =
- mDisplayId == mAccessibilityFocusedDisplayId;
+ mDisplayId == mAccessibilityFocusedDisplayId
+ || (mIsProxy && mProxyDisplayAccessibilityFocusedWindow
+ != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID);
// Modifies the value of top focused window, active window and a11y focused window
// only if this display is top focused display which has the top focused window.
if (isTopFocusedDisplay) {
@@ -635,9 +645,12 @@
// We'll clear accessibility focus if the window with focus is no longer visible to
// accessibility services.
+ int a11yFocusedWindowId = mIsProxy
+ ? mProxyDisplayAccessibilityFocusedWindow
+ : mAccessibilityFocusedWindowId;
if (isAccessibilityFocusedDisplay) {
- shouldClearAccessibilityFocus = mAccessibilityFocusedWindowId
- != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+ shouldClearAccessibilityFocus = a11yFocusedWindowId
+ != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
}
boolean hasWindowIgnore = false;
@@ -701,7 +714,7 @@
if (isAccessibilityFocusedDisplay) {
for (int i = 0; i < accessibilityWindowCount; i++) {
final AccessibilityWindowInfo window = mWindows.get(i);
- if (window.getId() == mAccessibilityFocusedWindowId) {
+ if (window.getId() == a11yFocusedWindowId) {
window.setAccessibilityFocused(true);
shouldClearAccessibilityFocus = false;
break;
@@ -718,7 +731,7 @@
}
if (shouldClearAccessibilityFocus) {
- clearAccessibilityFocusLocked(mAccessibilityFocusedWindowId);
+ clearAccessibilityFocusLocked(a11yFocusedWindowId);
}
}
@@ -1022,6 +1035,7 @@
}
if (proxyed && !observer.mIsProxy) {
observer.mIsProxy = true;
+ mHasProxy = true;
}
if (observer.isTrackingWindowsLocked()) {
return;
@@ -1044,6 +1058,7 @@
observer.stopTrackingWindowsLocked();
mDisplayWindowsObservers.remove(displayId);
}
+ resetHasProxyIfNeededLocked();
}
}
@@ -1053,11 +1068,26 @@
*/
public void stopTrackingDisplayProxy(int displayId) {
synchronized (mLock) {
- DisplayWindowsObserver observer = mDisplayWindowsObservers.get(displayId);
+ final DisplayWindowsObserver proxyObserver = mDisplayWindowsObservers.get(displayId);
+ if (proxyObserver != null) {
+ proxyObserver.mIsProxy = false;
+ }
+ resetHasProxyIfNeededLocked();
+ }
+ }
+
+ private void resetHasProxyIfNeededLocked() {
+ boolean hasProxy = false;
+ final int count = mDisplayWindowsObservers.size();
+ for (int i = 0; i < count; i++) {
+ final DisplayWindowsObserver observer = mDisplayWindowsObservers.valueAt(i);
if (observer != null) {
- observer.mIsProxy = false;
+ if (observer.mIsProxy) {
+ hasProxy = true;
+ }
}
}
+ mHasProxy = hasProxy;
}
/**
@@ -1490,6 +1520,11 @@
case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: {
synchronized (mLock) {
+ // If window id belongs to a proxy display, then find the display, update the
+ // observer focus and send WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED events.
+ if (mHasProxy && setProxyFocusLocked(windowId)) {
+ return;
+ }
if (mAccessibilityFocusedWindowId != windowId) {
clearAccessibilityFocusLocked(mAccessibilityFocusedWindowId);
setAccessibilityFocusedWindowLocked(windowId);
@@ -1500,6 +1535,10 @@
case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: {
synchronized (mLock) {
+ // If cleared happened on the proxy display, then clear the tracked focus.
+ if (mHasProxy && clearProxyFocusLocked(windowId, eventAction)) {
+ return;
+ }
if (mAccessibilityFocusNodeId == nodeId) {
mAccessibilityFocusNodeId = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
}
@@ -1599,9 +1638,10 @@
if (mAccessibilityFocusedDisplayId != Display.INVALID_DISPLAY
&& mAccessibilityFocusedWindowId
!= AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) {
+ // Previously focused window -> send a focused event for losing focus
events.add(AccessibilityEvent.obtainWindowsChangedEvent(
mAccessibilityFocusedDisplayId, mAccessibilityFocusedWindowId,
- AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED));
+ WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED));
}
mAccessibilityFocusedWindowId = windowId;
@@ -1611,8 +1651,9 @@
final DisplayWindowsObserver observer = mDisplayWindowsObservers.valueAt(i);
if (observer != null && observer.setAccessibilityFocusedWindowLocked(windowId)) {
mAccessibilityFocusedDisplayId = observer.mDisplayId;
+ // Newly focused window -> send a focused event for gaining focus
events.add(AccessibilityEvent.obtainWindowsChangedEvent(observer.mDisplayId,
- windowId, AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED));
+ windowId, WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED));
}
}
@@ -1662,6 +1703,33 @@
* @return The focused windowId
*/
public int getFocusedWindowId(int focusType) {
+ return getFocusedWindowId(focusType, Display.INVALID_DISPLAY);
+ }
+
+ /**
+ * Returns focused windowId or accessibility focused windowId according to given focusType and
+ * display id.
+ * @param focusType {@link AccessibilityNodeInfo#FOCUS_INPUT} or
+ * {@link AccessibilityNodeInfo#FOCUS_ACCESSIBILITY}
+ * @param displayId the display id to check. If this display is proxy-ed, the proxy's a11y focus
+ * will be returned.
+ * @return The focused windowId
+ */
+ public int getFocusedWindowId(int focusType, int displayId) {
+ if (displayId == Display.INVALID_DISPLAY || displayId == Display.DEFAULT_DISPLAY
+ || !mHasProxy) {
+ return getDefaultFocus(focusType);
+ }
+
+ final DisplayWindowsObserver observer = mDisplayWindowsObservers.get(displayId);
+ if (observer != null && observer.mIsProxy) {
+ return getProxyFocus(focusType, observer);
+ } else {
+ return getDefaultFocus(focusType);
+ }
+ }
+
+ private int getDefaultFocus(int focusType) {
if (focusType == AccessibilityNodeInfo.FOCUS_INPUT) {
return mTopFocusedWindowId;
} else if (focusType == AccessibilityNodeInfo.FOCUS_ACCESSIBILITY) {
@@ -1670,6 +1738,16 @@
return AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
}
+ private int getProxyFocus(int focusType, DisplayWindowsObserver observer) {
+ if (focusType == AccessibilityNodeInfo.FOCUS_INPUT) {
+ return mTopFocusedWindowId;
+ } else if (focusType == AccessibilityNodeInfo.FOCUS_ACCESSIBILITY) {
+ return observer.mProxyDisplayAccessibilityFocusedWindow;
+ } else {
+ return AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+ }
+ }
+
/**
* Returns {@link AccessibilityWindowInfo} of PIP window.
*
@@ -1988,6 +2066,78 @@
}
/**
+ * Checks if the window belongs to a proxy display and if so clears the focused window id.
+ * @param focusClearedWindowId the cleared window id.
+ * @return true if an observer is proxy-ed and has cleared its focused window id.
+ */
+ private boolean clearProxyFocusLocked(int focusClearedWindowId, int eventAction) {
+ // If we are just moving focus from one view to the other in the same window, do nothing.
+ if (eventAction == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS) {
+ return false;
+ }
+ for (int i = 0; i < mDisplayWindowsObservers.size(); i++) {
+ final DisplayWindowsObserver observer = mDisplayWindowsObservers.get(i);
+ if (observer != null && observer.mWindows != null && observer.mIsProxy) {
+ final int windowCount = observer.mWindows.size();
+ for (int j = 0; j < windowCount; j++) {
+ AccessibilityWindowInfo window = observer.mWindows.get(j);
+ if (window.getId() == focusClearedWindowId) {
+ observer.mProxyDisplayAccessibilityFocusedWindow =
+ AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+ // TODO(268754409): Look into sending a WINDOW_FOCUS_CHANGED event since
+ // window no longer has focus (default window logic doesn't), and
+ // whether the node id needs to be cached (default window logic does).
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks if the window belongs to a proxy display and if so sends
+ * WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED for that window and the previously focused window.
+ * @param focusedWindowId the focused window id.
+ * @return true if an observer is proxy-ed and contains the focused window.
+ */
+ private boolean setProxyFocusLocked(int focusedWindowId) {
+ for (int i = 0; i < mDisplayWindowsObservers.size(); i++) {
+ final DisplayWindowsObserver observer = mDisplayWindowsObservers.valueAt(i);
+ if (observer != null && observer.mIsProxy
+ && observer.setAccessibilityFocusedWindowLocked(focusedWindowId)) {
+ final int previouslyFocusedWindowId =
+ observer.mProxyDisplayAccessibilityFocusedWindow;
+
+ if (previouslyFocusedWindowId == focusedWindowId) {
+ // Don't send a focus event if the window is already focused.
+ return true;
+ }
+
+ // Previously focused window -> Clear focus on UI thread and send a focused event
+ // for losing focus
+ if (previouslyFocusedWindowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) {
+ clearAccessibilityFocusLocked(previouslyFocusedWindowId);
+ mAccessibilityEventSender.sendAccessibilityEventForCurrentUserLocked(
+ AccessibilityEvent.obtainWindowsChangedEvent(
+ observer.mDisplayId, previouslyFocusedWindowId,
+ WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED));
+ }
+ observer.mProxyDisplayAccessibilityFocusedWindow = focusedWindowId;
+ // Newly focused window -> send a focused event for it gaining focus
+ mAccessibilityEventSender.sendAccessibilityEventForCurrentUserLocked(
+ AccessibilityEvent.obtainWindowsChangedEvent(
+ observer.mDisplayId,
+ observer.mProxyDisplayAccessibilityFocusedWindow,
+ WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED));
+
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
* Dumps all {@link AccessibilityWindowInfo}s here.
*/
public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
index df913aa..945d43b 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
@@ -255,6 +255,24 @@
}
@Override
+ int resolveAccessibilityWindowIdForFindFocusLocked(int windowId, int focusType) {
+ if (windowId == AccessibilityWindowInfo.ANY_WINDOW_ID) {
+ final int focusedWindowId = mA11yWindowManager.getFocusedWindowId(focusType,
+ mDisplayId);
+ // If the caller is a proxy and the found window doesn't belong to a proxy display
+ // (or vice versa), then return null. This doesn't work if there are multiple active
+ // proxys, but in the future this code shouldn't be needed if input focus
+ // properly split. (so we will deal with the issues if we see them).
+ //TODO(254545943): Remove this when there is user and proxy separation of input
+ if (!mA11yWindowManager.windowIdBelongsToDisplayType(focusedWindowId, mDisplayTypes)) {
+ return AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+ }
+ return focusedWindowId;
+ }
+ return windowId;
+ }
+
+ @Override
public void binderDied() {
}
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
index 2530338..9d91d10 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
@@ -78,8 +78,7 @@
AccessibilitySecurityPolicy securityPolicy,
AbstractAccessibilityServiceConnection.SystemSupport systemSupport,
AccessibilityTrace trace,
- WindowManagerInternal windowManagerInternal,
- AccessibilityWindowManager awm) throws RemoteException {
+ WindowManagerInternal windowManagerInternal) throws RemoteException {
// Set a default AccessibilityServiceInfo that is used before the proxy's info is
// populated. A proxy has the touch exploration and window capabilities.
@@ -93,7 +92,7 @@
new ProxyAccessibilityServiceConnection(context, info.getComponentName(), info,
id, mainHandler, mLock, securityPolicy, systemSupport, trace,
windowManagerInternal,
- awm, displayId);
+ mA11yWindowManager, displayId);
synchronized (mLock) {
mProxyA11yServiceConnections.put(displayId, connection);
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index 9c84c04..f85ef43f 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -44,6 +44,8 @@
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
import android.provider.Settings;
import android.util.Log;
import android.util.MathUtils;
@@ -279,7 +281,8 @@
return mTempPointerProperties;
}
- private void transitionTo(State state) {
+ @VisibleForTesting
+ void transitionTo(State state) {
if (DEBUG_STATE_TRANSITIONS) {
Slog.i(mLogTag,
(State.nameOf(mCurrentState) + " -> " + State.nameOf(state)
@@ -287,6 +290,9 @@
.replace(getClass().getName(), ""));
}
mPreviousState = mCurrentState;
+ if (state == mPanningScalingState) {
+ mPanningScalingState.prepareForState();
+ }
mCurrentState = state;
}
@@ -317,18 +323,34 @@
final class PanningScalingState extends SimpleOnGestureListener
implements OnScaleGestureListener, State {
+ private final Context mContext;
private final ScaleGestureDetector mScaleGestureDetector;
private final GestureDetector mScrollGestureDetector;
final float mScalingThreshold;
float mInitialScaleFactor = -1;
- boolean mScaling;
+ @VisibleForTesting boolean mScaling;
+
+ /**
+ * Whether it needs to detect the target scale passes
+ * {@link FullScreenMagnificationController#getPersistedScale} during panning scale.
+ */
+ @VisibleForTesting boolean mDetectingPassPersistedScale;
+
+ // The threshold for relative difference from given scale to persisted scale. If the
+ // difference >= threshold, we can start detecting if the scale passes the persisted
+ // scale during panning.
+ @VisibleForTesting static final float CHECK_DETECTING_PASS_PERSISTED_SCALE_THRESHOLD = 0.2f;
+ // The threshold for relative difference from given scale to persisted scale. If the
+ // difference < threshold, we can decide that the scale passes the persisted scale.
+ @VisibleForTesting static final float PASSING_PERSISTED_SCALE_THRESHOLD = 0.01f;
PanningScalingState(Context context) {
final TypedValue scaleValue = new TypedValue();
context.getResources().getValue(
R.dimen.config_screen_magnification_scaling_threshold,
scaleValue, false);
+ mContext = context;
mScalingThreshold = scaleValue.getFloat();
mScaleGestureDetector = new ScaleGestureDetector(context, this, Handler.getMain());
mScaleGestureDetector.setQuickScaleEnabled(false);
@@ -351,12 +373,59 @@
}
}
+
+ void prepareForState() {
+ checkShouldDetectPassPersistedScale();
+ }
+
+ private void checkShouldDetectPassPersistedScale() {
+ if (mDetectingPassPersistedScale) {
+ return;
+ }
+
+ final float currentScale =
+ mFullScreenMagnificationController.getScale(mDisplayId);
+ final float persistedScale =
+ mFullScreenMagnificationController.getPersistedScale(mDisplayId);
+
+ mDetectingPassPersistedScale =
+ (abs(currentScale - persistedScale) / persistedScale)
+ >= CHECK_DETECTING_PASS_PERSISTED_SCALE_THRESHOLD;
+ }
+
public void persistScaleAndTransitionTo(State state) {
mFullScreenMagnificationController.persistScale(mDisplayId);
clear();
transitionTo(state);
}
+ @VisibleForTesting
+ void setScaleAndClearIfNeeded(float scale, float pivotX, float pivotY) {
+ if (mDetectingPassPersistedScale) {
+ final float persistedScale =
+ mFullScreenMagnificationController.getPersistedScale(mDisplayId);
+ // If the scale passes the persisted scale during panning, perform a vibration
+ // feedback to user. Also, call {@link clear} to create a buffer zone so that
+ // user needs to panning more than {@link mScalingThreshold} to change scale again.
+ if (abs(scale - persistedScale) / persistedScale
+ < PASSING_PERSISTED_SCALE_THRESHOLD) {
+ scale = persistedScale;
+ final Vibrator vibrator = mContext.getSystemService(Vibrator.class);
+ if (vibrator != null) {
+ vibrator.vibrate(
+ VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK));
+ }
+ clear();
+ }
+ }
+
+ if (DEBUG_PANNING_SCALING) Slog.i(mLogTag, "Scaled content to: " + scale + "x");
+ mFullScreenMagnificationController.setScale(mDisplayId, scale, pivotX, pivotY, false,
+ AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+
+ checkShouldDetectPassPersistedScale();
+ }
+
@Override
public boolean onScroll(MotionEvent first, MotionEvent second,
float distanceX, float distanceY) {
@@ -402,11 +471,7 @@
scale = targetScale;
}
- final float pivotX = detector.getFocusX();
- final float pivotY = detector.getFocusY();
- if (DEBUG_PANNING_SCALING) Slog.i(mLogTag, "Scaled content to: " + scale + "x");
- mFullScreenMagnificationController.setScale(mDisplayId, scale, pivotX, pivotY, false,
- AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+ setScaleAndClearIfNeeded(scale, detector.getFocusX(), detector.getFocusY());
return /* handled: */ true;
}
@@ -424,6 +489,7 @@
public void clear() {
mInitialScaleFactor = -1;
mScaling = false;
+ mDetectingPassPersistedScale = false;
}
@Override
diff --git a/services/api/current.txt b/services/api/current.txt
index 329dbdf..b55166c 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -43,6 +43,8 @@
method @Deprecated public boolean bindSdkSandboxService(@NonNull android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull String, @NonNull String, int) throws android.os.RemoteException;
method public boolean canStartForegroundService(int, int, @NonNull String);
method public void killSdkSandboxClientAppProcess(@NonNull android.os.IBinder);
+ method @Nullable public android.content.ComponentName startSdkSandboxService(@NonNull android.content.Intent, int, @NonNull String, @NonNull String) throws android.os.RemoteException;
+ method public boolean stopSdkSandboxService(@NonNull android.content.Intent, int, @NonNull String, @NonNull String);
}
}
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 b4dcf43..f650560 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -51,6 +51,10 @@
import android.content.pm.ActivityInfo;
import android.graphics.PointF;
import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerGlobal;
+import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.IVirtualDisplayCallback;
+import android.hardware.display.VirtualDisplayConfig;
import android.hardware.input.VirtualDpadConfig;
import android.hardware.input.VirtualKeyEvent;
import android.hardware.input.VirtualKeyboardConfig;
@@ -82,6 +86,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.BlockedAppStreamingActivity;
+import com.android.server.LocalServices;
import com.android.server.companion.virtual.GenericWindowPolicyController.RunningAppsChangedListener;
import com.android.server.companion.virtual.audio.VirtualAudioController;
@@ -109,21 +114,29 @@
private final Context mContext;
private final AssociationInfo mAssociationInfo;
+ private final VirtualDeviceManagerService mService;
private final PendingTrampolineCallback mPendingTrampolineCallback;
private final int mOwnerUid;
private final int mDeviceId;
+ // Thou shall not hold the mVirtualDeviceLock over the mInputController calls.
+ // Holding the lock can lead to lock inversion with GlobalWindowManagerLock.
+ // 1. After display is created the window manager calls into VDM during construction
+ // of display specific context to fetch device id corresponding to the display.
+ // mVirtualDeviceLock will be held while this is done.
+ // 2. InputController interactions result in calls to DisplayManager (to set IME,
+ // possibly more indirect calls), and those attempt to lock GlobalWindowManagerLock which
+ // creates lock inversion.
private final InputController mInputController;
private final SensorController mSensorController;
private final CameraAccessController mCameraAccessController;
private VirtualAudioController mVirtualAudioController;
- @VisibleForTesting
- final ArraySet<Integer> mVirtualDisplayIds = new ArraySet<>();
- private final OnDeviceCloseListener mOnDeviceCloseListener;
private final IBinder mAppToken;
private final VirtualDeviceParams mParams;
- private final Map<Integer, PowerManager.WakeLock> mPerDisplayWakelocks = new ArrayMap<>();
+ @GuardedBy("mVirtualDeviceLock")
+ private final SparseArray<VirtualDisplayWrapper> mVirtualDisplays = new SparseArray<>();
private final IVirtualDeviceActivityListener mActivityListener;
private final IVirtualDeviceSoundEffectListener mSoundEffectListener;
+ private final DisplayManagerGlobal mDisplayManager;
@GuardedBy("mVirtualDeviceLock")
private final Map<IBinder, IntentFilter> mIntentInterceptors = new ArrayMap<>();
@NonNull
@@ -174,21 +187,14 @@
};
}
- /**
- * A mapping from the virtual display ID to its corresponding
- * {@link GenericWindowPolicyController}.
- */
- private final SparseArray<GenericWindowPolicyController> mWindowPolicyControllers =
- new SparseArray<>();
-
VirtualDeviceImpl(
Context context,
AssociationInfo associationInfo,
+ VirtualDeviceManagerService service,
IBinder token,
int ownerUid,
int deviceId,
CameraAccessController cameraAccessController,
- OnDeviceCloseListener onDeviceCloseListener,
PendingTrampolineCallback pendingTrampolineCallback,
IVirtualDeviceActivityListener activityListener,
IVirtualDeviceSoundEffectListener soundEffectListener,
@@ -197,40 +203,43 @@
this(
context,
associationInfo,
+ service,
token,
ownerUid,
deviceId,
/* inputController= */ null,
/* sensorController= */ null,
cameraAccessController,
- onDeviceCloseListener,
pendingTrampolineCallback,
activityListener,
soundEffectListener,
runningAppsChangedCallback,
- params);
+ params,
+ DisplayManagerGlobal.getInstance());
}
@VisibleForTesting
VirtualDeviceImpl(
Context context,
AssociationInfo associationInfo,
+ VirtualDeviceManagerService service,
IBinder token,
int ownerUid,
int deviceId,
InputController inputController,
SensorController sensorController,
CameraAccessController cameraAccessController,
- OnDeviceCloseListener onDeviceCloseListener,
PendingTrampolineCallback pendingTrampolineCallback,
IVirtualDeviceActivityListener activityListener,
IVirtualDeviceSoundEffectListener soundEffectListener,
Consumer<ArraySet<Integer>> runningAppsChangedCallback,
- VirtualDeviceParams params) {
+ VirtualDeviceParams params,
+ DisplayManagerGlobal displayManager) {
super(PermissionEnforcer.fromContext(context));
UserHandle ownerUserHandle = UserHandle.getUserHandleForUid(ownerUid);
mContext = context.createContextAsUser(ownerUserHandle, 0);
mAssociationInfo = associationInfo;
+ mService = service;
mPendingTrampolineCallback = pendingTrampolineCallback;
mActivityListener = activityListener;
mSoundEffectListener = soundEffectListener;
@@ -239,6 +248,7 @@
mDeviceId = deviceId;
mAppToken = token;
mParams = params;
+ mDisplayManager = displayManager;
if (inputController == null) {
mInputController = new InputController(
mVirtualDeviceLock,
@@ -259,7 +269,6 @@
}
mCameraAccessController = cameraAccessController;
mCameraAccessController.startObservingIfNeeded();
- mOnDeviceCloseListener = onDeviceCloseListener;
try {
token.linkToDeath(this, 0);
} catch (RemoteException e) {
@@ -331,9 +340,11 @@
@Override // Binder call
public void launchPendingIntent(int displayId, PendingIntent pendingIntent,
ResultReceiver resultReceiver) {
- if (!mVirtualDisplayIds.contains(displayId)) {
- throw new SecurityException("Display ID " + displayId
- + " not found for this virtual device");
+ synchronized (mVirtualDeviceLock) {
+ if (!mVirtualDisplays.contains(displayId)) {
+ throw new SecurityException("Display ID " + displayId
+ + " not found for this virtual device");
+ }
}
if (pendingIntent.isActivity()) {
try {
@@ -383,24 +394,34 @@
@EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void close() {
super.close_enforcePermission();
+ // Remove about-to-be-closed virtual device from the service before butchering it.
+ mService.removeVirtualDevice(mDeviceId);
+
+ VirtualDisplayWrapper[] virtualDisplaysToBeReleased;
synchronized (mVirtualDeviceLock) {
- if (!mPerDisplayWakelocks.isEmpty()) {
- mPerDisplayWakelocks.forEach((displayId, wakeLock) -> {
- Slog.w(TAG, "VirtualDisplay " + displayId + " owned by UID " + mOwnerUid
- + " was not properly released");
- wakeLock.release();
- });
- mPerDisplayWakelocks.clear();
- }
if (mVirtualAudioController != null) {
mVirtualAudioController.stopListening();
mVirtualAudioController = null;
}
mLocaleList = null;
+ virtualDisplaysToBeReleased = new VirtualDisplayWrapper[mVirtualDisplays.size()];
+ for (int i = 0; i < mVirtualDisplays.size(); i++) {
+ virtualDisplaysToBeReleased[i] = mVirtualDisplays.valueAt(i);
+ }
+ mVirtualDisplays.clear();
mVirtualSensorList = null;
mVirtualSensors.clear();
}
- mOnDeviceCloseListener.onClose(mDeviceId);
+ // Destroy the display outside locked section.
+ for (VirtualDisplayWrapper virtualDisplayWrapper : virtualDisplaysToBeReleased) {
+ mDisplayManager.releaseVirtualDisplay(virtualDisplayWrapper.getToken());
+ // The releaseVirtualDisplay call above won't trigger
+ // VirtualDeviceImpl.onVirtualDisplayRemoved callback because we already removed the
+ // virtual device from the service - we release the other display-tied resources here
+ // with the guarantee it will be done exactly once.
+ releaseOwnedVirtualDisplayResources(virtualDisplayWrapper);
+ }
+
mAppToken.unlinkToDeath(this, 0);
mCameraAccessController.stopObservingIfNeeded();
@@ -429,11 +450,6 @@
return mVirtualAudioController;
}
- @VisibleForTesting
- SparseArray<GenericWindowPolicyController> getWindowPolicyControllersForTesting() {
- return mWindowPolicyControllers;
- }
-
@Override // Binder call
@EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void onAudioSessionStarting(int displayId,
@@ -441,7 +457,7 @@
@Nullable IAudioConfigChangedCallback configChangedCallback) {
super.onAudioSessionStarting_enforcePermission();
synchronized (mVirtualDeviceLock) {
- if (!mVirtualDisplayIds.contains(displayId)) {
+ if (!mVirtualDisplays.contains(displayId)) {
throw new SecurityException(
"Cannot start audio session for a display not associated with this virtual "
+ "device");
@@ -449,7 +465,8 @@
if (mVirtualAudioController == null) {
mVirtualAudioController = new VirtualAudioController(mContext);
- GenericWindowPolicyController gwpc = mWindowPolicyControllers.get(displayId);
+ GenericWindowPolicyController gwpc = mVirtualDisplays.get(
+ displayId).getWindowPolicyController();
mVirtualAudioController.startListening(gwpc, routingCallback,
configChangedCallback);
}
@@ -473,7 +490,7 @@
public void createVirtualDpad(VirtualDpadConfig config, @NonNull IBinder deviceToken) {
super.createVirtualDpad_enforcePermission();
synchronized (mVirtualDeviceLock) {
- if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
+ if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) {
throw new SecurityException(
"Cannot create a virtual dpad for a display not associated with "
+ "this virtual device");
@@ -493,7 +510,7 @@
public void createVirtualKeyboard(VirtualKeyboardConfig config, @NonNull IBinder deviceToken) {
super.createVirtualKeyboard_enforcePermission();
synchronized (mVirtualDeviceLock) {
- if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
+ if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) {
throw new SecurityException(
"Cannot create a virtual keyboard for a display not associated with "
+ "this virtual device");
@@ -515,7 +532,7 @@
public void createVirtualMouse(VirtualMouseConfig config, @NonNull IBinder deviceToken) {
super.createVirtualMouse_enforcePermission();
synchronized (mVirtualDeviceLock) {
- if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
+ if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) {
throw new SecurityException(
"Cannot create a virtual mouse for a display not associated with this "
+ "virtual device");
@@ -536,7 +553,7 @@
@NonNull IBinder deviceToken) {
super.createVirtualTouchscreen_enforcePermission();
synchronized (mVirtualDeviceLock) {
- if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
+ if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) {
throw new SecurityException(
"Cannot create a virtual touchscreen for a display not associated with "
+ "this virtual device");
@@ -566,7 +583,7 @@
@NonNull IBinder deviceToken) {
super.createVirtualNavigationTouchpad_enforcePermission();
synchronized (mVirtualDeviceLock) {
- if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
+ if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) {
throw new SecurityException(
"Cannot create a virtual navigation touchpad for a display not associated "
+ "with this virtual device");
@@ -704,7 +721,8 @@
try {
synchronized (mVirtualDeviceLock) {
mDefaultShowPointerIcon = showPointerIcon;
- for (int displayId : mVirtualDisplayIds) {
+ for (int i = 0; i < mVirtualDisplays.size(); i++) {
+ final int displayId = mVirtualDisplays.keyAt(i);
mInputController.setShowPointerIcon(mDefaultShowPointerIcon, displayId);
}
}
@@ -795,8 +813,8 @@
fout.println(" mParams: " + mParams);
fout.println(" mVirtualDisplayIds: ");
synchronized (mVirtualDeviceLock) {
- for (int id : mVirtualDisplayIds) {
- fout.println(" " + id);
+ for (int i = 0; i < mVirtualDisplays.size(); i++) {
+ fout.println(" " + mVirtualDisplays.keyAt(i));
}
fout.println(" mDefaultShowPointerIcon: " + mDefaultShowPointerIcon);
}
@@ -804,61 +822,75 @@
mSensorController.dump(fout);
}
- GenericWindowPolicyController createWindowPolicyController(
+ private GenericWindowPolicyController createWindowPolicyController(
@NonNull List<String> displayCategories) {
- synchronized (mVirtualDeviceLock) {
- final GenericWindowPolicyController gwpc =
- new GenericWindowPolicyController(FLAG_SECURE,
- SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
- getAllowedUserHandles(),
- mParams.getAllowedCrossTaskNavigations(),
- mParams.getBlockedCrossTaskNavigations(),
- mParams.getAllowedActivities(),
- mParams.getBlockedActivities(),
- mParams.getDefaultActivityPolicy(),
- createListenerAdapter(),
- this::onEnteringPipBlocked,
- this::onActivityBlocked,
- this::onSecureWindowShown,
- this::shouldInterceptIntent,
- displayCategories,
- mParams.getDefaultRecentsPolicy());
- gwpc.registerRunningAppsChangedListener(/* listener= */ this);
- return gwpc;
- }
+ final GenericWindowPolicyController gwpc =
+ new GenericWindowPolicyController(FLAG_SECURE,
+ SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
+ getAllowedUserHandles(),
+ mParams.getAllowedCrossTaskNavigations(),
+ mParams.getBlockedCrossTaskNavigations(),
+ mParams.getAllowedActivities(),
+ mParams.getBlockedActivities(),
+ mParams.getDefaultActivityPolicy(),
+ createListenerAdapter(),
+ this::onEnteringPipBlocked,
+ this::onActivityBlocked,
+ this::onSecureWindowShown,
+ this::shouldInterceptIntent,
+ displayCategories,
+ mParams.getDefaultRecentsPolicy());
+ gwpc.registerRunningAppsChangedListener(/* listener= */ this);
+ return gwpc;
}
- void onVirtualDisplayCreatedLocked(GenericWindowPolicyController gwpc, int displayId) {
+ int createVirtualDisplay(@NonNull VirtualDisplayConfig virtualDisplayConfig,
+ @NonNull IVirtualDisplayCallback callback, String packageName) {
+ GenericWindowPolicyController gwpc = createWindowPolicyController(
+ virtualDisplayConfig.getDisplayCategories());
+ DisplayManagerInternal displayManager = LocalServices.getService(
+ DisplayManagerInternal.class);
+ int displayId;
+ displayId = displayManager.createVirtualDisplay(virtualDisplayConfig, callback,
+ this, gwpc, packageName);
+ gwpc.setDisplayId(displayId);
+
synchronized (mVirtualDeviceLock) {
- if (displayId == Display.INVALID_DISPLAY) {
- return;
- }
- if (mVirtualDisplayIds.contains(displayId)) {
+ if (mVirtualDisplays.contains(displayId)) {
+ gwpc.unregisterRunningAppsChangedListener(this);
throw new IllegalStateException(
"Virtual device already has a virtual display with ID " + displayId);
}
- mVirtualDisplayIds.add(displayId);
- gwpc.setDisplayId(displayId);
- mWindowPolicyControllers.put(displayId, gwpc);
+ PowerManager.WakeLock wakeLock = createAndAcquireWakeLockForDisplay(displayId);
+ mVirtualDisplays.put(displayId, new VirtualDisplayWrapper(callback, gwpc, wakeLock));
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
mInputController.setShowPointerIcon(mDefaultShowPointerIcon, displayId);
mInputController.setPointerAcceleration(1f, displayId);
mInputController.setDisplayEligibilityForPointerCapture(/* isEligible= */ false,
displayId);
mInputController.setLocalIme(displayId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return displayId;
+ }
- if (mPerDisplayWakelocks.containsKey(displayId)) {
- Slog.e(TAG, "Not creating wakelock for displayId " + displayId);
- return;
- }
+ private PowerManager.WakeLock createAndAcquireWakeLockForDisplay(int displayId) {
+ final long token = Binder.clearCallingIdentity();
+ try {
PowerManager powerManager = mContext.getSystemService(PowerManager.class);
PowerManager.WakeLock wakeLock = powerManager.newWakeLock(
PowerManager.SCREEN_BRIGHT_WAKE_LOCK,
TAG + ":" + displayId, displayId);
- mPerDisplayWakelocks.put(displayId, wakeLock);
wakeLock.acquire();
+ return wakeLock;
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
}
@@ -872,8 +904,10 @@
}
private void onSecureWindowShown(int displayId, int uid) {
- if (!mVirtualDisplayIds.contains(displayId)) {
- return;
+ synchronized (mVirtualDeviceLock) {
+ if (!mVirtualDisplays.contains(displayId)) {
+ return;
+ }
}
// If a virtual display isn't secure, the screen can't be captured. Show a warning toast
@@ -888,55 +922,102 @@
private ArraySet<UserHandle> getAllowedUserHandles() {
ArraySet<UserHandle> result = new ArraySet<>();
- DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
- UserManager userManager = mContext.getSystemService(UserManager.class);
- for (UserHandle profile : userManager.getAllProfiles()) {
- int nearbyAppStreamingPolicy = dpm.getNearbyAppStreamingPolicy(profile.getIdentifier());
- if (nearbyAppStreamingPolicy == NEARBY_STREAMING_ENABLED
- || nearbyAppStreamingPolicy == NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY) {
- result.add(profile);
- } else if (nearbyAppStreamingPolicy == NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY) {
- if (mParams.getUsersWithMatchingAccounts().contains(profile)) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+ UserManager userManager = mContext.getSystemService(UserManager.class);
+ for (UserHandle profile : userManager.getAllProfiles()) {
+ int nearbyAppStreamingPolicy = dpm.getNearbyAppStreamingPolicy(
+ profile.getIdentifier());
+ if (nearbyAppStreamingPolicy == NEARBY_STREAMING_ENABLED
+ || nearbyAppStreamingPolicy == NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY) {
result.add(profile);
+ } else if (nearbyAppStreamingPolicy == NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY) {
+ if (mParams.getUsersWithMatchingAccounts().contains(profile)) {
+ result.add(profile);
+ }
}
}
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
return result;
}
- void onVirtualDisplayRemovedLocked(int displayId) {
+
+ void onVirtualDisplayRemoved(int displayId) {
+ /* This is callback invoked by VirtualDeviceManagerService when VirtualDisplay was released
+ * by DisplayManager (most probably caused by someone calling VirtualDisplay.close()).
+ * At this point, the display is already released, but we still need to release the
+ * corresponding wakeLock and unregister the RunningAppsChangedListener from corresponding
+ * WindowPolicyController.
+ *
+ * Note that when the display is destroyed during VirtualDeviceImpl.close() call,
+ * this callback won't be invoked because the display is removed from
+ * VirtualDeviceManagerService before any resources are released.
+ */
+ VirtualDisplayWrapper virtualDisplayWrapper;
synchronized (mVirtualDeviceLock) {
- if (!mVirtualDisplayIds.contains(displayId)) {
- throw new IllegalStateException(
- "Virtual device doesn't have a virtual display with ID " + displayId);
- }
- PowerManager.WakeLock wakeLock = mPerDisplayWakelocks.get(displayId);
- if (wakeLock != null) {
- wakeLock.release();
- mPerDisplayWakelocks.remove(displayId);
- }
- GenericWindowPolicyController gwpc = mWindowPolicyControllers.get(displayId);
- if (gwpc != null) {
- gwpc.unregisterRunningAppsChangedListener(/* listener= */ this);
- }
- mVirtualDisplayIds.remove(displayId);
- mWindowPolicyControllers.remove(displayId);
+ virtualDisplayWrapper = mVirtualDisplays.removeReturnOld(displayId);
}
+
+ if (virtualDisplayWrapper == null) {
+ throw new IllegalStateException(
+ "Virtual device doesn't have a virtual display with ID " + displayId);
+ }
+
+ releaseOwnedVirtualDisplayResources(virtualDisplayWrapper);
+
+ }
+
+ /**
+ * Release resources tied to virtual display owned by this VirtualDevice instance.
+ *
+ * Note that this method won't release the virtual display itself.
+ *
+ * @param virtualDisplayWrapper - VirtualDisplayWrapper to release resources for.
+ */
+ private void releaseOwnedVirtualDisplayResources(VirtualDisplayWrapper virtualDisplayWrapper) {
+ virtualDisplayWrapper.getWakeLock().release();
+ virtualDisplayWrapper.getWindowPolicyController().unregisterRunningAppsChangedListener(
+ this);
}
int getOwnerUid() {
return mOwnerUid;
}
+ ArraySet<Integer> getDisplayIds() {
+ synchronized (mVirtualDeviceLock) {
+ final int size = mVirtualDisplays.size();
+ ArraySet<Integer> arraySet = new ArraySet<>(size);
+ for (int i = 0; i < size; i++) {
+ arraySet.append(mVirtualDisplays.keyAt(i));
+ }
+ return arraySet;
+ }
+ }
+
+ @VisibleForTesting
+ GenericWindowPolicyController getDisplayWindowPolicyControllerForTest(int displayId) {
+ VirtualDisplayWrapper virtualDisplayWrapper;
+ synchronized (mVirtualDeviceLock) {
+ virtualDisplayWrapper = mVirtualDisplays.get(displayId);
+ }
+ return virtualDisplayWrapper != null ? virtualDisplayWrapper.getWindowPolicyController()
+ : null;
+ }
+
/**
* Returns true if an app with the given {@code uid} is currently running on this virtual
* device.
*/
boolean isAppRunningOnVirtualDevice(int uid) {
- final int size = mWindowPolicyControllers.size();
- for (int i = 0; i < size; i++) {
- if (mWindowPolicyControllers.valueAt(i).containsUid(uid)) {
- return true;
+ synchronized (mVirtualDeviceLock) {
+ for (int i = 0; i < mVirtualDisplays.size(); i++) {
+ if (mVirtualDisplays.valueAt(i).getWindowPolicyController().containsUid(uid)) {
+ return true;
+ }
}
}
return false;
@@ -957,11 +1038,9 @@
Looper looper) {
synchronized (mVirtualDeviceLock) {
DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
- final int size = mWindowPolicyControllers.size();
- for (int i = 0; i < size; i++) {
- if (mWindowPolicyControllers.valueAt(i).containsUid(uid)) {
- int displayId = mWindowPolicyControllers.keyAt(i);
- Display display = displayManager.getDisplay(displayId);
+ for (int i = 0; i < mVirtualDisplays.size(); i++) {
+ if (mVirtualDisplays.valueAt(i).getWindowPolicyController().containsUid(uid)) {
+ Display display = displayManager.getDisplay(mVirtualDisplays.keyAt(i));
if (display != null && display.isValid()) {
Toast.makeText(mContext.createDisplayContext(display), looper, text,
duration).show();
@@ -972,7 +1051,9 @@
}
boolean isDisplayOwnedByVirtualDevice(int displayId) {
- return mVirtualDisplayIds.contains(displayId);
+ synchronized (mVirtualDeviceLock) {
+ return mVirtualDisplays.contains(displayId);
+ }
}
void onEnteringPipBlocked(int uid) {
@@ -1016,10 +1097,6 @@
}
}
- interface OnDeviceCloseListener {
- void onClose(int deviceId);
- }
-
interface PendingTrampolineCallback {
/**
* Called when the callback should start waiting for the given pending trampoline.
@@ -1073,4 +1150,31 @@
+ ", displayId=" + mDisplayId + "}";
}
}
+
+ /** Data class wrapping resources tied to single virtual display. */
+ private static final class VirtualDisplayWrapper {
+ private final IVirtualDisplayCallback mToken;
+ private final GenericWindowPolicyController mWindowPolicyController;
+ private final PowerManager.WakeLock mWakeLock;
+
+ VirtualDisplayWrapper(@NonNull IVirtualDisplayCallback token,
+ @NonNull GenericWindowPolicyController windowPolicyController,
+ @NonNull PowerManager.WakeLock wakeLock) {
+ mToken = Objects.requireNonNull(token);
+ mWindowPolicyController = Objects.requireNonNull(windowPolicyController);
+ mWakeLock = Objects.requireNonNull(wakeLock);
+ }
+
+ GenericWindowPolicyController getWindowPolicyController() {
+ return mWindowPolicyController;
+ }
+
+ PowerManager.WakeLock getWakeLock() {
+ return mWakeLock;
+ }
+
+ IVirtualDisplayCallback getToken() {
+ return mToken;
+ }
+ }
}
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 9bb05a6..ed5b858 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -37,7 +37,6 @@
import android.companion.virtual.sensor.VirtualSensor;
import android.content.Context;
import android.content.Intent;
-import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.IVirtualDisplayCallback;
import android.hardware.display.VirtualDisplayConfig;
import android.os.Binder;
@@ -108,26 +107,26 @@
private final ActivityInterceptorCallback mActivityInterceptorCallback =
new ActivityInterceptorCallback() {
- @Nullable
- @Override
- public ActivityInterceptResult onInterceptActivityLaunch(@NonNull
- ActivityInterceptorInfo info) {
- if (info.getCallingPackage() == null) {
- return null;
- }
- PendingTrampoline pt = mPendingTrampolines.remove(info.getCallingPackage());
- if (pt == null) {
- return null;
- }
- pt.mResultReceiver.send(VirtualDeviceManager.LAUNCH_SUCCESS, null);
- ActivityOptions options = info.getCheckedOptions();
- if (options == null) {
- options = ActivityOptions.makeBasic();
- }
- return new ActivityInterceptResult(
- info.getIntent(), options.setLaunchDisplayId(pt.mDisplayId));
- }
- };
+ @Nullable
+ @Override
+ public ActivityInterceptResult onInterceptActivityLaunch(@NonNull
+ ActivityInterceptorInfo info) {
+ if (info.getCallingPackage() == null) {
+ return null;
+ }
+ PendingTrampoline pt = mPendingTrampolines.remove(info.getCallingPackage());
+ if (pt == null) {
+ return null;
+ }
+ pt.mResultReceiver.send(VirtualDeviceManager.LAUNCH_SUCCESS, null);
+ ActivityOptions options = info.getCheckedOptions();
+ if (options == null) {
+ options = ActivityOptions.makeBasic();
+ }
+ return new ActivityInterceptResult(
+ info.getIntent(), options.setLaunchDisplayId(pt.mDisplayId));
+ }
+ };
@Override
public void onStart() {
@@ -146,8 +145,8 @@
CharSequence deviceName = mVirtualDevices.valueAt(i).getDisplayName();
mVirtualDevices.valueAt(i).showToastWhereUidIsRunning(appUid,
getContext().getString(
- com.android.internal.R.string.vdm_camera_access_denied,
- deviceName),
+ com.android.internal.R.string.vdm_camera_access_denied,
+ deviceName),
Toast.LENGTH_LONG, Looper.myLooper());
}
}
@@ -193,34 +192,46 @@
}
}
- @VisibleForTesting
void removeVirtualDevice(int deviceId) {
synchronized (mVirtualDeviceManagerLock) {
mAppsOnVirtualDevices.remove(deviceId);
mVirtualDevices.remove(deviceId);
}
+
+ Intent i = new Intent(VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED);
+ i.putExtra(VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID, deviceId);
+ i.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ getContext().sendBroadcastAsUser(i, UserHandle.ALL);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub {
private final VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback =
new VirtualDeviceImpl.PendingTrampolineCallback() {
- @Override
- public void startWaitingForPendingTrampoline(PendingTrampoline pendingTrampoline) {
- PendingTrampoline existing = mPendingTrampolines.put(
- pendingTrampoline.mPendingIntent.getCreatorPackage(),
- pendingTrampoline);
- if (existing != null) {
- existing.mResultReceiver.send(
- VirtualDeviceManager.LAUNCH_FAILURE_NO_ACTIVITY, null);
- }
- }
+ @Override
+ public void startWaitingForPendingTrampoline(
+ PendingTrampoline pendingTrampoline) {
+ PendingTrampoline existing = mPendingTrampolines.put(
+ pendingTrampoline.mPendingIntent.getCreatorPackage(),
+ pendingTrampoline);
+ if (existing != null) {
+ existing.mResultReceiver.send(
+ VirtualDeviceManager.LAUNCH_FAILURE_NO_ACTIVITY, null);
+ }
+ }
- @Override
- public void stopWaitingForPendingTrampoline(PendingTrampoline pendingTrampoline) {
- mPendingTrampolines.remove(pendingTrampoline.mPendingIntent.getCreatorPackage());
- }
- };
+ @Override
+ public void stopWaitingForPendingTrampoline(
+ PendingTrampoline pendingTrampoline) {
+ mPendingTrampolines.remove(
+ pendingTrampoline.mPendingIntent.getCreatorPackage());
+ }
+ };
@Override // Binder call
public IVirtualDevice createVirtualDevice(
@@ -251,8 +262,9 @@
final Consumer<ArraySet<Integer>> runningAppsChangedCallback =
runningUids -> notifyRunningAppsChanged(deviceId, runningUids);
VirtualDeviceImpl virtualDevice = new VirtualDeviceImpl(getContext(),
- associationInfo, token, callingUid, deviceId, cameraAccessController,
- this::onDeviceClosed, mPendingTrampolineCallback, activityListener,
+ associationInfo, VirtualDeviceManagerService.this, token, callingUid,
+ deviceId, cameraAccessController,
+ mPendingTrampolineCallback, activityListener,
soundEffectListener, runningAppsChangedCallback, params);
mVirtualDevices.put(deviceId, virtualDevice);
return virtualDevice;
@@ -281,26 +293,9 @@
"uid " + callingUid
+ " is not the owner of the supplied VirtualDevice");
}
- GenericWindowPolicyController gwpc;
- final long token = Binder.clearCallingIdentity();
- try {
- gwpc = virtualDeviceImpl.createWindowPolicyController(
- virtualDisplayConfig.getDisplayCategories());
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- DisplayManagerInternal displayManager = getLocalService(
- DisplayManagerInternal.class);
- int displayId = displayManager.createVirtualDisplay(virtualDisplayConfig, callback,
- virtualDevice, gwpc, packageName);
-
- final long tokenTwo = Binder.clearCallingIdentity();
- try {
- virtualDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, displayId);
- } finally {
- Binder.restoreCallingIdentity(tokenTwo);
- }
+ int displayId = virtualDeviceImpl.createVirtualDisplay(virtualDisplayConfig, callback,
+ packageName);
mLocalService.onVirtualDisplayCreated(displayId);
return displayId;
}
@@ -412,19 +407,6 @@
return null;
}
- private void onDeviceClosed(int deviceId) {
- removeVirtualDevice(deviceId);
- Intent i = new Intent(VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED);
- i.putExtra(VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID, deviceId);
- i.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- final long identity = Binder.clearCallingIdentity();
- try {
- getContext().sendBroadcastAsUser(i, UserHandle.ALL);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
@@ -512,9 +494,14 @@
@Override
public void onVirtualDisplayRemoved(IVirtualDevice virtualDevice, int displayId) {
final VirtualDisplayListener[] listeners;
+ VirtualDeviceImpl virtualDeviceImpl;
synchronized (mVirtualDeviceManagerLock) {
- ((VirtualDeviceImpl) virtualDevice).onVirtualDisplayRemovedLocked(displayId);
listeners = mVirtualDisplayListeners.toArray(new VirtualDisplayListener[0]);
+ virtualDeviceImpl = mVirtualDevices.get(
+ ((VirtualDeviceImpl) virtualDevice).getDeviceId());
+ }
+ if (virtualDeviceImpl != null) {
+ virtualDeviceImpl.onVirtualDisplayRemoved(displayId);
}
mHandler.post(() -> {
for (VirtualDisplayListener listener : listeners) {
@@ -599,16 +586,11 @@
@Override
public @NonNull ArraySet<Integer> getDisplayIdsForDevice(int deviceId) {
+ VirtualDeviceImpl virtualDevice;
synchronized (mVirtualDeviceManagerLock) {
- int size = mVirtualDevices.size();
- for (int i = 0; i < size; i++) {
- VirtualDeviceImpl device = mVirtualDevices.valueAt(i);
- if (device.getDeviceId() == deviceId) {
- return new ArraySet<>(device.mVirtualDisplayIds);
- }
- }
+ virtualDevice = mVirtualDevices.get(deviceId);
}
- return new ArraySet<>();
+ return virtualDevice == null ? new ArraySet<>() : virtualDevice.getDisplayIds();
}
@Override
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 669dcfc..92e322f 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -153,6 +153,8 @@
private int mLastMaxChargingCurrent;
private int mLastMaxChargingVoltage;
private int mLastChargeCounter;
+ private int mLastBatteryCycleCount;
+ private int mLastCharingState;
private int mSequence = 1;
@@ -523,7 +525,9 @@
|| mHealthInfo.maxChargingCurrentMicroamps != mLastMaxChargingCurrent
|| mHealthInfo.maxChargingVoltageMicrovolts != mLastMaxChargingVoltage
|| mHealthInfo.batteryChargeCounterUah != mLastChargeCounter
- || mInvalidCharger != mLastInvalidCharger)) {
+ || mInvalidCharger != mLastInvalidCharger
+ || mHealthInfo.batteryCycleCount != mLastBatteryCycleCount
+ || mHealthInfo.chargingState != mLastCharingState)) {
if (mPlugType != mLastPlugType) {
if (mLastPlugType == BATTERY_PLUGGED_NONE) {
@@ -705,6 +709,8 @@
mLastChargeCounter = mHealthInfo.batteryChargeCounterUah;
mLastBatteryLevelCritical = mBatteryLevelCritical;
mLastInvalidCharger = mInvalidCharger;
+ mLastBatteryCycleCount = mHealthInfo.batteryCycleCount;
+ mLastCharingState = mHealthInfo.chargingState;
}
}
@@ -736,6 +742,8 @@
BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE,
mHealthInfo.maxChargingVoltageMicrovolts);
intent.putExtra(BatteryManager.EXTRA_CHARGE_COUNTER, mHealthInfo.batteryChargeCounterUah);
+ intent.putExtra(BatteryManager.EXTRA_CYCLE_COUNT, mHealthInfo.batteryCycleCount);
+ intent.putExtra(BatteryManager.EXTRA_CHARGING_STATUS, mHealthInfo.chargingState);
if (DEBUG) {
Slog.d(TAG, "Sending ACTION_BATTERY_CHANGED. scale:" + BATTERY_SCALE
+ ", info:" + mHealthInfo.toString());
@@ -760,6 +768,8 @@
event.putInt(BatteryManager.EXTRA_TEMPERATURE, mHealthInfo.batteryTemperatureTenthsCelsius);
event.putInt(BatteryManager.EXTRA_CHARGE_COUNTER, mHealthInfo.batteryChargeCounterUah);
event.putLong(BatteryManager.EXTRA_EVENT_TIMESTAMP, now);
+ event.putInt(BatteryManager.EXTRA_CYCLE_COUNT, mHealthInfo.batteryCycleCount);
+ event.putInt(BatteryManager.EXTRA_CHARGING_STATUS, mHealthInfo.chargingState);
boolean queueWasEmpty = mBatteryLevelsEventQueue.isEmpty();
mBatteryLevelsEventQueue.add(event);
@@ -1278,11 +1288,20 @@
}
}
- // Reduced IBatteryPropertiesRegistrar that only implements getProperty for usage
- // in BatteryManager.
+ // Reduced IBatteryPropertiesRegistrar that implements getProperty for usage
+ // in BatteryManager and enforce permissions.
private final class BatteryPropertiesRegistrar extends IBatteryPropertiesRegistrar.Stub {
@Override
public int getProperty(int id, final BatteryProperty prop) throws RemoteException {
+ switch (id) {
+ case BatteryManager.BATTERY_PROPERTY_MANUFACTURING_DATE:
+ case BatteryManager.BATTERY_PROPERTY_FIRST_USAGE_DATE:
+ case BatteryManager.BATTERY_PROPERTY_CHARGING_POLICY:
+ case BatteryManager.BATTERY_PROPERTY_STATE_OF_HEALTH:
+ mContext.enforceCallingPermission(
+ android.Manifest.permission.BATTERY_STATS, null);
+ break;
+ }
return mHealthServiceWrapper.getProperty(id, prop);
}
@Override
diff --git a/services/core/java/com/android/server/IntentResolver.java b/services/core/java/com/android/server/IntentResolver.java
index 2a46d86..3f1ad3a 100644
--- a/services/core/java/com/android/server/IntentResolver.java
+++ b/services/core/java/com/android/server/IntentResolver.java
@@ -16,12 +16,18 @@
package com.android.server;
+import static android.content.IntentFilter.BLOCK_NULL_ACTION_INTENTS;
+
+import static com.android.internal.util.FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__NULL_ACTION_MATCH;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
+import android.os.Binder;
+import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.FastImmutableArraySet;
@@ -34,6 +40,7 @@
import android.util.proto.ProtoOutputStream;
import com.android.internal.util.FastPrintWriter;
+import com.android.server.am.ActivityManagerUtils;
import com.android.server.pm.Computer;
import com.android.server.pm.snapshot.PackageDataSnapshot;
@@ -81,7 +88,7 @@
* Returns whether an intent matches the IntentFilter with a pre-resolved type.
*/
public static boolean intentMatchesFilter(
- IntentFilter filter, Intent intent, String resolvedType) {
+ IntentFilter filter, Intent intent, String resolvedType, boolean blockNullAction) {
final boolean debug = localLOGV
|| ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
@@ -95,7 +102,8 @@
}
final int match = filter.match(intent.getAction(), resolvedType, intent.getScheme(),
- intent.getData(), intent.getCategories(), TAG);
+ intent.getData(), intent.getCategories(), TAG, /* supportWildcards */ false,
+ blockNullAction, null, null);
if (match >= 0) {
if (debug) {
@@ -350,14 +358,32 @@
return Collections.unmodifiableSet(mFilters);
}
+ private boolean blockNullAction(Computer computer, Intent intent,
+ String resolvedType, int callingUid, boolean debug) {
+ if (intent.getAction() == null) {
+ final boolean blockNullAction = UserHandle.isCore(callingUid)
+ || computer.isChangeEnabled(BLOCK_NULL_ACTION_INTENTS, callingUid);
+ ActivityManagerUtils.logUnsafeIntentEvent(
+ UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__NULL_ACTION_MATCH,
+ callingUid, intent, resolvedType, blockNullAction);
+ if (blockNullAction) {
+ if (debug) Slog.v(TAG, "Skip matching filters: action is null");
+ return true;
+ }
+ }
+ return false;
+ }
+
public List<R> queryIntentFromList(@NonNull Computer computer, Intent intent,
- String resolvedType, boolean defaultOnly, ArrayList<F[]> listCut, int userId,
- long customFlags) {
+ String resolvedType, boolean defaultOnly, ArrayList<F[]> listCut,
+ int callingUid, @UserIdInt int userId, long customFlags) {
ArrayList<R> resultList = new ArrayList<R>();
final boolean debug = localLOGV ||
((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
+ if (blockNullAction(computer, intent, resolvedType, callingUid, debug)) return resultList;
+
FastImmutableArraySet<String> categories = getFastIntentCategories(intent);
final String scheme = intent.getScheme();
int N = listCut.size();
@@ -365,18 +391,26 @@
buildResolveList(computer, intent, categories, debug, defaultOnly, resolvedType, scheme,
listCut.get(i), resultList, userId, customFlags);
}
- filterResults(resultList);
+ filterResults(computer, intent, resultList);
sortResults(resultList);
return resultList;
}
- public List<R> queryIntent(@NonNull PackageDataSnapshot snapshot, Intent intent,
+ public final List<R> queryIntent(@NonNull PackageDataSnapshot snapshot, Intent intent,
String resolvedType, boolean defaultOnly, @UserIdInt int userId) {
- return queryIntent(snapshot, intent, resolvedType, defaultOnly, userId, 0);
+ return queryIntent(snapshot, intent, resolvedType, defaultOnly,
+ Binder.getCallingUid(), userId, 0);
+ }
+
+ public List<R> queryIntent(@NonNull PackageDataSnapshot snapshot, Intent intent,
+ String resolvedType, boolean defaultOnly, int callingUid, @UserIdInt int userId) {
+ return queryIntent(snapshot, intent, resolvedType, defaultOnly, callingUid, userId, 0);
}
protected final List<R> queryIntent(@NonNull PackageDataSnapshot snapshot, Intent intent,
- String resolvedType, boolean defaultOnly, @UserIdInt int userId, long customFlags) {
+ String resolvedType, boolean defaultOnly, int callingUid, @UserIdInt int userId,
+ long customFlags) {
+ final Computer computer = (Computer) snapshot;
String scheme = intent.getScheme();
ArrayList<R> finalList = new ArrayList<R>();
@@ -388,6 +422,8 @@
TAG, "Resolving type=" + resolvedType + " scheme=" + scheme
+ " defaultOnly=" + defaultOnly + " userId=" + userId + " of " + intent);
+ if (blockNullAction(computer, intent, resolvedType, callingUid, debug)) return finalList;
+
F[] firstTypeCut = null;
F[] secondTypeCut = null;
F[] thirdTypeCut = null;
@@ -448,7 +484,6 @@
}
FastImmutableArraySet<String> categories = getFastIntentCategories(intent);
- Computer computer = (Computer) snapshot;
if (firstTypeCut != null) {
buildResolveList(computer, intent, categories, debug, defaultOnly, resolvedType,
scheme, firstTypeCut, finalList, userId, customFlags);
@@ -465,7 +500,7 @@
buildResolveList(computer, intent, categories, debug, defaultOnly, resolvedType,
scheme, schemeCut, finalList, userId, customFlags);
}
- filterResults(finalList);
+ filterResults(computer, intent, finalList);
sortResults(finalList);
if (debug) {
@@ -534,7 +569,8 @@
/**
* Apply filtering to the results. This happens before the results are sorted.
*/
- protected void filterResults(List<R> results) {
+ protected void filterResults(@NonNull Computer computer, @NonNull Intent intent,
+ List<R> results) {
}
protected void dumpFilter(PrintWriter out, String prefix, F filter) {
@@ -766,7 +802,11 @@
continue;
}
- match = intentFilter.match(action, resolvedType, scheme, data, categories, TAG);
+ match = intentFilter.match(action, resolvedType, scheme, data, categories, TAG,
+ false /*supportWildcards*/,
+ false /*blockNullAction: already handled*/,
+ null /*ignoreActions*/,
+ null /*extras*/);
if (match >= 0) {
if (debug) Slog.v(TAG, " Filter matched! match=0x" +
Integer.toHexString(match) + " hasDefault="
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 0252492..e0d1c1e 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -2753,6 +2753,8 @@
enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
try {
+ int avgWriteAmount = 0;
+ int targetDirtyRatio = mTargetDirtyRatio;
int latestWrite = mVold.getWriteAmount();
if (latestWrite == -1) {
Slog.w(TAG, "Failed to get storage write record");
@@ -2765,11 +2767,12 @@
// (first boot after OTA), We skip the smart idle maintenance
if (!needsCheckpoint() || !supportsBlockCheckpoint()) {
if (!refreshLifetimeConstraint() || !checkChargeStatus()) {
- return;
+ Slog.i(TAG, "Turn off gc_urgent based on checking lifetime and charge status");
+ targetDirtyRatio = 100;
+ } else {
+ avgWriteAmount = getAverageWriteAmount();
}
- int avgWriteAmount = getAverageWriteAmount();
-
Slog.i(TAG, "Set smart idle maintenance: " + "latest write amount: " +
latestWrite + ", average write amount: " + avgWriteAmount +
", min segment threshold: " + mMinSegmentsThreshold +
@@ -2777,10 +2780,10 @@
", segment reclaim weight: " + mSegmentReclaimWeight +
", period(min): " + sSmartIdleMaintPeriod +
", min gc sleep time(ms): " + mMinGCSleepTime +
- ", target dirty ratio: " + mTargetDirtyRatio);
+ ", target dirty ratio: " + targetDirtyRatio);
mVold.setGCUrgentPace(avgWriteAmount, mMinSegmentsThreshold, mDirtyReclaimRate,
mSegmentReclaimWeight, sSmartIdleMaintPeriod,
- mMinGCSleepTime, mTargetDirtyRatio);
+ mMinGCSleepTime, targetDirtyRatio);
} else {
Slog.i(TAG, "Skipping smart idle maintenance - block based checkpoint in progress");
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 3a93cb3..70304c5 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -522,6 +522,7 @@
final ArrayList<ServiceRecord> mStartingBackground = new ArrayList<>();
final ArrayMap<String, ActiveForegroundApp> mActiveForegroundApps = new ArrayMap<>();
+ final ArrayList<String> mPendingRemoveForegroundApps = new ArrayList<>();
boolean mActiveForegroundAppsChanged;
@@ -746,10 +747,13 @@
ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
int callingPid, int callingUid, boolean fgRequired, String callingPackage,
- @Nullable String callingFeatureId, final int userId)
+ @Nullable String callingFeatureId, final int userId, boolean isSdkSandboxService,
+ int sdkSandboxClientAppUid, String sdkSandboxClientAppPackage, String instanceName)
throws TransactionTooLargeException {
return startServiceLocked(caller, service, resolvedType, callingPid, callingUid, fgRequired,
- callingPackage, callingFeatureId, userId, BackgroundStartPrivileges.NONE);
+ callingPackage, callingFeatureId, userId, BackgroundStartPrivileges.NONE,
+ isSdkSandboxService, sdkSandboxClientAppUid, sdkSandboxClientAppPackage,
+ instanceName);
}
ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
@@ -757,6 +761,17 @@
String callingPackage, @Nullable String callingFeatureId, final int userId,
BackgroundStartPrivileges backgroundStartPrivileges)
throws TransactionTooLargeException {
+ return startServiceLocked(caller, service, resolvedType, callingPid, callingUid, fgRequired,
+ callingPackage, callingFeatureId, userId, backgroundStartPrivileges,
+ false /* isSdkSandboxService */, INVALID_UID, null, null);
+ }
+
+ ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
+ int callingPid, int callingUid, boolean fgRequired,
+ String callingPackage, @Nullable String callingFeatureId, final int userId,
+ BackgroundStartPrivileges backgroundStartPrivileges, boolean isSdkSandboxService,
+ int sdkSandboxClientAppUid, String sdkSandboxClientAppPackage, String instanceName)
+ throws TransactionTooLargeException {
if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "startService: " + service
+ " type=" + resolvedType + " args=" + service.getExtras());
@@ -774,9 +789,9 @@
callerFg = true;
}
- ServiceLookupResult res =
- retrieveServiceLocked(service, null, resolvedType, callingPackage,
- callingPid, callingUid, userId, true, callerFg, false, false, false);
+ ServiceLookupResult res = retrieveServiceLocked(service, instanceName, isSdkSandboxService,
+ sdkSandboxClientAppUid, sdkSandboxClientAppPackage, resolvedType, callingPackage,
+ callingPid, callingUid, userId, true, callerFg, false, false, null, false);
if (res == null) {
return null;
}
@@ -800,16 +815,30 @@
return null;
}
+ // For the SDK sandbox, we start the service on behalf of the client app.
+ final int appUid = isSdkSandboxService ? sdkSandboxClientAppUid : r.appInfo.uid;
+ final String appPackageName =
+ isSdkSandboxService ? sdkSandboxClientAppPackage : r.packageName;
+ int appTargetSdkVersion = r.appInfo.targetSdkVersion;
+ if (isSdkSandboxService) {
+ try {
+ appTargetSdkVersion = AppGlobals.getPackageManager().getApplicationInfo(
+ appPackageName, ActivityManagerService.STOCK_PM_FLAGS,
+ userId).targetSdkVersion;
+ } catch (RemoteException ignored) {
+ }
+ }
+
// If we're starting indirectly (e.g. from PendingIntent), figure out whether
// we're launching into an app in a background state. This keys off of the same
// idleness state tracking as e.g. O+ background service start policy.
- final boolean bgLaunch = !mAm.isUidActiveLOSP(r.appInfo.uid);
+ final boolean bgLaunch = !mAm.isUidActiveLOSP(appUid);
// If the app has strict background restrictions, we treat any bg service
// start analogously to the legacy-app forced-restrictions case, regardless
// of its target SDK version.
boolean forcedStandby = false;
- if (bgLaunch && appRestrictedAnyInBackground(r.appInfo.uid, r.packageName)) {
+ if (bgLaunch && appRestrictedAnyInBackground(appUid, appPackageName)) {
if (DEBUG_FOREGROUND_SERVICE) {
Slog.d(TAG, "Forcing bg-only service start only for " + r.shortInstanceName
+ " : bgLaunch=" + bgLaunch + " callerFg=" + callerFg);
@@ -839,7 +868,7 @@
boolean forceSilentAbort = false;
if (fgRequired) {
final int mode = mAm.getAppOpsManager().checkOpNoThrow(
- AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName);
+ AppOpsManager.OP_START_FOREGROUND, appUid, appPackageName);
switch (mode) {
case AppOpsManager.MODE_ALLOWED:
case AppOpsManager.MODE_DEFAULT:
@@ -861,12 +890,12 @@
}
// If this isn't a direct-to-foreground start, check our ability to kick off an
- // arbitrary service
+ // arbitrary service.
if (forcedStandby || (!r.startRequested && !fgRequired)) {
// Before going further -- if this app is not allowed to start services in the
// background, then at this point we aren't going to let it period.
- final int allowed = mAm.getAppStartModeLOSP(r.appInfo.uid, r.packageName,
- r.appInfo.targetSdkVersion, callingPid, false, false, forcedStandby);
+ final int allowed = mAm.getAppStartModeLOSP(appUid, appPackageName, appTargetSdkVersion,
+ callingPid, false, false, forcedStandby);
if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
Slog.w(TAG, "Background start not allowed: service "
+ service + " to " + r.shortInstanceName
@@ -890,7 +919,7 @@
}
// This app knows it is in the new model where this operation is not
// allowed, so tell it what has happened.
- UidRecord uidRec = mAm.mProcessList.getUidRecordLOSP(r.appInfo.uid);
+ UidRecord uidRec = mAm.mProcessList.getUidRecordLOSP(appUid);
return new ComponentName("?", "app is in background uid " + uidRec);
}
}
@@ -899,10 +928,10 @@
// an ordinary startService() or a startForegroundService(). Now, only require that
// the app follow through on the startForegroundService() -> startForeground()
// contract if it actually targets O+.
- if (r.appInfo.targetSdkVersion < Build.VERSION_CODES.O && fgRequired) {
+ if (appTargetSdkVersion < Build.VERSION_CODES.O && fgRequired) {
if (DEBUG_BACKGROUND_CHECK || DEBUG_FOREGROUND_SERVICE) {
Slog.i(TAG, "startForegroundService() but host targets "
- + r.appInfo.targetSdkVersion + " - not requiring startForeground()");
+ + appTargetSdkVersion + " - not requiring startForeground()");
}
fgRequired = false;
}
@@ -1376,7 +1405,8 @@
}
int stopServiceLocked(IApplicationThread caller, Intent service,
- String resolvedType, int userId) {
+ String resolvedType, int userId, boolean isSdkSandboxService,
+ int sdkSandboxClientAppUid, String sdkSandboxClientAppPackage, String instanceName) {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "stopService: " + service
+ " type=" + resolvedType);
@@ -1389,9 +1419,10 @@
}
// If this service is active, make sure it is stopped.
- ServiceLookupResult r = retrieveServiceLocked(service, null, resolvedType, null,
+ ServiceLookupResult r = retrieveServiceLocked(service, instanceName, isSdkSandboxService,
+ sdkSandboxClientAppUid, sdkSandboxClientAppPackage, resolvedType, null,
Binder.getCallingPid(), Binder.getCallingUid(), userId, false, false, false, false,
- false);
+ null, false);
if (r != null) {
if (r.record != null) {
final long origId = Binder.clearCallingIdentity();
@@ -1657,13 +1688,14 @@
if (smap != null) {
if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "Updating foreground apps for user "
+ smap.mUserId);
+ smap.mPendingRemoveForegroundApps.clear();
for (int i = smap.mActiveForegroundApps.size()-1; i >= 0; i--) {
ActiveForegroundApp aa = smap.mActiveForegroundApps.valueAt(i);
if (aa.mEndTime != 0) {
boolean canRemove = foregroundAppShownEnoughLocked(aa, now);
if (canRemove) {
// This was up for longer than the timeout, so just remove immediately.
- smap.mActiveForegroundApps.removeAt(i);
+ smap.mPendingRemoveForegroundApps.add(smap.mActiveForegroundApps.keyAt(i));
smap.mActiveForegroundAppsChanged = true;
continue;
}
@@ -1688,6 +1720,9 @@
}
}
}
+ for(int i = smap.mPendingRemoveForegroundApps.size() - 1; i >= 0; i--) {
+ smap.mActiveForegroundApps.remove(smap.mPendingRemoveForegroundApps.get(i));
+ }
smap.removeMessages(ServiceMap.MSG_UPDATE_FOREGROUND_APPS);
if (nextUpdateTime < Long.MAX_VALUE) {
if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "Next update time in: "
@@ -2029,6 +2064,9 @@
foregroundServiceType == FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
final boolean isOldTypeShortFgsAndTimedOut = r.shouldTriggerShortFgsTimeout();
+ // If true, we skip the BFSL check.
+ boolean bypassBfslCheck = false;
+
if (r.isForeground && (isOldTypeShortFgs || isNewTypeShortFgs)) {
if (DEBUG_SHORT_SERVICE) {
Slog.i(TAG_SERVICE, String.format(
@@ -2059,9 +2097,6 @@
if (isNewTypeShortFgs) {
// Only in this case, we extend the SHORT_SERVICE time out.
extendShortServiceTimeout = true;
- if (DEBUG_SHORT_SERVICE) {
- Slog.i(TAG_SERVICE, "Extending SHORT_SERVICE time out: " + r);
- }
} else {
// FGS type is changing from SHORT_SERVICE to another type when
// an app is allowed to start FGS, so this will succeed.
@@ -2069,8 +2104,20 @@
// maybeUpdateShortFgsTrackingLocked().
}
} else {
- // We catch this case later, in the
- // "if (r.mAllowStartForeground == REASON_DENIED...)" block below.
+ if (isNewTypeShortFgs) {
+ // startForeground(SHORT_SERVICE) is called on an already running
+ // SHORT_SERVICE FGS, when BFSL is not allowed.
+ // In this case, the call should succeed
+ // (== ForegroundServiceStartNotAllowedException shouldn't be
+ // thrown), but the short service timeout shouldn't extend
+ // (== extendShortServiceTimeout should be false).
+ // We still do everything else -- e.g. we still need to update
+ // the notification.
+ bypassBfslCheck = true;
+ } else {
+ // We catch this case later, in the
+ // "if (r.mAllowStartForeground == REASON_DENIED...)" block below.
+ }
}
} else if (r.mStartForegroundCount == 0) {
@@ -2125,23 +2172,25 @@
+ "location/camera/microphone access: service "
+ r.shortInstanceName);
}
- logFgsBackgroundStart(r);
- if (r.mAllowStartForeground == REASON_DENIED
- && isBgFgsRestrictionEnabledForService) {
- final String msg = "Service.startForeground() not allowed due to "
- + "mAllowStartForeground false: service "
- + r.shortInstanceName
- + (isOldTypeShortFgs ? " (Called on SHORT_SERVICE)" : "");
- Slog.w(TAG, msg);
- showFgsBgRestrictedNotificationLocked(r);
- updateServiceForegroundLocked(psr, true);
- ignoreForeground = true;
- logFGSStateChangeLocked(r,
- FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED,
- 0, FGS_STOP_REASON_UNKNOWN, FGS_TYPE_POLICY_CHECK_UNKNOWN);
- if (CompatChanges.isChangeEnabled(FGS_START_EXCEPTION_CHANGE_ID,
- r.appInfo.uid)) {
- throw new ForegroundServiceStartNotAllowedException(msg);
+ if (!bypassBfslCheck) {
+ logFgsBackgroundStart(r);
+ if (r.mAllowStartForeground == REASON_DENIED
+ && isBgFgsRestrictionEnabledForService) {
+ final String msg = "Service.startForeground() not allowed due to "
+ + "mAllowStartForeground false: service "
+ + r.shortInstanceName
+ + (isOldTypeShortFgs ? " (Called on SHORT_SERVICE)" : "");
+ Slog.w(TAG, msg);
+ showFgsBgRestrictedNotificationLocked(r);
+ updateServiceForegroundLocked(psr, true);
+ ignoreForeground = true;
+ logFGSStateChangeLocked(r,
+ FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED,
+ 0, FGS_STOP_REASON_UNKNOWN, FGS_TYPE_POLICY_CHECK_UNKNOWN);
+ if (CompatChanges.isChangeEnabled(FGS_START_EXCEPTION_CHANGE_ID,
+ r.appInfo.uid)) {
+ throw new ForegroundServiceStartNotAllowedException(msg);
+ }
}
}
@@ -3084,11 +3133,17 @@
unscheduleShortFgsTimeoutLocked(sr);
return;
}
- if (DEBUG_SHORT_SERVICE) {
- Slog.i(TAG_SERVICE, "Short FGS started: " + sr);
- }
- if (extendTimeout || !sr.hasShortFgsInfo()) {
+ final boolean isAlreadyShortFgs = sr.hasShortFgsInfo();
+
+ if (extendTimeout || !isAlreadyShortFgs) {
+ if (DEBUG_SHORT_SERVICE) {
+ if (isAlreadyShortFgs) {
+ Slog.i(TAG_SERVICE, "Extending SHORT_SERVICE time out: " + sr);
+ } else {
+ Slog.i(TAG_SERVICE, "Short FGS started: " + sr);
+ }
+ }
sr.setShortFgsInfo(SystemClock.uptimeMillis());
// We'll restart the timeout.
@@ -3098,6 +3153,10 @@
ActivityManagerService.SERVICE_SHORT_FGS_TIMEOUT_MSG, sr);
mAm.mHandler.sendMessageAtTime(msg, sr.getShortFgsInfo().getTimeoutTime());
} else {
+ if (DEBUG_SHORT_SERVICE) {
+ Slog.w(TAG_SERVICE, "NOT extending SHORT_SERVICE time out: " + sr);
+ }
+
// We only (potentially) update the start command, start count, but not the timeout
// time.
// In this case, we keep the existing timeout running.
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 44f475f..9e95e5f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -129,6 +129,7 @@
static final String KEY_KILL_BG_RESTRICTED_CACHED_IDLE = "kill_bg_restricted_cached_idle";
static final String KEY_KILL_BG_RESTRICTED_CACHED_IDLE_SETTLE_TIME =
"kill_bg_restricted_cached_idle_settle_time";
+ static final String KEY_MAX_PREVIOUS_TIME = "max_previous_time";
/**
* Note this key is on {@link DeviceConfig#NAMESPACE_ACTIVITY_MANAGER_COMPONENT_ALIAS}.
* @see #mEnableComponentAlias
@@ -145,6 +146,9 @@
*/
static final String KEY_NETWORK_ACCESS_TIMEOUT_MS = "network_access_timeout_ms";
+ static final String KEY_USE_TIERED_CACHED_ADJ = "use_tiered_cached_adj";
+ static final String KEY_TIERED_CACHED_ADJ_DECAY_TIME = "tiered_cached_adj_decay_time";
+
private static final int DEFAULT_MAX_CACHED_PROCESSES = 32;
private static final boolean DEFAULT_PRIORITIZE_ALARM_BROADCASTS = true;
private static final long DEFAULT_FGSERVICE_MIN_SHOWN_TIME = 2*1000;
@@ -192,6 +196,7 @@
private static final float DEFAULT_FGS_START_DENIED_LOG_SAMPLE_RATE = 1; // 100%
private static final long DEFAULT_PROCESS_KILL_TIMEOUT_MS = 10 * 1000;
private static final long DEFAULT_NETWORK_ACCESS_TIMEOUT_MS = 200; // 0.2 sec
+ private static final long DEFAULT_MAX_PREVIOUS_TIME = 60 * 1000; // 60s
static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60 * 1000;
static final long DEFAULT_KILL_BG_RESTRICTED_CACHED_IDLE_SETTLE_TIME_MS = 60 * 1000;
@@ -199,6 +204,9 @@
static final int DEFAULT_MAX_SERVICE_CONNECTIONS_PER_PROCESS = 3000;
+ private static final boolean DEFAULT_USE_TIERED_CACHED_ADJ = false;
+ private static final long DEFAULT_TIERED_CACHED_ADJ_DECAY_TIME = 60 * 1000;
+
/**
* Same as {@link TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED}
*/
@@ -534,6 +542,9 @@
public long TOP_TO_ALMOST_PERCEPTIBLE_GRACE_DURATION =
DEFAULT_TOP_TO_ALMOST_PERCEPTIBLE_GRACE_DURATION;
+ // How long a process can remain at previous oom_adj before dropping to cached
+ public static long MAX_PREVIOUS_TIME = DEFAULT_MAX_PREVIOUS_TIME;
+
/**
* The minimum time we allow between crashes, for us to consider this
* application to be bad and stop its services and reject broadcasts.
@@ -1006,6 +1017,12 @@
public volatile long mShortFgsAnrExtraWaitDuration =
DEFAULT_SHORT_FGS_ANR_EXTRA_WAIT_DURATION;
+ /** @see #KEY_USE_TIERED_CACHED_ADJ */
+ public boolean USE_TIERED_CACHED_ADJ = DEFAULT_USE_TIERED_CACHED_ADJ;
+
+ /** @see #KEY_TIERED_CACHED_ADJ_DECAY_TIME */
+ public long TIERED_CACHED_ADJ_DECAY_TIME = DEFAULT_TIERED_CACHED_ADJ_DECAY_TIME;
+
private final OnPropertiesChangedListener mOnDeviceConfigChangedListener =
new OnPropertiesChangedListener() {
@Override
@@ -1171,6 +1188,13 @@
case KEY_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION:
updateEnableWaitForFinishAttachApplication();
break;
+ case KEY_MAX_PREVIOUS_TIME:
+ updateMaxPreviousTime();
+ break;
+ case KEY_USE_TIERED_CACHED_ADJ:
+ case KEY_TIERED_CACHED_ADJ_DECAY_TIME:
+ updateUseTieredCachedAdj();
+ break;
default:
break;
}
@@ -1825,6 +1849,7 @@
DEFAULT_LOW_SWAP_THRESHOLD_PERCENT);
}
+
private void updateTopToFgsGraceDuration() {
TOP_TO_FGS_GRACE_DURATION = DeviceConfig.getLong(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -1832,6 +1857,13 @@
DEFAULT_TOP_TO_FGS_GRACE_DURATION);
}
+ private void updateMaxPreviousTime() {
+ MAX_PREVIOUS_TIME = DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_MAX_PREVIOUS_TIME,
+ DEFAULT_MAX_PREVIOUS_TIME);
+ }
+
private void updateMinAssocLogDuration() {
MIN_ASSOC_LOG_DURATION = DeviceConfig.getLong(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_MIN_ASSOC_LOG_DURATION,
@@ -1908,6 +1940,17 @@
DEFAULT_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION);
}
+ private void updateUseTieredCachedAdj() {
+ USE_TIERED_CACHED_ADJ = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_USE_TIERED_CACHED_ADJ,
+ DEFAULT_USE_TIERED_CACHED_ADJ);
+ TIERED_CACHED_ADJ_DECAY_TIME = DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_TIERED_CACHED_ADJ_DECAY_TIME,
+ DEFAULT_TIERED_CACHED_ADJ_DECAY_TIME);
+ }
+
@NeverCompile // Avoid size overhead of debugging code.
void dump(PrintWriter pw) {
pw.println("ACTIVITY MANAGER SETTINGS (dumpsys activity settings) "
@@ -2092,6 +2135,11 @@
pw.print(" "); pw.print(KEY_SHORT_FGS_ANR_EXTRA_WAIT_DURATION);
pw.print("="); pw.println(mShortFgsAnrExtraWaitDuration);
+ pw.print(" "); pw.print(KEY_USE_TIERED_CACHED_ADJ);
+ pw.print("="); pw.println(USE_TIERED_CACHED_ADJ);
+ pw.print(" "); pw.print(KEY_TIERED_CACHED_ADJ_DECAY_TIME);
+ pw.print("="); pw.println(TIERED_CACHED_ADJ_DECAY_TIME);
+
pw.println();
if (mOverrideMaxCachedProcesses >= 0) {
pw.print(" mOverrideMaxCachedProcesses="); pw.println(mOverrideMaxCachedProcesses);
diff --git a/services/core/java/com/android/server/am/ActivityManagerLocal.java b/services/core/java/com/android/server/am/ActivityManagerLocal.java
index 3f06990..928af3f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerLocal.java
+++ b/services/core/java/com/android/server/am/ActivityManagerLocal.java
@@ -17,8 +17,10 @@
package com.android.server.am;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Context.BindServiceFlags;
import android.content.Context.BindServiceFlagsBits;
@@ -68,9 +70,53 @@
void tempAllowWhileInUsePermissionInFgs(int uid, long durationMs);
/**
- * Binds to a sdk sandbox service, creating it if needed. You can through the arguments
- * here have the system bring up multiple concurrent processes hosting their own instance of
- * that service. The {@code processName} you provide here identifies the different instances.
+ * Requests that an SDK sandbox service be started. If this service is not already running,
+ * it will be instantiated and started (creating a process for it if needed). You can through
+ * the arguments here have the system bring up multiple concurrent processes hosting their own
+ * instance of that service. Each instance is identified by the {@code processName} provided
+ * here.
+ *
+ * @param service Identifies the sdk sandbox process service to connect to. The Intent must
+ * specify an explicit component name. This value cannot be null.
+ * @param clientAppUid Uid of the app for which the sdk sandbox process needs to be spawned.
+ * @param clientAppPackage Package of the app for which the sdk sandbox process needs to
+ * be spawned. This package must belong to the clientAppUid.
+ * @param processName Unique identifier for the service instance. Each unique name here will
+ * result in a different service instance being created. Identifiers must only contain
+ * ASCII letters, digits, underscores, and periods.
+ *
+ * @throws RemoteException If the service could not be started.
+ * @return If the service is being started or is already running, the {@link ComponentName} of
+ * the actual service that was started is returned; else if the service does not exist null is
+ * returned.
+ */
+ @Nullable
+ @SuppressLint("RethrowRemoteException")
+ ComponentName startSdkSandboxService(@NonNull Intent service, int clientAppUid,
+ @NonNull String clientAppPackage, @NonNull String processName)
+ throws RemoteException;
+
+ // TODO(b/269592470): What if the sandbox is stopped while there is an active binding to it?
+ /**
+ * Requests that an SDK sandbox service with a given {@code processName} be stopped.
+ *
+ * @param service Identifies the sdk sandbox process service to connect to. The Intent must
+ * specify an explicit component name. This value cannot be null.
+ * @param clientAppUid Uid of the app for which the sdk sandbox process needs to be stopped.
+ * @param clientAppPackage Package of the app for which the sdk sandbox process needs to
+ * be stopped. This package must belong to the clientAppUid.
+ * @param processName Unique identifier for the service instance. Each unique name here will
+ * result in a different service instance being created. Identifiers must only contain
+ * ASCII letters, digits, underscores, and periods.
+ *
+ * @return If there is a service matching the given Intent that is already running, then it is
+ * stopped and true is returned; else false is returned.
+ */
+ boolean stopSdkSandboxService(@NonNull Intent service, int clientAppUid,
+ @NonNull String clientAppPackage, @NonNull String processName);
+
+ /**
+ * Binds to an SDK sandbox service for a given client application.
*
* @param service Identifies the sdk sandbox process service to connect to. The Intent must
* specify an explicit component name. This value cannot be null.
@@ -90,7 +136,7 @@
* service that your client has permission to bind to; {@code false}
* if the system couldn't find the service or if your client doesn't
* have permission to bind to it.
- * @throws RemoteException If the service could not be brought up.
+ * @throws RemoteException If the service could not be bound to.
* @see Context#bindService(Intent, ServiceConnection, int)
*/
@SuppressLint("RethrowRemoteException")
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 5ad0409..74f7990 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -215,6 +215,7 @@
import android.app.PendingIntentStats;
import android.app.ProcessMemoryState;
import android.app.ProfilerInfo;
+import android.app.ServiceStartNotAllowedException;
import android.app.SyncNotedAppOp;
import android.app.WaitResult;
import android.app.assist.ActivityId;
@@ -1127,6 +1128,19 @@
}
@Override
+ protected void filterResults(@NonNull Computer computer,
+ @NonNull Intent intent, List<BroadcastFilter> results) {
+ if (intent.getAction() != null) return;
+ // When the resolved component is targeting U+, block null action intents
+ for (int i = results.size() - 1; i >= 0; --i) {
+ if (computer.isChangeEnabled(
+ IntentFilter.BLOCK_NULL_ACTION_INTENTS, results.get(i).owningUid)) {
+ results.remove(i);
+ }
+ }
+ }
+
+ @Override
protected IntentFilter getIntentFilter(@NonNull BroadcastFilter input) {
return input;
}
@@ -13156,6 +13170,15 @@
String resolvedType, boolean requireForeground, String callingPackage,
String callingFeatureId, int userId)
throws TransactionTooLargeException {
+ return startService(caller, service, resolvedType, requireForeground, callingPackage,
+ callingFeatureId, userId, false /* isSdkSandboxService */, INVALID_UID, null, null);
+ }
+
+ private ComponentName startService(IApplicationThread caller, Intent service,
+ String resolvedType, boolean requireForeground, String callingPackage,
+ String callingFeatureId, int userId, boolean isSdkSandboxService,
+ int sdkSandboxClientAppUid, String sdkSandboxClientAppPackage, String instanceName)
+ throws TransactionTooLargeException {
enforceNotIsolatedCaller("startService");
enforceAllowedToStartOrBindServiceIfSdkSandbox(service);
// Refuse possible leaked file descriptors
@@ -13167,6 +13190,11 @@
throw new IllegalArgumentException("callingPackage cannot be null");
}
+ if (isSdkSandboxService && instanceName == null) {
+ throw new IllegalArgumentException("No instance name provided for SDK sandbox process");
+ }
+ validateServiceInstanceName(instanceName);
+
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE,
"*** startService: " + service + " type=" + resolvedType + " fg=" + requireForeground);
final int callingPid = Binder.getCallingPid();
@@ -13182,7 +13210,9 @@
synchronized (this) {
res = mServices.startServiceLocked(caller, service,
resolvedType, callingPid, callingUid,
- requireForeground, callingPackage, callingFeatureId, userId);
+ requireForeground, callingPackage, callingFeatureId, userId,
+ isSdkSandboxService, sdkSandboxClientAppUid, sdkSandboxClientAppPackage,
+ instanceName);
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
@@ -13191,9 +13221,26 @@
return res;
}
+ private void validateServiceInstanceName(String instanceName) {
+ // Ensure that instanceName, which is caller provided, does not contain
+ // unusual characters.
+ if (instanceName != null) {
+ if (!instanceName.matches("[a-zA-Z0-9_.]+")) {
+ throw new IllegalArgumentException("Illegal instanceName");
+ }
+ }
+ }
+
@Override
public int stopService(IApplicationThread caller, Intent service,
String resolvedType, int userId) {
+ return stopService(caller, service, resolvedType, userId, false /* isSdkSandboxService */,
+ INVALID_UID, null, null);
+ }
+
+ private int stopService(IApplicationThread caller, Intent service, String resolvedType,
+ int userId, boolean isSdkSandboxService,
+ int sdkSandboxClientAppUid, String sdkSandboxClientAppPackage, String instanceName) {
enforceNotIsolatedCaller("stopService");
// Refuse possible leaked file descriptors
if (service != null && service.hasFileDescriptors() == true) {
@@ -13205,7 +13252,9 @@
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "stopService: " + service);
}
synchronized (this) {
- return mServices.stopServiceLocked(caller, service, resolvedType, userId);
+ return mServices.stopServiceLocked(caller, service, resolvedType, userId,
+ isSdkSandboxService, sdkSandboxClientAppUid, sdkSandboxClientAppPackage,
+ instanceName);
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
@@ -13364,17 +13413,7 @@
throw new IllegalArgumentException("No instance name provided for isolated process");
}
- // Ensure that instanceName, which is caller provided, does not contain
- // unusual characters.
- if (instanceName != null) {
- for (int i = 0; i < instanceName.length(); ++i) {
- char c = instanceName.charAt(i);
- if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
- || (c >= '0' && c <= '9') || c == '_' || c == '.')) {
- throw new IllegalArgumentException("Illegal instanceName");
- }
- }
- }
+ validateServiceInstanceName(instanceName);
try {
if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
@@ -13939,11 +13978,19 @@
(intent.getFlags() & Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS) == 0) {
continue;
}
+
+ final boolean blockNullAction = mPlatformCompat.isChangeEnabledInternal(
+ IntentFilter.BLOCK_NULL_ACTION_INTENTS, callerApp.info);
// If intent has scheme "content", it will need to access
// provider that needs to lock mProviderMap in ActivityThread
// and also it may need to wait application response, so we
// cannot lock ActivityManagerService here.
- if (filter.match(resolver, intent, true, TAG) >= 0) {
+ if (filter.match(intent.getAction(), intent.resolveType(resolver),
+ intent.getScheme(), intent.getData(), intent.getCategories(), TAG,
+ false /* supportWildcards */,
+ blockNullAction,
+ null /* ignoreActions */,
+ intent.getExtras()) >= 0) {
if (allSticky == null) {
allSticky = new ArrayList<Intent>();
}
@@ -14962,7 +15009,7 @@
}
List<BroadcastFilter> registeredReceiversForUser =
mReceiverResolver.queryIntent(snapshot, intent,
- resolvedType, false /*defaultOnly*/, users[i]);
+ resolvedType, false /*defaultOnly*/, callingUid, users[i]);
if (registeredReceivers == null) {
registeredReceivers = registeredReceiversForUser;
} else if (registeredReceiversForUser != null) {
@@ -14971,7 +15018,7 @@
}
} else {
registeredReceivers = mReceiverResolver.queryIntent(snapshot, intent,
- resolvedType, false /*defaultOnly*/, userId);
+ resolvedType, false /*defaultOnly*/, callingUid, userId);
}
}
BroadcastQueue.traceEnd(cookie);
@@ -17271,6 +17318,53 @@
}
@Override
+ public ComponentName startSdkSandboxService(Intent service, int clientAppUid,
+ String clientAppPackage, String processName) throws RemoteException {
+ validateSdkSandboxParams(service, clientAppUid, clientAppPackage, processName);
+ // TODO(b/269598719): Is passing the application thread of the system_server alright?
+ // e.g. the sandbox getting privileged access due to this.
+ ComponentName cn = ActivityManagerService.this.startService(
+ mContext.getIApplicationThread(), service,
+ service.resolveTypeIfNeeded(mContext.getContentResolver()), false,
+ mContext.getOpPackageName(), mContext.getAttributionTag(),
+ UserHandle.getUserId(clientAppUid), true, clientAppUid, clientAppPackage,
+ processName);
+ if (cn != null) {
+ if (cn.getPackageName().equals("!")) {
+ throw new SecurityException(
+ "Not allowed to start service " + service
+ + " without permission " + cn.getClassName());
+ } else if (cn.getPackageName().equals("!!")) {
+ throw new SecurityException(
+ "Unable to start service " + service
+ + ": " + cn.getClassName());
+ } else if (cn.getPackageName().equals("?")) {
+ throw ServiceStartNotAllowedException.newInstance(false,
+ "Not allowed to start service " + service + ": "
+ + cn.getClassName());
+ }
+ }
+
+ return cn;
+ }
+
+ @Override
+ public boolean stopSdkSandboxService(Intent service, int clientAppUid,
+ String clientAppPackage, String processName) {
+ validateSdkSandboxParams(service, clientAppUid, clientAppPackage, processName);
+ int res = ActivityManagerService.this.stopService(
+ mContext.getIApplicationThread(), service,
+ service.resolveTypeIfNeeded(mContext.getContentResolver()),
+ UserHandle.getUserId(clientAppUid), true, clientAppUid, clientAppPackage,
+ processName);
+ if (res < 0) {
+ throw new SecurityException(
+ "Not allowed to stop service " + service);
+ }
+ return res != 0;
+ }
+
+ @Override
public boolean bindSdkSandboxService(Intent service, ServiceConnection conn,
int clientAppUid, IBinder clientApplicationThread, String clientAppPackage,
String processName, int flags)
@@ -17292,27 +17386,10 @@
int clientAppUid, IBinder clientApplicationThread, String clientAppPackage,
String processName, long flags)
throws RemoteException {
- if (service == null) {
- throw new IllegalArgumentException("intent is null");
- }
+ validateSdkSandboxParams(service, clientAppUid, clientAppPackage, processName);
if (conn == null) {
throw new IllegalArgumentException("connection is null");
}
- if (clientAppPackage == null) {
- throw new IllegalArgumentException("clientAppPackage is null");
- }
- if (processName == null) {
- throw new IllegalArgumentException("processName is null");
- }
- if (service.getComponent() == null) {
- throw new IllegalArgumentException("service must specify explicit component");
- }
- if (!UserHandle.isApp(clientAppUid)) {
- throw new IllegalArgumentException("uid is not within application range");
- }
- if (mAppOpsService.checkPackage(clientAppUid, clientAppPackage) != MODE_ALLOWED) {
- throw new IllegalArgumentException("uid does not belong to provided package");
- }
Handler handler = mContext.getMainThreadHandler();
IApplicationThread clientApplicationThreadVerified = null;
@@ -17345,6 +17422,28 @@
UserHandle.getUserId(clientAppUid)) != 0;
}
+ private void validateSdkSandboxParams(Intent service, int clientAppUid,
+ String clientAppPackage, String processName) {
+ if (service == null) {
+ throw new IllegalArgumentException("intent is null");
+ }
+ if (clientAppPackage == null) {
+ throw new IllegalArgumentException("clientAppPackage is null");
+ }
+ if (processName == null) {
+ throw new IllegalArgumentException("processName is null");
+ }
+ if (service.getComponent() == null) {
+ throw new IllegalArgumentException("service must specify explicit component");
+ }
+ if (!UserHandle.isApp(clientAppUid)) {
+ throw new IllegalArgumentException("uid is not within application range");
+ }
+ if (mAppOpsService.checkPackage(clientAppUid, clientAppPackage) != MODE_ALLOWED) {
+ throw new IllegalArgumentException("uid does not belong to provided package");
+ }
+ }
+
@Override
public boolean bindSdkSandboxService(Intent service, ServiceConnection conn,
int clientAppUid, String clientAppPackage, String processName, int flags)
diff --git a/services/core/java/com/android/server/am/ActivityManagerUtils.java b/services/core/java/com/android/server/am/ActivityManagerUtils.java
index 9be553c..01466b8 100644
--- a/services/core/java/com/android/server/am/ActivityManagerUtils.java
+++ b/services/core/java/com/android/server/am/ActivityManagerUtils.java
@@ -17,11 +17,13 @@
import android.app.ActivityThread;
import android.content.ContentResolver;
+import android.content.Intent;
import android.provider.Settings;
import android.util.ArrayMap;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -133,4 +135,25 @@
public static int hashComponentNameForAtom(String shortInstanceName) {
return getUnsignedHashUnCached(shortInstanceName) ^ getAndroidIdHash();
}
+
+ /**
+ * Helper method to log an unsafe intent event.
+ */
+ public static void logUnsafeIntentEvent(int event, int callingUid,
+ Intent intent, String resolvedType, boolean blocked) {
+ String[] categories = intent.getCategories() == null ? new String[0]
+ : intent.getCategories().toArray(String[]::new);
+ String component = intent.getComponent() == null ? null
+ : intent.getComponent().flattenToString();
+ FrameworkStatsLog.write(FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED,
+ event,
+ callingUid,
+ component,
+ intent.getPackage(),
+ intent.getAction(),
+ categories,
+ resolvedType,
+ intent.getScheme(),
+ blocked);
+ }
}
diff --git a/services/core/java/com/android/server/am/AppBatteryTracker.java b/services/core/java/com/android/server/am/AppBatteryTracker.java
index 6619025..c2326f6 100644
--- a/services/core/java/com/android/server/am/AppBatteryTracker.java
+++ b/services/core/java/com/android/server/am/AppBatteryTracker.java
@@ -1388,7 +1388,7 @@
final int mDefaultBgCurrentDrainPowerComponent;
/**
- * Default value to {@link #mBgCurrentDrainExmptedTypes}.
+ * Default value to {@link #mBgCurrentDrainExemptedTypes}.
**/
final int mDefaultBgCurrentDrainExemptedTypes;
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 913f151..f4685f0 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -1921,9 +1921,26 @@
public void handleMessage(Message msg) {
switch (msg.what) {
case SET_FROZEN_PROCESS_MSG:
+ {
+ ProcessRecord proc = (ProcessRecord) msg.obj;
+ int pid = proc.getPid();
+ final String name = proc.processName;
synchronized (mAm) {
- freezeProcess((ProcessRecord) msg.obj);
+ freezeProcess(proc);
}
+ try {
+ // post-check to prevent deadlock
+ mProcLocksReader.handleBlockingFileLocks(this);
+ } catch (Exception e) {
+ Slog.e(TAG_AM, "Unable to check file locks for "
+ + name + "(" + pid + "): " + e);
+ synchronized (mAm) {
+ synchronized (mProcLock) {
+ unfreezeAppLSP(proc, OomAdjuster.OOM_ADJ_REASON_NONE);
+ }
+ }
+ }
+ }
break;
case REPORT_UNFREEZE_MSG:
int pid = msg.arg1;
@@ -2057,16 +2074,6 @@
}
});
}
-
- try {
- // post-check to prevent deadlock
- mProcLocksReader.handleBlockingFileLocks(this);
- } catch (Exception e) {
- Slog.e(TAG_AM, "Unable to check file locks for " + name + "(" + pid + "): " + e);
- synchronized (mProcLock) {
- unfreezeAppLSP(proc, OomAdjuster.OOM_ADJ_REASON_NONE);
- }
- }
}
private void reportUnfreeze(int pid, int frozenDuration, String processName,
@@ -2123,22 +2130,25 @@
if (DEBUG_FREEZER) {
Slog.d(TAG_AM, "Blocking file lock found: " + pids);
}
- synchronized (mProcLock) {
- int pid = pids.get(0);
- ProcessRecord app = mFrozenProcesses.get(pid);
- ProcessRecord pr;
- if (app != null) {
- for (int i = 1; i < pids.size(); i++) {
- int blocked = pids.get(i);
- synchronized (mAm.mPidsSelfLocked) {
- pr = mAm.mPidsSelfLocked.get(blocked);
- }
- if (pr != null && pr.mState.getCurAdj() < ProcessList.CACHED_APP_MIN_ADJ) {
- Slog.d(TAG_AM, app.processName + " (" + pid + ") blocks "
- + pr.processName + " (" + blocked + ")");
- // Found at least one blocked non-cached process
- unfreezeAppLSP(app, OomAdjuster.OOM_ADJ_REASON_NONE);
- break;
+ synchronized (mAm) {
+ synchronized (mProcLock) {
+ int pid = pids.get(0);
+ ProcessRecord app = mFrozenProcesses.get(pid);
+ ProcessRecord pr;
+ if (app != null) {
+ for (int i = 1; i < pids.size(); i++) {
+ int blocked = pids.get(i);
+ synchronized (mAm.mPidsSelfLocked) {
+ pr = mAm.mPidsSelfLocked.get(blocked);
+ }
+ if (pr != null
+ && pr.mState.getCurAdj() < ProcessList.CACHED_APP_MIN_ADJ) {
+ Slog.d(TAG_AM, app.processName + " (" + pid + ") blocks "
+ + pr.processName + " (" + blocked + ")");
+ // Found at least one blocked non-cached process
+ unfreezeAppLSP(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ break;
+ }
}
}
}
diff --git a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
index c0cb7d9..993595b 100644
--- a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
+++ b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
@@ -316,6 +316,10 @@
// then we should care, otherwise we assume
// it's not related to any FGS
UidState uidState = mUids.get(uid);
+ if (uidState == null) {
+ Log.w(TAG, "API event end called before start!");
+ return -1;
+ }
if (uidState.mOpenWithFgsCount.contains(apiType)) {
// are there any calls that started with an FGS?
if (uidState.mOpenWithFgsCount.get(apiType) != 0) {
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 32db33d..0c36626 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1073,135 +1073,165 @@
@GuardedBy({"mService", "mProcLock"})
private void assignCachedAdjIfNecessary(ArrayList<ProcessRecord> lruList) {
final int numLru = lruList.size();
+ if (mConstants.USE_TIERED_CACHED_ADJ) {
+ final long now = SystemClock.uptimeMillis();
+ for (int i = numLru - 1; i >= 0; i--) {
+ ProcessRecord app = lruList.get(i);
+ final ProcessStateRecord state = app.mState;
+ final ProcessCachedOptimizerRecord opt = app.mOptRecord;
+ if (!app.isKilledByAm() && app.getThread() != null && state.getCurAdj()
+ >= UNKNOWN_ADJ) {
+ final ProcessServiceRecord psr = app.mServices;
+ int targetAdj = CACHED_APP_MIN_ADJ;
- // First update the OOM adjustment for each of the
- // application processes based on their current state.
- int curCachedAdj = CACHED_APP_MIN_ADJ;
- int nextCachedAdj = curCachedAdj + (CACHED_APP_IMPORTANCE_LEVELS * 2);
- int curCachedImpAdj = 0;
- int curEmptyAdj = CACHED_APP_MIN_ADJ + CACHED_APP_IMPORTANCE_LEVELS;
- int nextEmptyAdj = curEmptyAdj + (CACHED_APP_IMPORTANCE_LEVELS * 2);
+ if (opt != null && opt.isFreezeExempt()) {
+ // BIND_WAIVE_PRIORITY and the like get oom_adj 900
+ targetAdj += 0;
+ } else if ((state.getSetAdj() >= CACHED_APP_MIN_ADJ)
+ && (state.getLastStateTime()
+ + mConstants.TIERED_CACHED_ADJ_DECAY_TIME) < now) {
+ // Older cached apps get 950
+ targetAdj += 50;
+ } else {
+ // Newer cached apps get 910
+ targetAdj += 10;
+ }
+ state.setCurRawAdj(targetAdj);
+ state.setCurAdj(psr.modifyRawOomAdj(targetAdj));
+ }
+ }
+ } else {
+ // First update the OOM adjustment for each of the
+ // application processes based on their current state.
+ int curCachedAdj = CACHED_APP_MIN_ADJ;
+ int nextCachedAdj = curCachedAdj + (CACHED_APP_IMPORTANCE_LEVELS * 2);
+ int curCachedImpAdj = 0;
+ int curEmptyAdj = CACHED_APP_MIN_ADJ + CACHED_APP_IMPORTANCE_LEVELS;
+ int nextEmptyAdj = curEmptyAdj + (CACHED_APP_IMPORTANCE_LEVELS * 2);
- final int emptyProcessLimit = mConstants.CUR_MAX_EMPTY_PROCESSES;
- final int cachedProcessLimit = mConstants.CUR_MAX_CACHED_PROCESSES
- - emptyProcessLimit;
- // Let's determine how many processes we have running vs.
- // how many slots we have for background processes; we may want
- // to put multiple processes in a slot of there are enough of
- // them.
- int numEmptyProcs = numLru - mNumNonCachedProcs - mNumCachedHiddenProcs;
- if (numEmptyProcs > cachedProcessLimit) {
- // If there are more empty processes than our limit on cached
- // processes, then use the cached process limit for the factor.
- // This ensures that the really old empty processes get pushed
- // down to the bottom, so if we are running low on memory we will
- // have a better chance at keeping around more cached processes
- // instead of a gazillion empty processes.
- numEmptyProcs = cachedProcessLimit;
- }
- int cachedFactor = (mNumCachedHiddenProcs > 0 ? (mNumCachedHiddenProcs + mNumSlots - 1) : 1)
- / mNumSlots;
- if (cachedFactor < 1) cachedFactor = 1;
+ final int emptyProcessLimit = mConstants.CUR_MAX_EMPTY_PROCESSES;
+ final int cachedProcessLimit = mConstants.CUR_MAX_CACHED_PROCESSES
+ - emptyProcessLimit;
+ // Let's determine how many processes we have running vs.
+ // how many slots we have for background processes; we may want
+ // to put multiple processes in a slot of there are enough of
+ // them.
+ int numEmptyProcs = numLru - mNumNonCachedProcs - mNumCachedHiddenProcs;
+ if (numEmptyProcs > cachedProcessLimit) {
+ // If there are more empty processes than our limit on cached
+ // processes, then use the cached process limit for the factor.
+ // This ensures that the really old empty processes get pushed
+ // down to the bottom, so if we are running low on memory we will
+ // have a better chance at keeping around more cached processes
+ // instead of a gazillion empty processes.
+ numEmptyProcs = cachedProcessLimit;
+ }
+ int cachedFactor = (mNumCachedHiddenProcs > 0
+ ? (mNumCachedHiddenProcs + mNumSlots - 1) : 1)
+ / mNumSlots;
+ if (cachedFactor < 1) cachedFactor = 1;
- int emptyFactor = (numEmptyProcs + mNumSlots - 1) / mNumSlots;
- if (emptyFactor < 1) emptyFactor = 1;
+ int emptyFactor = (numEmptyProcs + mNumSlots - 1) / mNumSlots;
+ if (emptyFactor < 1) emptyFactor = 1;
- int stepCached = -1;
- int stepEmpty = -1;
- int lastCachedGroup = 0;
- int lastCachedGroupImportance = 0;
- int lastCachedGroupUid = 0;
+ int stepCached = -1;
+ int stepEmpty = -1;
+ int lastCachedGroup = 0;
+ int lastCachedGroupImportance = 0;
+ int lastCachedGroupUid = 0;
- for (int i = numLru - 1; i >= 0; i--) {
- ProcessRecord app = lruList.get(i);
- final ProcessStateRecord state = app.mState;
- // If we haven't yet assigned the final cached adj
- // to the process, do that now.
- if (!app.isKilledByAm() && app.getThread() != null && state.getCurAdj()
+
+ for (int i = numLru - 1; i >= 0; i--) {
+ ProcessRecord app = lruList.get(i);
+ final ProcessStateRecord state = app.mState;
+ // If we haven't yet assigned the final cached adj
+ // to the process, do that now.
+ if (!app.isKilledByAm() && app.getThread() != null && state.getCurAdj()
>= UNKNOWN_ADJ) {
- final ProcessServiceRecord psr = app.mServices;
- switch (state.getCurProcState()) {
- case PROCESS_STATE_CACHED_ACTIVITY:
- case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
- case ActivityManager.PROCESS_STATE_CACHED_RECENT:
- // Figure out the next cached level, taking into account groups.
- boolean inGroup = false;
- final int connectionGroup = psr.getConnectionGroup();
- if (connectionGroup != 0) {
- final int connectionImportance = psr.getConnectionImportance();
- if (lastCachedGroupUid == app.uid
+ final ProcessServiceRecord psr = app.mServices;
+ switch (state.getCurProcState()) {
+ case PROCESS_STATE_CACHED_ACTIVITY:
+ case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
+ case ActivityManager.PROCESS_STATE_CACHED_RECENT:
+ // Figure out the next cached level, taking into account groups.
+ boolean inGroup = false;
+ final int connectionGroup = psr.getConnectionGroup();
+ if (connectionGroup != 0) {
+ final int connectionImportance = psr.getConnectionImportance();
+ if (lastCachedGroupUid == app.uid
&& lastCachedGroup == connectionGroup) {
- // This is in the same group as the last process, just tweak
- // adjustment by importance.
- if (connectionImportance > lastCachedGroupImportance) {
- lastCachedGroupImportance = connectionImportance;
- if (curCachedAdj < nextCachedAdj
+ // This is in the same group as the last process, just tweak
+ // adjustment by importance.
+ if (connectionImportance > lastCachedGroupImportance) {
+ lastCachedGroupImportance = connectionImportance;
+ if (curCachedAdj < nextCachedAdj
&& curCachedAdj < CACHED_APP_MAX_ADJ) {
- curCachedImpAdj++;
+ curCachedImpAdj++;
+ }
+ }
+ inGroup = true;
+ } else {
+ lastCachedGroupUid = app.uid;
+ lastCachedGroup = connectionGroup;
+ lastCachedGroupImportance = connectionImportance;
+ }
+ }
+ if (!inGroup && curCachedAdj != nextCachedAdj) {
+ stepCached++;
+ curCachedImpAdj = 0;
+ if (stepCached >= cachedFactor) {
+ stepCached = 0;
+ curCachedAdj = nextCachedAdj;
+ nextCachedAdj += CACHED_APP_IMPORTANCE_LEVELS * 2;
+ if (nextCachedAdj > CACHED_APP_MAX_ADJ) {
+ nextCachedAdj = CACHED_APP_MAX_ADJ;
}
}
- inGroup = true;
- } else {
- lastCachedGroupUid = app.uid;
- lastCachedGroup = connectionGroup;
- lastCachedGroupImportance = connectionImportance;
}
- }
- if (!inGroup && curCachedAdj != nextCachedAdj) {
- stepCached++;
- curCachedImpAdj = 0;
- if (stepCached >= cachedFactor) {
- stepCached = 0;
- curCachedAdj = nextCachedAdj;
- nextCachedAdj += CACHED_APP_IMPORTANCE_LEVELS * 2;
- if (nextCachedAdj > CACHED_APP_MAX_ADJ) {
- nextCachedAdj = CACHED_APP_MAX_ADJ;
+ // This process is a cached process holding activities...
+ // assign it the next cached value for that type, and then
+ // step that cached level.
+ state.setCurRawAdj(curCachedAdj + curCachedImpAdj);
+ state.setCurAdj(psr.modifyRawOomAdj(curCachedAdj + curCachedImpAdj));
+ if (DEBUG_LRU) {
+ Slog.d(TAG_LRU, "Assigning activity LRU #" + i
+ + " adj: " + state.getCurAdj()
+ + " (curCachedAdj=" + curCachedAdj
+ + " curCachedImpAdj=" + curCachedImpAdj + ")");
+ }
+ break;
+ default:
+ // Figure out the next cached level.
+ if (curEmptyAdj != nextEmptyAdj) {
+ stepEmpty++;
+ if (stepEmpty >= emptyFactor) {
+ stepEmpty = 0;
+ curEmptyAdj = nextEmptyAdj;
+ nextEmptyAdj += CACHED_APP_IMPORTANCE_LEVELS * 2;
+ if (nextEmptyAdj > CACHED_APP_MAX_ADJ) {
+ nextEmptyAdj = CACHED_APP_MAX_ADJ;
+ }
}
}
- }
- // This process is a cached process holding activities...
- // assign it the next cached value for that type, and then
- // step that cached level.
- state.setCurRawAdj(curCachedAdj + curCachedImpAdj);
- state.setCurAdj(psr.modifyRawOomAdj(curCachedAdj + curCachedImpAdj));
- if (DEBUG_LRU) {
- Slog.d(TAG_LRU, "Assigning activity LRU #" + i
- + " adj: " + state.getCurAdj()
- + " (curCachedAdj=" + curCachedAdj
- + " curCachedImpAdj=" + curCachedImpAdj + ")");
- }
- break;
- default:
- // Figure out the next cached level.
- if (curEmptyAdj != nextEmptyAdj) {
- stepEmpty++;
- if (stepEmpty >= emptyFactor) {
- stepEmpty = 0;
- curEmptyAdj = nextEmptyAdj;
- nextEmptyAdj += CACHED_APP_IMPORTANCE_LEVELS * 2;
- if (nextEmptyAdj > CACHED_APP_MAX_ADJ) {
- nextEmptyAdj = CACHED_APP_MAX_ADJ;
- }
+ // For everything else, assign next empty cached process
+ // level and bump that up. Note that this means that
+ // long-running services that have dropped down to the
+ // cached level will be treated as empty (since their process
+ // state is still as a service), which is what we want.
+ state.setCurRawAdj(curEmptyAdj);
+ state.setCurAdj(psr.modifyRawOomAdj(curEmptyAdj));
+ if (DEBUG_LRU) {
+ Slog.d(TAG_LRU, "Assigning empty LRU #" + i
+ + " adj: " + state.getCurAdj()
+ + " (curEmptyAdj=" + curEmptyAdj
+ + ")");
}
- }
- // For everything else, assign next empty cached process
- // level and bump that up. Note that this means that
- // long-running services that have dropped down to the
- // cached level will be treated as empty (since their process
- // state is still as a service), which is what we want.
- state.setCurRawAdj(curEmptyAdj);
- state.setCurAdj(psr.modifyRawOomAdj(curEmptyAdj));
- if (DEBUG_LRU) {
- Slog.d(TAG_LRU, "Assigning empty LRU #" + i
- + " adj: " + state.getCurAdj() + " (curEmptyAdj=" + curEmptyAdj
- + ")");
- }
- break;
+ break;
+ }
}
}
}
}
-
private long mNextNoKillDebugMessageTime;
private double mLastFreeSwapPercent = 1.00;
@@ -1318,6 +1348,13 @@
// left sitting around after no longer needed.
app.killLocked("isolated not needed", ApplicationExitInfo.REASON_OTHER,
ApplicationExitInfo.SUBREASON_ISOLATED_NOT_NEEDED, true);
+ } else if (app.isSdkSandbox && psr.numberOfRunningServices() <= 0
+ && app.getActiveInstrumentation() == null) {
+ // If this is an SDK sandbox process and there are no services running it, we
+ // aggressively kill the sandbox as we usually don't want to re-use the same
+ // sandbox again.
+ app.killLocked("sandbox not needed", ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.SUBREASON_SDK_SANDBOX_NOT_NEEDED, true);
} else {
// Keeping this process, update its uid.
updateAppUidRecLSP(app);
@@ -2018,25 +2055,37 @@
}
}
}
-
if (state.getCachedIsPreviousProcess() && state.getCachedHasActivities()) {
- if (adj > PREVIOUS_APP_ADJ) {
- // This was the previous process that showed UI to the user.
- // We want to try to keep it around more aggressively, to give
- // a good experience around switching between two apps.
- adj = PREVIOUS_APP_ADJ;
- schedGroup = SCHED_GROUP_BACKGROUND;
- state.setCached(false);
- state.setAdjType("previous");
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to prev: " + app);
- }
- }
- if (procState > PROCESS_STATE_LAST_ACTIVITY) {
+ // This was the previous process that showed UI to the user. We want to
+ // try to keep it around more aggressively, to give a good experience
+ // around switching between two apps. However, we don't want to keep the
+ // process in this privileged state indefinitely. Eventually, allow the
+ // app to be demoted to cached.
+ if ((state.getSetProcState() == PROCESS_STATE_LAST_ACTIVITY
+ && (state.getLastStateTime() + mConstants.MAX_PREVIOUS_TIME) < now)) {
procState = PROCESS_STATE_LAST_ACTIVITY;
- state.setAdjType("previous");
+ schedGroup = SCHED_GROUP_BACKGROUND;
+ state.setAdjType("previous-expired");
+ adj = CACHED_APP_MIN_ADJ;
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to prev: " + app);
+ reportOomAdjMessageLocked(TAG_OOM_ADJ, "Expire prev adj: " + app);
+ }
+ } else {
+ if (adj > PREVIOUS_APP_ADJ) {
+ adj = PREVIOUS_APP_ADJ;
+ schedGroup = SCHED_GROUP_BACKGROUND;
+ state.setCached(false);
+ state.setAdjType("previous");
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to prev: " + app);
+ }
+ }
+ if (procState > PROCESS_STATE_LAST_ACTIVITY) {
+ procState = PROCESS_STATE_LAST_ACTIVITY;
+ state.setAdjType("previous");
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to prev: " + app);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 14ecf9f..daf227c 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -69,7 +69,8 @@
@Overridable
private static final long DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_SENDER = 244637991;
private static final String ENABLE_DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_SENDER =
- "enable_default_rescind_bal_privileges_from_pending_intent_sender";
+ "DefaultRescindBalPrivilegesFromPendingIntentSender__"
+ + "enable_default_rescind_bal_privileges_from_pending_intent_sender";
public static final int FLAG_ACTIVITY_SENDER = 1 << 0;
public static final int FLAG_BROADCAST_SENDER = 1 << 1;
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index dce88da..005ad20 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -23,6 +23,7 @@
import android.app.TaskStackListener;
import android.content.Context;
import android.content.pm.ApplicationInfo;
+import android.hardware.biometrics.AuthenticateOptions;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
@@ -44,8 +45,8 @@
/**
* A class to keep track of the authentication state for a given client.
*/
-public abstract class AuthenticationClient<T> extends AcquisitionClient<T>
- implements AuthenticationConsumer {
+public abstract class AuthenticationClient<T, O extends AuthenticateOptions>
+ extends AcquisitionClient<T> implements AuthenticationConsumer {
// New, has not started yet
public static final int STATE_NEW = 0;
@@ -89,14 +90,15 @@
public AuthenticationClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
- int targetUserId, long operationId, boolean restricted, @NonNull String owner,
- int cookie, boolean requireConfirmation, int sensorId,
+ long operationId, boolean restricted, @NonNull O options,
+ int cookie, boolean requireConfirmation,
@NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
boolean isStrongBiometric, @Nullable TaskStackListener taskStackListener,
@NonNull LockoutTracker lockoutTracker, boolean allowBackgroundAuthentication,
boolean shouldVibrate, int sensorStrength) {
- super(context, lazyDaemon, token, listener, targetUserId, owner, cookie, sensorId,
- shouldVibrate, biometricLogger, biometricContext);
+ super(context, lazyDaemon, token, listener, options.getUserId(),
+ options.getOpPackageName(), cookie, options.getSensorId(), shouldVibrate,
+ biometricLogger, biometricContext);
mIsStrongBiometric = isStrongBiometric;
mOperationId = operationId;
mRequireConfirmation = requireConfirmation;
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/BiometricServiceProvider.java
index 0f1fe68..25651fa28 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricServiceProvider.java
@@ -17,6 +17,7 @@
package com.android.server.biometrics.sensors;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.hardware.biometrics.SensorPropertiesInternal;
import android.util.proto.ProtoOutputStream;
@@ -39,7 +40,7 @@
List<T> getSensorProperties();
/** Properties for the given sensor id. */
- @NonNull
+ @Nullable
T getSensorProperties(int sensorId);
boolean isHardwareDetected(int sensorId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java b/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java
index 2263e80..a4b0a0e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java
@@ -92,7 +92,7 @@
final int previousBiometricState = mBiometricState;
if (client instanceof AuthenticationClient) {
- final AuthenticationClient<?> authClient = (AuthenticationClient<?>) client;
+ final AuthenticationClient<?, ?> authClient = (AuthenticationClient<?, ?>) client;
if (authClient.isKeyguard()) {
mBiometricState = STATE_KEYGUARD_AUTH;
} else if (authClient.isBiometricPrompt()) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
index 5182968..fb64bcc 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
@@ -64,9 +64,10 @@
long operationId, int userId, IBiometricSensorReceiver sensorReceiver,
String opPackageName, long requestId, int cookie, boolean allowBackgroundAuthentication)
throws RemoteException {
- mFaceService.prepareForAuthentication(mSensorId, requireConfirmation, token, operationId,
+ mFaceService.prepareForAuthentication(requireConfirmation, token, operationId,
sensorReceiver, new FaceAuthenticateOptions.Builder()
.setUserId(userId)
+ .setSensorId(mSensorId)
.setOpPackageName(opPackageName)
.build(),
requestId, cookie, allowBackgroundAuthentication);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 1ee9f53..6d7b2cb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -246,7 +246,6 @@
super.authenticate_enforcePermission();
- final int userId = options.getUserId();
final String opPackageName = options.getOpPackageName();
final boolean restricted = false; // Face APIs are private
final int statsClient = Utils.isKeyguard(getContext(), opPackageName)
@@ -261,9 +260,11 @@
if (provider == null) {
Slog.w(TAG, "Null provider for authenticate");
return -1;
+ } else {
+ options.setSensorId(provider.first);
}
- return provider.second.scheduleAuthenticate(provider.first, token, operationId,
+ return provider.second.scheduleAuthenticate(token, operationId,
0 /* cookie */, new ClientMonitorCallbackConverter(receiver), options,
restricted, statsClient, isKeyguard);
}
@@ -286,28 +287,27 @@
return -1;
}
- return provider.second.scheduleFaceDetect(provider.first, token, options.getUserId(),
- new ClientMonitorCallbackConverter(receiver), opPackageName,
+ return provider.second.scheduleFaceDetect(token,
+ new ClientMonitorCallbackConverter(receiver), options,
BiometricsProtoEnums.CLIENT_KEYGUARD);
}
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
- public void prepareForAuthentication(int sensorId, boolean requireConfirmation,
+ public void prepareForAuthentication(boolean requireConfirmation,
IBinder token, long operationId, IBiometricSensorReceiver sensorReceiver,
FaceAuthenticateOptions options, long requestId, int cookie,
boolean allowBackgroundAuthentication) {
super.prepareForAuthentication_enforcePermission();
- final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(options.getSensorId());
if (provider == null) {
Slog.w(TAG, "Null provider for prepareForAuthentication");
return;
}
- final boolean isKeyguardBypassEnabled = false; // only valid for keyguard clients
final boolean restricted = true; // BiometricPrompt is always restricted
- provider.scheduleAuthenticate(sensorId, token, operationId, cookie,
+ provider.scheduleAuthenticate(token, operationId, cookie,
new ClientMonitorCallbackConverter(sensorReceiver), options, requestId,
restricted, BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
allowBackgroundAuthentication);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
index 609c6a7..2cf64b7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
@@ -83,18 +83,19 @@
void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId);
- long scheduleFaceDetect(int sensorId, @NonNull IBinder token, int userId,
- @NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,
+ long scheduleFaceDetect(@NonNull IBinder token,
+ @NonNull ClientMonitorCallbackConverter callback,
+ @NonNull FaceAuthenticateOptions options,
int statsClient);
void cancelFaceDetect(int sensorId, @NonNull IBinder token, long requestId);
- long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
+ long scheduleAuthenticate(@NonNull IBinder token, long operationId,
int cookie, @NonNull ClientMonitorCallbackConverter callback,
@NonNull FaceAuthenticateOptions options,
boolean restricted, int statsClient, boolean allowBackgroundAuthentication);
- void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
+ void scheduleAuthenticate(@NonNull IBinder token, long operationId,
int cookie, @NonNull ClientMonitorCallbackConverter callback,
@NonNull FaceAuthenticateOptions options, long requestId,
boolean restricted, int statsClient, boolean allowBackgroundAuthentication);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index 29dd707..976f1cb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -28,6 +28,7 @@
import android.hardware.biometrics.BiometricManager.Authenticators;
import android.hardware.biometrics.common.ICancellationSignal;
import android.hardware.biometrics.face.IFace;
+import android.hardware.face.FaceAuthenticateOptions;
import android.hardware.face.FaceAuthenticationFrame;
import android.hardware.face.FaceManager;
import android.os.IBinder;
@@ -56,7 +57,7 @@
/**
* Face-specific authentication client for the {@link IFace} AIDL HAL interface.
*/
-class FaceAuthenticationClient extends AuthenticationClient<AidlSession>
+class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAuthenticateOptions>
implements LockoutConsumer {
private static final String TAG = "FaceAuthenticationClient";
@@ -80,16 +81,16 @@
FaceAuthenticationClient(@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon,
@NonNull IBinder token, long requestId,
- @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
- boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId,
+ @NonNull ClientMonitorCallbackConverter listener, long operationId,
+ boolean restricted, @NonNull FaceAuthenticateOptions options, int cookie,
+ boolean requireConfirmation,
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
boolean isStrongBiometric, @NonNull UsageStats usageStats,
@NonNull LockoutCache lockoutCache, boolean allowBackgroundAuthentication,
@Authenticators.Types int sensorStrength) {
- this(context, lazyDaemon, token, requestId, listener, targetUserId, operationId,
- restricted, owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
- isStrongBiometric, usageStats, lockoutCache /* lockoutCache */,
- allowBackgroundAuthentication,
+ this(context, lazyDaemon, token, requestId, listener, operationId,
+ restricted, options, cookie, requireConfirmation, logger, biometricContext,
+ isStrongBiometric, usageStats, lockoutCache, allowBackgroundAuthentication,
context.getSystemService(SensorPrivacyManager.class), sensorStrength);
}
@@ -97,15 +98,16 @@
FaceAuthenticationClient(@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon,
@NonNull IBinder token, long requestId,
- @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
- boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId,
+ @NonNull ClientMonitorCallbackConverter listener, long operationId,
+ boolean restricted, @NonNull FaceAuthenticateOptions options, int cookie,
+ boolean requireConfirmation,
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
boolean isStrongBiometric, @NonNull UsageStats usageStats,
@NonNull LockoutCache lockoutCache, boolean allowBackgroundAuthentication,
SensorPrivacyManager sensorPrivacyManager,
@Authenticators.Types int biometricStrength) {
- super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
- owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
+ super(context, lazyDaemon, token, listener, operationId, restricted,
+ options, cookie, requireConfirmation, logger, biometricContext,
isStrongBiometric, null /* taskStackListener */, null /* lockoutCache */,
allowBackgroundAuthentication, false /* shouldVibrate */,
biometricStrength);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
index 506b2bc..e65202d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
@@ -22,6 +22,7 @@
import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.face.FaceAuthenticateOptions;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
@@ -51,11 +52,11 @@
FaceDetectClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
@NonNull IBinder token, long requestId,
- @NonNull ClientMonitorCallbackConverter listener, int userId,
- @NonNull String owner, int sensorId,
+ @NonNull ClientMonitorCallbackConverter listener,
+ @NonNull FaceAuthenticateOptions options,
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
boolean isStrongBiometric) {
- this(context, lazyDaemon, token, requestId, listener, userId, owner, sensorId,
+ this(context, lazyDaemon, token, requestId, listener, options,
logger, biometricContext, isStrongBiometric,
context.getSystemService(SensorPrivacyManager.class));
}
@@ -63,11 +64,12 @@
@VisibleForTesting
FaceDetectClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
@NonNull IBinder token, long requestId,
- @NonNull ClientMonitorCallbackConverter listener, int userId,
- @NonNull String owner, int sensorId,
+ @NonNull ClientMonitorCallbackConverter listener,
+ @NonNull FaceAuthenticateOptions options,
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
boolean isStrongBiometric, SensorPrivacyManager sensorPrivacyManager) {
- super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
+ super(context, lazyDaemon, token, listener, options.getUserId(),
+ options.getOpPackageName(), 0 /* cookie */, options.getSensorId(),
true /* shouldVibrate */, logger, biometricContext);
setRequestId(requestId);
mIsStrongBiometric = isStrongBiometric;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index 41e0269..cf8ea86 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -410,16 +410,17 @@
}
@Override
- public long scheduleFaceDetect(int sensorId, @NonNull IBinder token,
- int userId, @NonNull ClientMonitorCallbackConverter callback,
- @NonNull String opPackageName, int statsClient) {
+ public long scheduleFaceDetect(@NonNull IBinder token,
+ @NonNull ClientMonitorCallbackConverter callback,
+ @NonNull FaceAuthenticateOptions options, int statsClient) {
final long id = mRequestCounter.incrementAndGet();
+ final int sensorId = options.getSensorId();
mHandler.post(() -> {
final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
final FaceDetectClient client = new FaceDetectClient(mContext,
mSensors.get(sensorId).getLazySession(),
- token, id, callback, userId, opPackageName, sensorId,
+ token, id, callback, options,
createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
mBiometricContext, isStrongBiometric);
scheduleForSensor(sensorId, client, mBiometricStateCallback);
@@ -435,18 +436,19 @@
}
@Override
- public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
+ public void scheduleAuthenticate(@NonNull IBinder token, long operationId,
int cookie, @NonNull ClientMonitorCallbackConverter callback,
@NonNull FaceAuthenticateOptions options,
long requestId, boolean restricted, int statsClient,
boolean allowBackgroundAuthentication) {
mHandler.post(() -> {
final int userId = options.getUserId();
+ final int sensorId = options.getSensorId();
final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
final FaceAuthenticationClient client = new FaceAuthenticationClient(
mContext, mSensors.get(sensorId).getLazySession(), token, requestId, callback,
- userId, operationId, restricted, options.getOpPackageName(), cookie,
- false /* requireConfirmation */, sensorId,
+ operationId, restricted, options, cookie,
+ false /* requireConfirmation */,
createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
mBiometricContext, isStrongBiometric,
mUsageStats, mSensors.get(sensorId).getLockoutCache(),
@@ -470,13 +472,13 @@
}
@Override
- public long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
+ public long scheduleAuthenticate(@NonNull IBinder token, long operationId,
int cookie, @NonNull ClientMonitorCallbackConverter callback,
@NonNull FaceAuthenticateOptions options, boolean restricted, int statsClient,
boolean allowBackgroundAuthentication) {
final long id = mRequestCounter.incrementAndGet();
- scheduleAuthenticate(sensorId, token, operationId, cookie, callback,
+ scheduleAuthenticate(token, operationId, cookie, callback,
options, id, restricted, statsClient, allowBackgroundAuthentication);
return id;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index 7e575bc..0f22296 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -651,9 +651,9 @@
}
@Override
- public long scheduleFaceDetect(int sensorId, @NonNull IBinder token,
- int userId, @NonNull ClientMonitorCallbackConverter callback,
- @NonNull String opPackageName, int statsClient) {
+ public long scheduleFaceDetect(@NonNull IBinder token,
+ @NonNull ClientMonitorCallbackConverter callback,
+ @NonNull FaceAuthenticateOptions options, int statsClient) {
throw new IllegalStateException("Face detect not supported by IBiometricsFace@1.0. Did you"
+ "forget to check the supportsFaceDetection flag?");
}
@@ -665,7 +665,7 @@
}
@Override
- public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
+ public void scheduleAuthenticate(@NonNull IBinder token, long operationId,
int cookie, @NonNull ClientMonitorCallbackConverter receiver,
@NonNull FaceAuthenticateOptions options, long requestId, boolean restricted,
int statsClient, boolean allowBackgroundAuthentication) {
@@ -675,8 +675,8 @@
final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorId);
final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext,
- mLazyDaemon, token, requestId, receiver, userId, operationId, restricted,
- options.getOpPackageName(), cookie, false /* requireConfirmation */, mSensorId,
+ mLazyDaemon, token, requestId, receiver, operationId, restricted,
+ options, cookie, false /* requireConfirmation */,
createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
mBiometricContext, isStrongBiometric, mLockoutTracker,
mUsageStats, allowBackgroundAuthentication,
@@ -686,13 +686,13 @@
}
@Override
- public long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
+ public long scheduleAuthenticate(@NonNull IBinder token, long operationId,
int cookie, @NonNull ClientMonitorCallbackConverter receiver,
@NonNull FaceAuthenticateOptions options, boolean restricted, int statsClient,
boolean allowBackgroundAuthentication) {
final long id = mRequestCounter.incrementAndGet();
- scheduleAuthenticate(sensorId, token, operationId, cookie, receiver,
+ scheduleAuthenticate(token, operationId, cookie, receiver,
options, id, restricted, statsClient, allowBackgroundAuthentication);
return id;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
index 1c1f56c..8ab8892 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
@@ -25,6 +25,7 @@
import android.hardware.biometrics.BiometricFaceConstants;
import android.hardware.biometrics.BiometricManager.Authenticators;
import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+import android.hardware.face.FaceAuthenticateOptions;
import android.hardware.face.FaceManager;
import android.os.IBinder;
import android.os.RemoteException;
@@ -50,7 +51,8 @@
* Face-specific authentication client supporting the {@link android.hardware.biometrics.face.V1_0}
* HIDL interface.
*/
-class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> {
+class FaceAuthenticationClient
+ extends AuthenticationClient<IBiometricsFace, FaceAuthenticateOptions> {
private static final String TAG = "FaceAuthenticationClient";
@@ -67,17 +69,18 @@
FaceAuthenticationClient(@NonNull Context context,
@NonNull Supplier<IBiometricsFace> lazyDaemon,
@NonNull IBinder token, long requestId,
- @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
- boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId,
+ @NonNull ClientMonitorCallbackConverter listener, long operationId,
+ boolean restricted, @NonNull FaceAuthenticateOptions options, int cookie,
+ boolean requireConfirmation,
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
boolean isStrongBiometric, @NonNull LockoutTracker lockoutTracker,
@NonNull UsageStats usageStats, boolean allowBackgroundAuthentication,
@Authenticators.Types int sensorStrength) {
- super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
- owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
+ super(context, lazyDaemon, token, listener, operationId, restricted,
+ options, cookie, requireConfirmation, logger, biometricContext,
isStrongBiometric, null /* taskStackListener */,
lockoutTracker, allowBackgroundAuthentication, false /* shouldVibrate */,
- sensorStrength);
+ sensorStrength);
setRequestId(requestId);
mUsageStats = usageStats;
mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
index 52d887a..d47a57a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
@@ -23,6 +23,7 @@
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
import android.hardware.biometrics.SensorPropertiesInternal;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
import android.hardware.fingerprint.IFingerprintService;
import android.os.IBinder;
import android.os.RemoteException;
@@ -63,8 +64,13 @@
long operationId, int userId, IBiometricSensorReceiver sensorReceiver,
String opPackageName, long requestId, int cookie, boolean allowBackgroundAuthentication)
throws RemoteException {
- mFingerprintService.prepareForAuthentication(mSensorId, token, operationId, userId,
- sensorReceiver, opPackageName, requestId, cookie, allowBackgroundAuthentication);
+ mFingerprintService.prepareForAuthentication(token, operationId, sensorReceiver,
+ new FingerprintAuthenticateOptions.Builder()
+ .setSensorId(mSensorId)
+ .setUserId(userId)
+ .setOpPackageName(opPackageName)
+ .build(),
+ requestId, cookie, allowBackgroundAuthentication);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index affc496e..8a33f22 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -119,7 +119,7 @@
@NonNull
private final Supplier<String[]> mAidlInstanceNameSupplier;
@NonNull
- private final Function<String, IFingerprint> mIFingerprintProvider;
+ private final Function<String, FingerprintProvider> mFingerprintProvider;
@NonNull
private final BiometricStateCallback<ServiceProvider, FingerprintSensorPropertiesInternal>
mBiometricStateCallback;
@@ -307,6 +307,8 @@
if (provider == null) {
Slog.w(TAG, "Null provider for authenticate");
return -1;
+ } else {
+ options.setSensorId(provider.first);
}
final FingerprintSensorPropertiesInternal sensorProps =
@@ -322,8 +324,8 @@
return -1;
}
}
- return provider.second.scheduleAuthenticate(provider.first, token, operationId, userId,
- 0 /* cookie */, new ClientMonitorCallbackConverter(receiver), opPackageName,
+ return provider.second.scheduleAuthenticate(token, operationId,
+ 0 /* cookie */, new ClientMonitorCallbackConverter(receiver), options,
restricted, statsClient, isKeyguard);
}
@@ -436,29 +438,32 @@
if (provider == null) {
Slog.w(TAG, "Null provider for detectFingerprint");
return -1;
+ } else {
+ options.setSensorId(provider.first);
}
- return provider.second.scheduleFingerDetect(provider.first, token, options.getUserId(),
- new ClientMonitorCallbackConverter(receiver), opPackageName,
+ return provider.second.scheduleFingerDetect(token,
+ new ClientMonitorCallbackConverter(receiver), options,
BiometricsProtoEnums.CLIENT_KEYGUARD);
}
@android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_BIOMETRIC)
@Override // Binder call
- public void prepareForAuthentication(int sensorId, IBinder token, long operationId,
- int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName,
+ public void prepareForAuthentication(IBinder token, long operationId,
+ IBiometricSensorReceiver sensorReceiver,
+ @NonNull FingerprintAuthenticateOptions options,
long requestId, int cookie, boolean allowBackgroundAuthentication) {
super.prepareForAuthentication_enforcePermission();
- final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(options.getSensorId());
if (provider == null) {
Slog.w(TAG, "Null provider for prepareForAuthentication");
return;
}
final boolean restricted = true; // BiometricPrompt is always restricted
- provider.scheduleAuthenticate(sensorId, token, operationId, userId, cookie,
- new ClientMonitorCallbackConverter(sensorReceiver), opPackageName, requestId,
+ provider.scheduleAuthenticate(token, operationId, cookie,
+ new ClientMonitorCallbackConverter(sensorReceiver), options, requestId,
restricted, BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
allowBackgroundAuthentication);
}
@@ -982,8 +987,7 @@
() -> IBiometricService.Stub.asInterface(
ServiceManager.getService(Context.BIOMETRIC_SERVICE)),
() -> ServiceManager.getDeclaredInstances(IFingerprint.DESCRIPTOR),
- (fqName) -> IFingerprint.Stub.asInterface(
- Binder.allowBlocking(ServiceManager.waitForDeclaredService(fqName))));
+ null /* fingerprintProvider */);
}
@VisibleForTesting
@@ -991,16 +995,35 @@
BiometricContext biometricContext,
Supplier<IBiometricService> biometricServiceSupplier,
Supplier<String[]> aidlInstanceNameSupplier,
- Function<String, IFingerprint> fingerprintProvider) {
+ Function<String, FingerprintProvider> fingerprintProvider) {
super(context);
mBiometricContext = biometricContext;
mAidlInstanceNameSupplier = aidlInstanceNameSupplier;
- mIFingerprintProvider = fingerprintProvider;
mAppOps = context.getSystemService(AppOpsManager.class);
mGestureAvailabilityDispatcher = new GestureAvailabilityDispatcher();
mLockoutResetDispatcher = new LockoutResetDispatcher(context);
mLockPatternUtils = new LockPatternUtils(context);
mBiometricStateCallback = new BiometricStateCallback<>(UserManager.get(context));
+ mFingerprintProvider = fingerprintProvider != null ? fingerprintProvider :
+ (name) -> {
+ final String fqName = IFingerprint.DESCRIPTOR + "/" + name;
+ final IFingerprint fp = IFingerprint.Stub.asInterface(
+ Binder.allowBlocking(ServiceManager.waitForDeclaredService(fqName)));
+ if (fp != null) {
+ try {
+ return new FingerprintProvider(getContext(),
+ mBiometricStateCallback, fp.getSensorProps(), name,
+ mLockoutResetDispatcher, mGestureAvailabilityDispatcher,
+ mBiometricContext);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
+ }
+ } else {
+ Slog.e(TAG, "Unable to get declared service: " + fqName);
+ }
+
+ return null;
+ };
mHandler = new Handler(Looper.getMainLooper());
mRegistry = new FingerprintServiceRegistry(mServiceWrapper, biometricServiceSupplier);
mRegistry.addAllRegisteredCallback(new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
@@ -1044,23 +1067,9 @@
final List<ServiceProvider> providers = new ArrayList<>();
for (String instance : instances) {
- final String fqName = IFingerprint.DESCRIPTOR + "/" + instance;
- final IFingerprint fp = mIFingerprintProvider.apply(fqName);
-
- if (fp != null) {
- try {
- final FingerprintProvider provider = new FingerprintProvider(getContext(),
- mBiometricStateCallback, fp.getSensorProps(), instance,
- mLockoutResetDispatcher, mGestureAvailabilityDispatcher,
- mBiometricContext);
- Slog.i(TAG, "Adding AIDL provider: " + fqName);
- providers.add(provider);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
- }
- } else {
- Slog.e(TAG, "Unable to get declared service: " + fqName);
- }
+ final FingerprintProvider provider = mFingerprintProvider.apply(instance);
+ Slog.i(TAG, "Adding AIDL provider: " + instance);
+ providers.add(provider);
}
return providers;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index 5b6f14d..004af2c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -23,6 +23,7 @@
import android.hardware.biometrics.ITestSessionCallback;
import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
@@ -78,19 +79,21 @@
void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId);
- long scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
- @NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,
+ long scheduleFingerDetect(@NonNull IBinder token,
+ @NonNull ClientMonitorCallbackConverter callback,
+ @NonNull FingerprintAuthenticateOptions options,
int statsClient);
- void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, int userId,
+ void scheduleAuthenticate(@NonNull IBinder token, long operationId,
int cookie, @NonNull ClientMonitorCallbackConverter callback,
- @NonNull String opPackageName, long requestId, boolean restricted, int statsClient,
+ @NonNull FingerprintAuthenticateOptions options,
+ long requestId, boolean restricted, int statsClient,
boolean allowBackgroundAuthentication);
- long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, int userId,
+ long scheduleAuthenticate(@NonNull IBinder token, long operationId,
int cookie, @NonNull ClientMonitorCallbackConverter callback,
- @NonNull String opPackageName, boolean restricted, int statsClient,
- boolean allowBackgroundAuthentication);
+ @NonNull FingerprintAuthenticateOptions options,
+ boolean restricted, int statsClient, boolean allowBackgroundAuthentication);
void startPreparedClient(int sensorId, int cookie);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index d1a7b13..0f81f9f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -26,6 +26,7 @@
import android.hardware.biometrics.BiometricManager.Authenticators;
import android.hardware.biometrics.common.ICancellationSignal;
import android.hardware.biometrics.fingerprint.PointerContext;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
import android.hardware.fingerprint.IUdfpsOverlay;
@@ -65,7 +66,8 @@
* Fingerprint-specific authentication client supporting the {@link
* android.hardware.biometrics.fingerprint.IFingerprint} AIDL interface.
*/
-class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
+class FingerprintAuthenticationClient
+ extends AuthenticationClient<AidlSession, FingerprintAuthenticateOptions>
implements Udfps, LockoutConsumer, PowerPressHandler {
private static final String TAG = "FingerprintAuthenticationClient";
private static final int MESSAGE_AUTH_SUCCESS = 2;
@@ -97,13 +99,11 @@
@NonNull IBinder token,
long requestId,
@NonNull ClientMonitorCallbackConverter listener,
- int targetUserId,
long operationId,
boolean restricted,
- @NonNull String owner,
+ @NonNull FingerprintAuthenticateOptions options,
int cookie,
boolean requireConfirmation,
- int sensorId,
@NonNull BiometricLogger biometricLogger,
@NonNull BiometricContext biometricContext,
boolean isStrongBiometric,
@@ -122,13 +122,11 @@
lazyDaemon,
token,
listener,
- targetUserId,
operationId,
restricted,
- owner,
+ options,
cookie,
requireConfirmation,
- sensorId,
biometricLogger,
biometricContext,
isStrongBiometric,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index f6911ea..376d231 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.hardware.biometrics.BiometricOverlayConstants;
import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.IBinder;
@@ -52,13 +53,14 @@
FingerprintDetectClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
@NonNull IBinder token, long requestId,
- @NonNull ClientMonitorCallbackConverter listener, int userId,
- @NonNull String owner, int sensorId,
+ @NonNull ClientMonitorCallbackConverter listener,
+ @NonNull FingerprintAuthenticateOptions options,
@NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
@Nullable IUdfpsOverlayController udfpsOverlayController,
@Nullable IUdfpsOverlay udfpsOverlay,
boolean isStrongBiometric) {
- super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
+ super(context, lazyDaemon, token, listener, options.getUserId(),
+ options.getOpPackageName(), 0 /* cookie */, options.getSensorId(),
true /* shouldVibrate */, biometricLogger, biometricContext);
setRequestId(requestId);
mIsStrongBiometric = isStrongBiometric;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 776d331..a833278 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -37,6 +37,7 @@
import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.biometrics.fingerprint.SensorProps;
import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
@@ -422,15 +423,17 @@
}
@Override
- public long scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
- @NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,
+ public long scheduleFingerDetect(@NonNull IBinder token,
+ @NonNull ClientMonitorCallbackConverter callback,
+ @NonNull FingerprintAuthenticateOptions options,
int statsClient) {
final long id = mRequestCounter.incrementAndGet();
mHandler.post(() -> {
+ final int sensorId = options.getSensorId();
final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
- mSensors.get(sensorId).getLazySession(), token, id, callback, userId,
- opPackageName, sensorId,
+ mSensors.get(sensorId).getLazySession(), token, id, callback,
+ options,
createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
mBiometricContext,
mUdfpsOverlayController, mUdfpsOverlay, isStrongBiometric);
@@ -441,16 +444,19 @@
}
@Override
- public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
- int userId, int cookie, @NonNull ClientMonitorCallbackConverter callback,
- @NonNull String opPackageName, long requestId, boolean restricted, int statsClient,
+ public void scheduleAuthenticate(@NonNull IBinder token, long operationId,
+ int cookie, @NonNull ClientMonitorCallbackConverter callback,
+ @NonNull FingerprintAuthenticateOptions options,
+ long requestId, boolean restricted, int statsClient,
boolean allowBackgroundAuthentication) {
mHandler.post(() -> {
+ final int userId = options.getUserId();
+ final int sensorId = options.getSensorId();
final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
mContext, mSensors.get(sensorId).getLazySession(), token, requestId, callback,
- userId, operationId, restricted, opPackageName, cookie,
- false /* requireConfirmation */, sensorId,
+ operationId, restricted, options, cookie,
+ false /* requireConfirmation */,
createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
mBiometricContext, isStrongBiometric,
mTaskStackListener, mSensors.get(sensorId).getLockoutCache(),
@@ -485,14 +491,14 @@
}
@Override
- public long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
- int userId, int cookie, @NonNull ClientMonitorCallbackConverter callback,
- @NonNull String opPackageName, boolean restricted, int statsClient,
+ public long scheduleAuthenticate(@NonNull IBinder token, long operationId,
+ int cookie, @NonNull ClientMonitorCallbackConverter callback,
+ @NonNull FingerprintAuthenticateOptions options, boolean restricted, int statsClient,
boolean allowBackgroundAuthentication) {
final long id = mRequestCounter.incrementAndGet();
- scheduleAuthenticate(sensorId, token, operationId, userId, cookie, callback,
- opPackageName, id, restricted, statsClient, allowBackgroundAuthentication);
+ scheduleAuthenticate(token, operationId, cookie, callback,
+ options, id, restricted, statsClient, allowBackgroundAuthentication);
return id;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index 4567addc..99c491a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -34,6 +34,7 @@
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprintClientCallback;
import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -631,17 +632,17 @@
}
@Override
- public long scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
- @NonNull ClientMonitorCallbackConverter listener, @NonNull String opPackageName,
+ public long scheduleFingerDetect(@NonNull IBinder token,
+ @NonNull ClientMonitorCallbackConverter listener,
+ @NonNull FingerprintAuthenticateOptions options,
int statsClient) {
final long id = mRequestCounter.incrementAndGet();
mHandler.post(() -> {
- scheduleUpdateActiveUserWithoutHandler(userId);
+ scheduleUpdateActiveUserWithoutHandler(options.getUserId());
final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId);
final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
- mLazyDaemon, token, id, listener, userId, opPackageName,
- mSensorProperties.sensorId,
+ mLazyDaemon, token, id, listener, options,
createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
mBiometricContext, mUdfpsOverlayController, mUdfpsOverlay,
isStrongBiometric);
@@ -652,18 +653,18 @@
}
@Override
- public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
- int userId, int cookie, @NonNull ClientMonitorCallbackConverter listener,
- @NonNull String opPackageName, long requestId, boolean restricted, int statsClient,
+ public void scheduleAuthenticate(@NonNull IBinder token, long operationId,
+ int cookie, @NonNull ClientMonitorCallbackConverter listener,
+ @NonNull FingerprintAuthenticateOptions options,
+ long requestId, boolean restricted, int statsClient,
boolean allowBackgroundAuthentication) {
mHandler.post(() -> {
- scheduleUpdateActiveUserWithoutHandler(userId);
+ scheduleUpdateActiveUserWithoutHandler(options.getUserId());
final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId);
final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
- mContext, mLazyDaemon, token, requestId, listener, userId, operationId,
- restricted, opPackageName, cookie, false /* requireConfirmation */,
- mSensorProperties.sensorId,
+ mContext, mLazyDaemon, token, requestId, listener, operationId,
+ restricted, options, cookie, false /* requireConfirmation */,
createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
mBiometricContext, isStrongBiometric,
mTaskStackListener, mLockoutTracker,
@@ -675,14 +676,14 @@
}
@Override
- public long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
- int userId, int cookie, @NonNull ClientMonitorCallbackConverter listener,
- @NonNull String opPackageName, boolean restricted, int statsClient,
+ public long scheduleAuthenticate(@NonNull IBinder token, long operationId,
+ int cookie, @NonNull ClientMonitorCallbackConverter listener,
+ @NonNull FingerprintAuthenticateOptions options, boolean restricted, int statsClient,
boolean allowBackgroundAuthentication) {
final long id = mRequestCounter.incrementAndGet();
- scheduleAuthenticate(sensorId, token, operationId, userId, cookie, listener,
- opPackageName, id, restricted, statsClient, allowBackgroundAuthentication);
+ scheduleAuthenticate(token, operationId, cookie, listener,
+ options, id, restricted, statsClient, allowBackgroundAuthentication);
return id;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
index 73b1288..0a47c12 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
@@ -22,6 +22,7 @@
import android.content.ContentResolver;
import android.content.Context;
import android.hardware.biometrics.fingerprint.PointerContext;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
@@ -362,13 +363,16 @@
// Store the authClient parameters so it can be rescheduled
final IBinder token = client.getToken();
final long operationId = authClient.getOperationId();
- final int user = client.getTargetUserId();
final int cookie = client.getCookie();
final ClientMonitorCallbackConverter listener = client.getListener();
- final String opPackageName = client.getOwnerString();
final boolean restricted = authClient.isRestricted();
final int statsClient = client.getLogger().getStatsClient();
final boolean isKeyguard = authClient.isKeyguard();
+ final FingerprintAuthenticateOptions options =
+ new FingerprintAuthenticateOptions.Builder()
+ .setUserId(client.getTargetUserId())
+ .setOpPackageName(client.getOwnerString())
+ .build();
// Don't actually send cancel() to the HAL, since successful auth already finishes
// HAL authenticate() lifecycle. Just
@@ -376,8 +380,8 @@
// Schedule this only after we invoke onClientFinished for the previous client, so that
// internal preemption logic is not run.
- mFingerprint21.scheduleAuthenticate(mFingerprint21.mSensorProperties.sensorId, token,
- operationId, user, cookie, listener, opPackageName, restricted, statsClient,
+ mFingerprint21.scheduleAuthenticate(token,
+ operationId, cookie, listener, options, restricted, statsClient,
isKeyguard);
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 957005a..d22aef8 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -26,6 +26,7 @@
import android.hardware.biometrics.BiometricManager.Authenticators;
import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
import android.hardware.fingerprint.IUdfpsOverlay;
@@ -57,7 +58,8 @@
* {@link android.hardware.biometrics.fingerprint.V2_1} and
* {@link android.hardware.biometrics.fingerprint.V2_2} HIDL interfaces.
*/
-class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFingerprint>
+class FingerprintAuthenticationClient
+ extends AuthenticationClient<IBiometricsFingerprint, FingerprintAuthenticateOptions>
implements Udfps {
private static final String TAG = "Biometrics/FingerprintAuthClient";
@@ -72,9 +74,9 @@
FingerprintAuthenticationClient(@NonNull Context context,
@NonNull Supplier<IBiometricsFingerprint> lazyDaemon,
@NonNull IBinder token, long requestId,
- @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
- boolean restricted, @NonNull String owner, int cookie, boolean requireConfirmation,
- int sensorId, @NonNull BiometricLogger logger,
+ @NonNull ClientMonitorCallbackConverter listener, long operationId,
+ boolean restricted, @NonNull FingerprintAuthenticateOptions options,
+ int cookie, boolean requireConfirmation, @NonNull BiometricLogger logger,
@NonNull BiometricContext biometricContext, boolean isStrongBiometric,
@NonNull TaskStackListener taskStackListener,
@NonNull LockoutFrameworkImpl lockoutTracker,
@@ -84,8 +86,8 @@
boolean allowBackgroundAuthentication,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
@Authenticators.Types int sensorStrength) {
- super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
- owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
+ super(context, lazyDaemon, token, listener, operationId, restricted,
+ options, cookie, requireConfirmation, logger, biometricContext,
isStrongBiometric, taskStackListener, lockoutTracker, allowBackgroundAuthentication,
false /* shouldVibrate */, sensorStrength);
setRequestId(requestId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
index cfa9fb4..362c820 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
@@ -24,6 +24,7 @@
import android.hardware.biometrics.BiometricOverlayConstants;
import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.IBinder;
@@ -61,12 +62,13 @@
public FingerprintDetectClient(@NonNull Context context,
@NonNull Supplier<IBiometricsFingerprint> lazyDaemon,
@NonNull IBinder token, long requestId,
- @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
- int sensorId,
+ @NonNull ClientMonitorCallbackConverter listener,
+ @NonNull FingerprintAuthenticateOptions options,
@NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
@Nullable IUdfpsOverlayController udfpsOverlayController,
@Nullable IUdfpsOverlay udfpsOverlay, boolean isStrongBiometric) {
- super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
+ super(context, lazyDaemon, token, listener, options.getUserId(),
+ options.getOpPackageName(), 0 /* cookie */, options.getSensorId(),
true /* shouldVibrate */, biometricLogger, biometricContext);
setRequestId(requestId);
mSensorOverlays = new SensorOverlays(udfpsOverlayController,
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index f7183e6..dd92ffc 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -484,6 +484,51 @@
sourcePackage);
}
+ @Override
+ public boolean areClipboardAccessNotificationsEnabledForUser(int userId) {
+ int result = getContext().checkCallingOrSelfPermission(
+ Manifest.permission.MANAGE_CLIPBOARD_ACCESS_NOTIFICATION);
+ if (result != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("areClipboardAccessNotificationsEnable requires "
+ + "permission MANAGE_CLIPBOARD_ACCESS_NOTIFICATION");
+ }
+
+ long callingId = Binder.clearCallingIdentity();
+ try {
+ return Settings.Secure.getIntForUser(getContext().getContentResolver(),
+ Settings.Secure.CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS,
+ getDefaultClipboardAccessNotificationsSetting(), userId) != 0;
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
+ }
+ }
+
+ @Override
+ public void setClipboardAccessNotificationsEnabledForUser(boolean enable, int userId) {
+ int result = getContext().checkCallingOrSelfPermission(
+ Manifest.permission.MANAGE_CLIPBOARD_ACCESS_NOTIFICATION);
+ if (result != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("areClipboardAccessNotificationsEnable requires "
+ + "permission MANAGE_CLIPBOARD_ACCESS_NOTIFICATION");
+ }
+
+ long callingId = Binder.clearCallingIdentity();
+ try {
+ ContentResolver resolver = getContext()
+ .createContextAsUser(UserHandle.of(userId), 0).getContentResolver();
+ Settings.Secure.putInt(resolver,
+ Settings.Secure.CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS, (enable ? 1 : 0));
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
+ }
+ }
+
+ private int getDefaultClipboardAccessNotificationsSetting() {
+ return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_CLIPBOARD,
+ ClipboardManager.DEVICE_CONFIG_SHOW_ACCESS_NOTIFICATIONS,
+ ClipboardManager.DEVICE_CONFIG_DEFAULT_SHOW_ACCESS_NOTIFICATIONS) ? 1 : 0;
+ }
+
private void checkAndSetPrimaryClip(
ClipData clip,
String callingPackage,
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index ab2c002..1ce917c 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -3254,6 +3254,8 @@
}
mActiveNetwork = network;
+ mUnderlyingLinkProperties = null;
+ mUnderlyingNetworkCapabilities = null;
mRetryCount = 0;
startOrMigrateIkeSession(network);
diff --git a/services/core/java/com/android/server/cpu/CpuMonitorService.java b/services/core/java/com/android/server/cpu/CpuMonitorService.java
index b0dfb84..4eefe5c 100644
--- a/services/core/java/com/android/server/cpu/CpuMonitorService.java
+++ b/services/core/java/com/android/server/cpu/CpuMonitorService.java
@@ -38,7 +38,7 @@
/** Service to monitor CPU availability and usage. */
public final class CpuMonitorService extends SystemService {
static final String TAG = CpuMonitorService.class.getSimpleName();
- static final boolean DEBUG = Slogf.isLoggable(TAG, Log.DEBUG);
+ static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
// TODO(b/242722241): Make this a resource overlay property.
// Maintain 3 monitoring intervals:
// * One to poll very frequently when mCpuAvailabilityCallbackInfoByCallbacks are available and
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index d4877eb..9917bfc 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1886,10 +1886,6 @@
? BrightnessEvent.FLAG_USER_SET : 0));
Slog.i(mTag, newEvent.toString(/* includeTime= */ false));
- // Log all events which are not temporary
- if (newEvent.getReason().getReason() != BrightnessReason.REASON_TEMPORARY) {
- logBrightnessEvent(newEvent, unthrottledBrightnessState);
- }
if (userSetBrightnessChanged) {
logManualBrightnessEvent(newEvent);
}
@@ -3002,154 +2998,6 @@
}
}
- // Return bucket index of range_[left]_[right] where
- // left <= nits < right
- private int nitsToRangeIndex(float nits) {
- float[] boundaries = {
- 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80,
- 90, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1200,
- 1400, 1600, 1800, 2000, 2250, 2500, 2750, 3000};
- int[] rangeIndex = {
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_UNKNOWN,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_0_1,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_1_2,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_2_3,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_3_4,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_4_5,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_5_6,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_6_7,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_7_8,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_8_9,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_9_10,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_10_20,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_20_30,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_30_40,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_40_50,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_50_60,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_60_70,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_70_80,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_80_90,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_90_100,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_100_200,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_200_300,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_300_400,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_400_500,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_500_600,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_600_700,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_700_800,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_800_900,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_900_1000,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_1000_1200,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_1200_1400,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_1400_1600,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_1600_1800,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_1800_2000,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_2000_2250,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_2250_2500,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_2500_2750,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_2750_3000,
- };
- for (int i = 0; i < boundaries.length; i++) {
- if (nits < boundaries[i]) {
- return rangeIndex[i];
- }
- }
- return FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_3000_INF;
- }
-
- private int convertBrightnessReasonToStatsEnum(int brightnessReason) {
- switch(brightnessReason) {
- case BrightnessReason.REASON_UNKNOWN:
- return FrameworkStatsLog
- .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_UNKNOWN;
- case BrightnessReason.REASON_MANUAL:
- return FrameworkStatsLog
- .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_MANUAL;
- case BrightnessReason.REASON_DOZE:
- return FrameworkStatsLog
- .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_DOZE;
- case BrightnessReason.REASON_DOZE_DEFAULT:
- return FrameworkStatsLog
- .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_DOZE_DEFAULT;
- case BrightnessReason.REASON_AUTOMATIC:
- return FrameworkStatsLog
- .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_AUTOMATIC;
- case BrightnessReason.REASON_SCREEN_OFF:
- return FrameworkStatsLog
- .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_SCREEN_OFF;
- case BrightnessReason.REASON_OVERRIDE:
- return FrameworkStatsLog
- .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_OVERRIDE;
- case BrightnessReason.REASON_TEMPORARY:
- return FrameworkStatsLog
- .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_TEMPORARY;
- case BrightnessReason.REASON_BOOST:
- return FrameworkStatsLog
- .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_BOOST;
- case BrightnessReason.REASON_SCREEN_OFF_BRIGHTNESS_SENSOR:
- return FrameworkStatsLog
- .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_SCREEN_OFF_BRIGHTNESS_SENSOR;
- case BrightnessReason.REASON_FOLLOWER:
- return FrameworkStatsLog
- .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_FOLLOWER;
- }
- return FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_UNKNOWN;
- }
-
- private int convertHbmModeToStatsEnum(int mode) {
- switch(mode) {
- case BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF:
- return FrameworkStatsLog
- .SCREEN_BRIGHTNESS_CHANGED_V2__HBM_MODE__HIGH_BRIGHTNESS_MODE_OFF;
- case BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT:
- return FrameworkStatsLog
- .SCREEN_BRIGHTNESS_CHANGED_V2__HBM_MODE__HIGH_BRIGHTNESS_MODE_SUNLIGHT;
- case BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR:
- return FrameworkStatsLog
- .SCREEN_BRIGHTNESS_CHANGED_V2__HBM_MODE__HIGH_BRIGHTNESS_MODE_HDR;
- }
- return FrameworkStatsLog
- .SCREEN_BRIGHTNESS_CHANGED_V2__HBM_MODE__HIGH_BRIGHTNESS_MODE_OFF;
- }
-
- // unmodifiedBrightness: the brightness value that has not been
- // modified by any modifiers(dimming/throttling/low-power-mode)
- private void logBrightnessEvent(BrightnessEvent event, float unmodifiedBrightness) {
- int modifier = event.getReason().getModifier();
- // It's easier to check if the brightness is at maximum level using the brightness
- // value untouched by any modifiers
- boolean brightnessIsMax = unmodifiedBrightness == event.getHbmMax();
- float brightnessInNits = convertToNits(event.getBrightness());
- float lowPowerModeFactor = event.getPowerFactor();
- int rbcStrength = event.getRbcStrength();
- float hbmMaxNits = convertToNits(event.getHbmMax());
- float thermalCapNits = convertToNits(event.getThermalMax());
-
- if (mLogicalDisplay.getPrimaryDisplayDeviceLocked() != null
- && mLogicalDisplay.getPrimaryDisplayDeviceLocked()
- .getDisplayDeviceInfoLocked().type == Display.TYPE_INTERNAL) {
- FrameworkStatsLog.write(FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2,
- event.getPhysicalDisplayId().hashCode(),
- brightnessInNits,
- convertToNits(unmodifiedBrightness),
- nitsToRangeIndex(brightnessInNits),
- brightnessIsMax,
- (event.getFlags() & BrightnessEvent.FLAG_USER_SET) > 0,
- convertBrightnessReasonToStatsEnum(event.getReason().getReason()),
- convertHbmModeToStatsEnum(event.getHbmMode()),
- (modifier & BrightnessReason.MODIFIER_LOW_POWER) > 0,
- (modifier & BrightnessReason.MODIFIER_THROTTLED) > 0,
- event.isRbcEnabled(),
- event.getLux(),
- event.wasShortTermModelActive(),
- lowPowerModeFactor,
- rbcStrength,
- hbmMaxNits,
- thermalCapNits,
- event.isAutomaticBrightnessEnabled());
- }
- }
-
private void logManualBrightnessEvent(BrightnessEvent event) {
float appliedLowPowerMode = event.isLowPowerModeSet() ? event.getPowerFactor() : -1f;
int appliedRbcStrength = event.isRbcEnabled() ? event.getRbcStrength() : -1;
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index a928777..f49419cf 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -1148,6 +1148,7 @@
final int previousPolicy;
boolean mustInitialize = false;
int brightnessAdjustmentFlags = 0;
+ mBrightnessReasonTemp.set(null);
mTempBrightnessEvent.reset();
SparseArray<DisplayPowerControllerInterface> displayBrightnessFollowers;
synchronized (mLock) {
@@ -1601,10 +1602,6 @@
? BrightnessEvent.FLAG_USER_SET : 0));
Slog.i(mTag, newEvent.toString(/* includeTime= */ false));
- // Log all events which are not temporary
- if (newEvent.getReason().getReason() != BrightnessReason.REASON_TEMPORARY) {
- logBrightnessEvent(newEvent, unthrottledBrightnessState);
- }
if (userSetBrightnessChanged) {
logManualBrightnessEvent(newEvent);
}
@@ -2541,154 +2538,6 @@
return mDisplayId == Display.DEFAULT_DISPLAY || mBootCompleted;
}
- // Return bucket index of range_[left]_[right] where
- // left <= nits < right
- private int nitsToRangeIndex(float nits) {
- float[] boundaries = {
- 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80,
- 90, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1200,
- 1400, 1600, 1800, 2000, 2250, 2500, 2750, 3000};
- int[] rangeIndex = {
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_UNKNOWN,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_0_1,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_1_2,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_2_3,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_3_4,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_4_5,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_5_6,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_6_7,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_7_8,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_8_9,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_9_10,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_10_20,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_20_30,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_30_40,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_40_50,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_50_60,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_60_70,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_70_80,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_80_90,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_90_100,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_100_200,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_200_300,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_300_400,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_400_500,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_500_600,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_600_700,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_700_800,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_800_900,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_900_1000,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_1000_1200,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_1200_1400,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_1400_1600,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_1600_1800,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_1800_2000,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_2000_2250,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_2250_2500,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_2500_2750,
- FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_2750_3000,
- };
- for (int i = 0; i < boundaries.length; i++) {
- if (nits < boundaries[i]) {
- return rangeIndex[i];
- }
- }
- return FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__BUCKET_INDEX__RANGE_3000_INF;
- }
-
- private int convertBrightnessReasonToStatsEnum(int brightnessReason) {
- switch(brightnessReason) {
- case BrightnessReason.REASON_UNKNOWN:
- return FrameworkStatsLog
- .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_UNKNOWN;
- case BrightnessReason.REASON_MANUAL:
- return FrameworkStatsLog
- .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_MANUAL;
- case BrightnessReason.REASON_DOZE:
- return FrameworkStatsLog
- .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_DOZE;
- case BrightnessReason.REASON_DOZE_DEFAULT:
- return FrameworkStatsLog
- .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_DOZE_DEFAULT;
- case BrightnessReason.REASON_AUTOMATIC:
- return FrameworkStatsLog
- .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_AUTOMATIC;
- case BrightnessReason.REASON_SCREEN_OFF:
- return FrameworkStatsLog
- .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_SCREEN_OFF;
- case BrightnessReason.REASON_OVERRIDE:
- return FrameworkStatsLog
- .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_OVERRIDE;
- case BrightnessReason.REASON_TEMPORARY:
- return FrameworkStatsLog
- .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_TEMPORARY;
- case BrightnessReason.REASON_BOOST:
- return FrameworkStatsLog
- .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_BOOST;
- case BrightnessReason.REASON_SCREEN_OFF_BRIGHTNESS_SENSOR:
- return FrameworkStatsLog
- .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_SCREEN_OFF_BRIGHTNESS_SENSOR;
- case BrightnessReason.REASON_FOLLOWER:
- return FrameworkStatsLog
- .SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_FOLLOWER;
- }
- return FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2__REASON__REASON_UNKNOWN;
- }
-
- private int convertHbmModeToStatsEnum(int mode) {
- switch(mode) {
- case BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF:
- return FrameworkStatsLog
- .SCREEN_BRIGHTNESS_CHANGED_V2__HBM_MODE__HIGH_BRIGHTNESS_MODE_OFF;
- case BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT:
- return FrameworkStatsLog
- .SCREEN_BRIGHTNESS_CHANGED_V2__HBM_MODE__HIGH_BRIGHTNESS_MODE_SUNLIGHT;
- case BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR:
- return FrameworkStatsLog
- .SCREEN_BRIGHTNESS_CHANGED_V2__HBM_MODE__HIGH_BRIGHTNESS_MODE_HDR;
- }
- return FrameworkStatsLog
- .SCREEN_BRIGHTNESS_CHANGED_V2__HBM_MODE__HIGH_BRIGHTNESS_MODE_OFF;
- }
-
- // unmodifiedBrightness: the brightness value that has not been
- // modified by any modifiers(dimming/throttling/low-power-mode)
- private void logBrightnessEvent(BrightnessEvent event, float unmodifiedBrightness) {
- int modifier = event.getReason().getModifier();
- // It's easier to check if the brightness is at maximum level using the brightness
- // value untouched by any modifiers
- boolean brightnessIsMax = unmodifiedBrightness == event.getHbmMax();
- float brightnessInNits = convertToNits(event.getBrightness());
- float lowPowerModeFactor = event.getPowerFactor();
- int rbcStrength = event.getRbcStrength();
- float hbmMaxNits = convertToNits(event.getHbmMax());
- float thermalCapNits = convertToNits(event.getThermalMax());
-
- if (mLogicalDisplay.getPrimaryDisplayDeviceLocked() != null
- && mLogicalDisplay.getPrimaryDisplayDeviceLocked()
- .getDisplayDeviceInfoLocked().type == Display.TYPE_INTERNAL) {
- FrameworkStatsLog.write(FrameworkStatsLog.SCREEN_BRIGHTNESS_CHANGED_V2,
- event.getPhysicalDisplayId().hashCode(),
- brightnessInNits,
- convertToNits(unmodifiedBrightness),
- nitsToRangeIndex(brightnessInNits),
- brightnessIsMax,
- (event.getFlags() & BrightnessEvent.FLAG_USER_SET) > 0,
- convertBrightnessReasonToStatsEnum(event.getReason().getReason()),
- convertHbmModeToStatsEnum(event.getHbmMode()),
- (modifier & BrightnessReason.MODIFIER_LOW_POWER) > 0,
- (modifier & BrightnessReason.MODIFIER_THROTTLED) > 0,
- event.isRbcEnabled(),
- event.getLux(),
- event.wasShortTermModelActive(),
- lowPowerModeFactor,
- rbcStrength,
- hbmMaxNits,
- thermalCapNits,
- event.isAutomaticBrightnessEnabled());
- }
- }
-
private final class DisplayControllerHandler extends Handler {
DisplayControllerHandler(Looper looper) {
super(looper, null, true /*async*/);
diff --git a/services/core/java/com/android/server/display/OWNERS b/services/core/java/com/android/server/display/OWNERS
index 8e34601..fcaa957 100644
--- a/services/core/java/com/android/server/display/OWNERS
+++ b/services/core/java/com/android/server/display/OWNERS
@@ -4,5 +4,7 @@
ogunwale@google.com
santoscordon@google.com
flc@google.com
+wilczynskip@google.com
+brup@google.com
per-file ColorDisplayService.java=christyfranks@google.com
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
index 8b09571..aff80de 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
@@ -100,7 +100,7 @@
* A utility to reset the BrightnessEvent to default values
*/
public void reset() {
- mReason.set(null);
+ mReason = new BrightnessReason();
mTime = SystemClock.uptimeMillis();
mPhysicalDisplayId = "";
// Lux values
diff --git a/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java b/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java
index c411319..1153cc3 100644
--- a/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java
+++ b/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.hardware.health.BatteryHealthData;
import android.hardware.health.HealthInfo;
import android.hardware.health.IHealth;
import android.os.BatteryManager;
@@ -113,6 +114,7 @@
private int getPropertyInternal(int id, BatteryProperty prop) throws RemoteException {
IHealth service = mLastService.get();
if (service == null) throw new RemoteException("no health service");
+ BatteryHealthData healthData;
try {
switch (id) {
case BatteryManager.BATTERY_PROPERTY_CHARGE_COUNTER:
@@ -133,6 +135,21 @@
case BatteryManager.BATTERY_PROPERTY_ENERGY_COUNTER:
prop.setLong(service.getEnergyCounterNwh());
break;
+ case BatteryManager.BATTERY_PROPERTY_MANUFACTURING_DATE:
+ healthData = service.getBatteryHealthData();
+ prop.setLong(healthData.batteryManufacturingDateSeconds);
+ break;
+ case BatteryManager.BATTERY_PROPERTY_FIRST_USAGE_DATE:
+ healthData = service.getBatteryHealthData();
+ prop.setLong(healthData.batteryFirstUsageSeconds);
+ break;
+ case BatteryManager.BATTERY_PROPERTY_CHARGING_POLICY:
+ prop.setLong(service.getChargingPolicy());
+ break;
+ case BatteryManager.BATTERY_PROPERTY_STATE_OF_HEALTH:
+ healthData = service.getBatteryHealthData();
+ prop.setLong(healthData.batteryStateOfHealth);
+ break;
}
} catch (UnsupportedOperationException e) {
// Leave prop untouched.
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 9e8b9f1..e0253fc 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -343,9 +343,9 @@
private void visitAllKeyboardLayouts(KeyboardLayoutVisitor visitor) {
final PackageManager pm = mContext.getPackageManager();
Intent intent = new Intent(InputManager.ACTION_QUERY_KEYBOARD_LAYOUTS);
- for (ResolveInfo resolveInfo : pm.queryBroadcastReceivers(intent,
+ for (ResolveInfo resolveInfo : pm.queryBroadcastReceiversAsUser(intent,
PackageManager.GET_META_DATA | PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE)) {
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, UserHandle.USER_SYSTEM)) {
final ActivityInfo activityInfo = resolveInfo.activityInfo;
final int priority = resolveInfo.priority;
visitKeyboardLayoutsInPackage(pm, activityInfo, null, priority, visitor);
@@ -464,7 +464,7 @@
return LocaleList.forLanguageTags(languageTags.replace('|', ','));
}
- private static String getLayoutDescriptor(@NonNull InputDeviceIdentifier identifier) {
+ private String getLayoutDescriptor(@NonNull InputDeviceIdentifier identifier) {
Objects.requireNonNull(identifier, "identifier must not be null");
Objects.requireNonNull(identifier.getDescriptor(), "descriptor must not be null");
@@ -474,7 +474,21 @@
// If vendor id and product id is available, use it as keys. This allows us to have the
// same setup for all keyboards with same product and vendor id. i.e. User can swap 2
// identical keyboards and still get the same setup.
- return "vendor:" + identifier.getVendorId() + ",product:" + identifier.getProductId();
+ StringBuilder key = new StringBuilder();
+ key.append("vendor:").append(identifier.getVendorId()).append(",product:").append(
+ identifier.getProductId());
+
+ InputDevice inputDevice = getInputDevice(identifier);
+ Objects.requireNonNull(inputDevice, "Input device must not be null");
+ // Some keyboards can have same product ID and vendor ID but different Keyboard info like
+ // language tag and layout type.
+ if (!TextUtils.isEmpty(inputDevice.getKeyboardLanguageTag())) {
+ key.append(",languageTag:").append(inputDevice.getKeyboardLanguageTag());
+ }
+ if (!TextUtils.isEmpty(inputDevice.getKeyboardLayoutType())) {
+ key.append(",layoutType:").append(inputDevice.getKeyboardLanguageTag());
+ }
+ return key.toString();
}
@Nullable
@@ -1075,7 +1089,7 @@
identifier.getDescriptor()) : null;
}
- private static String createLayoutKey(InputDeviceIdentifier identifier, int userId,
+ private String createLayoutKey(InputDeviceIdentifier identifier, int userId,
@NonNull InputMethodSubtypeHandle subtypeHandle) {
Objects.requireNonNull(subtypeHandle, "subtypeHandle must not be null");
return "layoutDescriptor:" + getLayoutDescriptor(identifier) + ",userId:" + userId
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
index 1c7294f..eb4dba6 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
@@ -20,12 +20,14 @@
import android.Manifest;
import android.annotation.AnyThread;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.UiThread;
import android.hardware.input.InputManager;
import android.os.IBinder;
import android.os.Looper;
+import android.text.TextUtils;
import android.util.Slog;
import android.view.BatchedInputEventReceiver;
import android.view.Choreographer;
@@ -35,6 +37,8 @@
import android.view.InputEventReceiver;
import android.view.MotionEvent;
import android.view.SurfaceControl;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
import com.android.server.LocalServices;
import com.android.server.input.InputManagerInternal;
@@ -52,6 +56,9 @@
// TODO(b/210039666): flip the flag.
static final boolean DEBUG = true;
private static final int EVENT_BUFFER_SIZE = 100;
+ // A longer event buffer used for handwriting delegation
+ // TODO(b/210039666): make this device touch sampling rate dependent.
+ private static final int LONG_EVENT_BUFFER = EVENT_BUFFER_SIZE * 20;
// This must be the looper for the UiThread.
private final Looper mLooper;
@@ -63,6 +70,9 @@
private Runnable mInkWindowInitRunnable;
private boolean mRecordingGesture;
private int mCurrentDisplayId;
+ // when set, package names are used for handwriting delegation.
+ private @Nullable String mDelegatePackageName;
+ private @Nullable String mDelegatorPackageName;
private HandwritingEventReceiverSurface mHandwritingSurface;
@@ -137,6 +147,41 @@
return mRecordingGesture;
}
+ boolean hasOngoingStylusHandwritingSession() {
+ return mHandwritingSurface != null && mHandwritingSurface.isIntercepting();
+ }
+
+ /**
+ * Prepare delegation of stylus handwriting to a different editor
+ * @see InputMethodManager#prepareStylusHandwritingDelegation(View, String)
+ */
+ void prepareStylusHandwritingDelegation(
+ @NonNull String delegatePackageName, @NonNull String delegatorPackageName) {
+ mDelegatePackageName = delegatePackageName;
+ mDelegatorPackageName = delegatorPackageName;
+ ((ArrayList) mHandwritingBuffer).ensureCapacity(LONG_EVENT_BUFFER);
+ // TODO(b/210039666): cancel delegation after a timeout or next input method client binding.
+ }
+
+ @Nullable String getDelegatePackageName() {
+ return mDelegatePackageName;
+ }
+
+ @Nullable String getDelegatorPackageName() {
+ return mDelegatorPackageName;
+ }
+
+ /**
+ * Clear any pending handwriting delegation info.
+ */
+ void clearPendingHandwritingDelegation() {
+ if (DEBUG) {
+ Slog.d(TAG, "clearPendingHandwritingDelegation");
+ }
+ mDelegatorPackageName = null;
+ mDelegatePackageName = null;
+ }
+
/**
* Starts a {@link HandwritingSession} to transfer to the IME.
*
@@ -223,6 +268,7 @@
}
}
+ clearPendingHandwritingDelegation();
mRecordingGesture = false;
}
@@ -259,7 +305,10 @@
mInkWindowInitRunnable = null;
}
- if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+ // If handwriting delegation is ongoing, don't clear the buffer so that multiple strokes
+ // can be buffered across windows.
+ if (TextUtils.isEmpty(mDelegatePackageName)
+ && (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL)) {
mRecordingGesture = false;
mHandwritingBuffer.clear();
return;
diff --git a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
index da65f27..2efb0be 100644
--- a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
+++ b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
@@ -24,13 +24,14 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
+import android.util.Log;
import android.view.inputmethod.ImeTracker;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.IImeTracker;
import com.android.internal.inputmethod.InputMethodDebug;
import com.android.internal.inputmethod.SoftInputShowHideReason;
import com.android.internal.util.FrameworkStatsLog;
-import com.android.internal.view.IImeTracker;
import java.io.PrintWriter;
import java.time.Instant;
@@ -53,7 +54,7 @@
@SuppressWarnings("GuardedBy")
public final class ImeTrackerService extends IImeTracker.Stub {
- static final String TAG = "ImeTrackerService";
+ private static final String TAG = ImeTracker.TAG;
/** The threshold in milliseconds after which a history entry is considered timed out. */
private static final long TIMEOUT_MS = 10_000;
@@ -71,67 +72,71 @@
@NonNull
@Override
- public synchronized IBinder onRequestShow(int uid, @ImeTracker.Origin int origin,
- @SoftInputShowHideReason int reason) {
- final IBinder binder = new Binder();
- final History.Entry entry = new History.Entry(uid, ImeTracker.TYPE_SHOW,
- ImeTracker.STATUS_RUN, origin, reason);
+ public synchronized ImeTracker.Token onRequestShow(@NonNull String tag, int uid,
+ @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
+ final var binder = new Binder();
+ final var token = new ImeTracker.Token(binder, tag);
+ final var entry = new History.Entry(tag, uid, ImeTracker.TYPE_SHOW, ImeTracker.STATUS_RUN,
+ origin, reason);
mHistory.addEntry(binder, entry);
// Register a delayed task to handle the case where the new entry times out.
mHandler.postDelayed(() -> {
synchronized (ImeTrackerService.this) {
- mHistory.setFinished(binder, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET);
+ mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET);
}
}, TIMEOUT_MS);
- return binder;
+ return token;
}
@NonNull
@Override
- public synchronized IBinder onRequestHide(int uid, @ImeTracker.Origin int origin,
- @SoftInputShowHideReason int reason) {
- final IBinder binder = new Binder();
- final History.Entry entry = new History.Entry(uid, ImeTracker.TYPE_HIDE,
- ImeTracker.STATUS_RUN, origin, reason);
+ public synchronized ImeTracker.Token onRequestHide(@NonNull String tag, int uid,
+ @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
+ final var binder = new Binder();
+ final var token = new ImeTracker.Token(binder, tag);
+ final var entry = new History.Entry(tag, uid, ImeTracker.TYPE_HIDE, ImeTracker.STATUS_RUN,
+ origin, reason);
mHistory.addEntry(binder, entry);
// Register a delayed task to handle the case where the new entry times out.
mHandler.postDelayed(() -> {
synchronized (ImeTrackerService.this) {
- mHistory.setFinished(binder, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET);
+ mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET);
}
}, TIMEOUT_MS);
- return binder;
+ return token;
}
@Override
- public synchronized void onProgress(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
- final History.Entry entry = mHistory.getEntry(statsToken);
+ public synchronized void onProgress(@NonNull IBinder binder, @ImeTracker.Phase int phase) {
+ final var entry = mHistory.getEntry(binder);
if (entry == null) return;
entry.mPhase = phase;
}
@Override
- public synchronized void onFailed(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+ public synchronized void onFailed(@NonNull ImeTracker.Token statsToken,
+ @ImeTracker.Phase int phase) {
mHistory.setFinished(statsToken, ImeTracker.STATUS_FAIL, phase);
}
@Override
- public synchronized void onCancelled(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+ public synchronized void onCancelled(@NonNull ImeTracker.Token statsToken,
+ @ImeTracker.Phase int phase) {
mHistory.setFinished(statsToken, ImeTracker.STATUS_CANCEL, phase);
}
@Override
- public synchronized void onShown(@NonNull IBinder statsToken) {
+ public synchronized void onShown(@NonNull ImeTracker.Token statsToken) {
mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
}
@Override
- public synchronized void onHidden(@NonNull IBinder statsToken) {
+ public synchronized void onHidden(@NonNull ImeTracker.Token statsToken) {
mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
}
@@ -141,9 +146,9 @@
* @param statsToken the token corresponding to the current IME request.
* @param requestWindowName the name of the window that created the IME request.
*/
- public synchronized void onImmsUpdate(@NonNull IBinder statsToken,
+ public synchronized void onImmsUpdate(@NonNull ImeTracker.Token statsToken,
@NonNull String requestWindowName) {
- final History.Entry entry = mHistory.getEntry(statsToken);
+ final var entry = mHistory.getEntry(statsToken.getBinder());
if (entry == null) return;
entry.mRequestWindowName = requestWindowName;
@@ -181,17 +186,17 @@
/** Latest entry sequence number. */
private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
- /** Adds a live entry. */
+ /** Adds a live entry corresponding to the given IME tracking token's binder. */
@GuardedBy("ImeTrackerService.this")
- private void addEntry(@NonNull IBinder statsToken, @NonNull Entry entry) {
- mLiveEntries.put(statsToken, entry);
+ private void addEntry(@NonNull IBinder binder, @NonNull Entry entry) {
+ mLiveEntries.put(binder, entry);
}
- /** Gets the entry corresponding to the given IME tracking token, if it exists. */
+ /** Gets the entry corresponding to the given IME tracking token's binder, if it exists. */
@Nullable
@GuardedBy("ImeTrackerService.this")
- private Entry getEntry(@NonNull IBinder statsToken) {
- return mLiveEntries.get(statsToken);
+ private Entry getEntry(@NonNull IBinder binder) {
+ return mLiveEntries.get(binder);
}
/**
@@ -204,10 +209,21 @@
* (or {@link ImeTracker#PHASE_NOT_SET} otherwise).
*/
@GuardedBy("ImeTrackerService.this")
- private void setFinished(@NonNull IBinder statsToken, @ImeTracker.Status int status,
- @ImeTracker.Phase int phase) {
- final Entry entry = mLiveEntries.remove(statsToken);
- if (entry == null) return;
+ private void setFinished(@NonNull ImeTracker.Token statsToken,
+ @ImeTracker.Status int status, @ImeTracker.Phase int phase) {
+ final var entry = mLiveEntries.remove(statsToken.getBinder());
+ if (entry == null) {
+ // This will be unconditionally called through the postDelayed above to handle
+ // potential timeouts, and is thus intentionally dropped to avoid having to manually
+ // save and remove the registered callback. Only timeout calls are expected.
+ if (status != ImeTracker.STATUS_TIMEOUT) {
+ Log.i(TAG, statsToken.getTag()
+ + ": setFinished on previously finished token at "
+ + ImeTracker.Debug.phaseToString(phase) + " with "
+ + ImeTracker.Debug.statusToString(status));
+ }
+ return;
+ }
entry.mDuration = System.currentTimeMillis() - entry.mStartTime;
entry.mStatus = status;
@@ -216,6 +232,13 @@
entry.mPhase = phase;
}
+ if (status == ImeTracker.STATUS_TIMEOUT) {
+ // All events other than timeouts are already logged in the client-side ImeTracker.
+ Log.i(TAG, statsToken.getTag() + ": setFinished at "
+ + ImeTracker.Debug.phaseToString(entry.mPhase) + " with "
+ + ImeTracker.Debug.statusToString(status));
+ }
+
// Remove excess entries overflowing capacity (plus one for the new entry).
while (mEntries.size() >= CAPACITY) {
mEntries.remove();
@@ -232,21 +255,22 @@
/** Dumps the contents of the circular buffer. */
@GuardedBy("ImeTrackerService.this")
private void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
- final DateTimeFormatter formatter =
- DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
- .withZone(ZoneId.systemDefault());
+ final var formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
+ .withZone(ZoneId.systemDefault());
pw.print(prefix);
- pw.println("ImeTrackerService#History.mLiveEntries:");
+ pw.println("ImeTrackerService#History.mLiveEntries: "
+ + mLiveEntries.size() + " elements");
- for (final Entry entry: mLiveEntries.values()) {
+ for (final var entry: mLiveEntries.values()) {
dumpEntry(entry, pw, prefix, formatter);
}
pw.print(prefix);
- pw.println("ImeTrackerService#History.mEntries:");
+ pw.println("ImeTrackerService#History.mEntries: "
+ + mEntries.size() + " elements");
- for (final Entry entry: mEntries) {
+ for (final var entry: mEntries) {
dumpEntry(entry, pw, prefix, formatter);
}
}
@@ -255,34 +279,22 @@
private void dumpEntry(@NonNull Entry entry, @NonNull PrintWriter pw,
@NonNull String prefix, @NonNull DateTimeFormatter formatter) {
pw.print(prefix);
- pw.println("ImeTrackerService#History #" + entry.mSequenceNumber + ":");
+ pw.print(" #" + entry.mSequenceNumber);
+ pw.print(" " + ImeTracker.Debug.typeToString(entry.mType));
+ pw.print(" - " + ImeTracker.Debug.statusToString(entry.mStatus));
+ pw.print(" - " + entry.mTag);
+ pw.println(" (" + entry.mDuration + "ms):");
pw.print(prefix);
- pw.println(" startTime=" + formatter.format(Instant.ofEpochMilli(entry.mStartTime)));
+ pw.print(" startTime=" + formatter.format(Instant.ofEpochMilli(entry.mStartTime)));
+ pw.println(" " + ImeTracker.Debug.originToString(entry.mOrigin));
pw.print(prefix);
- pw.println(" duration=" + entry.mDuration + "ms");
+ pw.print(" reason=" + InputMethodDebug.softInputDisplayReasonToString(entry.mReason));
+ pw.println(" " + ImeTracker.Debug.phaseToString(entry.mPhase));
pw.print(prefix);
- pw.print(" type=" + ImeTracker.Debug.typeToString(entry.mType));
-
- pw.print(prefix);
- pw.print(" status=" + ImeTracker.Debug.statusToString(entry.mStatus));
-
- pw.print(prefix);
- pw.print(" origin="
- + ImeTracker.Debug.originToString(entry.mOrigin));
-
- pw.print(prefix);
- pw.print(" reason="
- + InputMethodDebug.softInputDisplayReasonToString(entry.mReason));
-
- pw.print(prefix);
- pw.print(" phase="
- + ImeTracker.Debug.phaseToString(entry.mPhase));
-
- pw.print(prefix);
- pw.print(" requestWindowName=" + entry.mRequestWindowName);
+ pw.println(" requestWindowName=" + entry.mRequestWindowName);
}
/** A history entry. */
@@ -291,6 +303,10 @@
/** The entry's sequence number in the history. */
private final int mSequenceNumber = sSequenceNumber.getAndIncrement();
+ /** Logging tag, of the shape "component:random_hexadecimal". */
+ @NonNull
+ private final String mTag;
+
/** Uid of the client that requested the IME. */
private final int mUid;
@@ -323,13 +339,15 @@
/**
* Name of the window that created the IME request.
*
- * Note: This is later set through {@link #onImmsUpdate(IBinder, String)}.
+ * Note: This is later set through {@link #onImmsUpdate}.
*/
@NonNull
private String mRequestWindowName = "not set";
- private Entry(int uid, @ImeTracker.Type int type, @ImeTracker.Status int status,
- @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
+ private Entry(@NonNull String tag, int uid, @ImeTracker.Type int type,
+ @ImeTracker.Status int status, @ImeTracker.Origin int origin,
+ @SoftInputShowHideReason int reason) {
+ mTag = tag;
mUid = uid;
mType = type;
mStatus = status;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index f5875ab..f142293 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -145,6 +145,7 @@
import com.android.internal.infra.AndroidFuture;
import com.android.internal.inputmethod.DirectBootAwareness;
import com.android.internal.inputmethod.IAccessibilityInputMethodSession;
+import com.android.internal.inputmethod.IImeTracker;
import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback;
import com.android.internal.inputmethod.IInputContentUriToken;
import com.android.internal.inputmethod.IInputMethod;
@@ -168,7 +169,6 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.DumpUtils;
-import com.android.internal.view.IImeTracker;
import com.android.internal.view.IInputMethodManager;
import com.android.server.AccessibilityManagerInternal;
import com.android.server.EventLogTags;
@@ -236,6 +236,8 @@
private static final int MSG_FINISH_HANDWRITING = 1110;
private static final int MSG_REMOVE_HANDWRITING_WINDOW = 1120;
+ private static final int MSG_PREPARE_HANDWRITING_DELEGATION = 1130;
+
private static final int MSG_SET_INTERACTIVE = 3030;
private static final int MSG_HARD_KEYBOARD_SWITCH_CHANGED = 4000;
@@ -530,6 +532,7 @@
/**
* The client that is currently bound to an input method.
*/
+ @Nullable
private ClientState mCurClient;
/**
@@ -555,11 +558,26 @@
int mCurFocusedWindowSoftInputMode;
/**
- * The client by which {@link #mCurFocusedWindow} was reported.
+ * The client by which {@link #mCurFocusedWindow} was reported. This gets updated whenever an
+ * IME-focusable window gained focus (without necessarily starting an input connection),
+ * while {@link #mCurClient} only gets updated when we actually start an input connection.
+ *
+ * @see #mCurFocusedWindow
*/
+ @Nullable
ClientState mCurFocusedWindowClient;
/**
+ * The editor info by which {@link #mCurFocusedWindow} was reported. This differs from
+ * {@link #mCurEditorInfo} the same way {@link #mCurFocusedWindowClient} differs
+ * from {@link #mCurClient}.
+ *
+ * @see #mCurFocusedWindow
+ */
+ @Nullable
+ EditorInfo mCurFocusedWindowEditorInfo;
+
+ /**
* The {@link IRemoteInputConnection} last provided by the current client.
*/
IRemoteInputConnection mCurInputConnection;
@@ -578,6 +596,7 @@
/**
* The {@link EditorInfo} last provided by the current client.
*/
+ @Nullable
EditorInfo mCurEditorInfo;
/**
@@ -2263,6 +2282,7 @@
}
if (mCurFocusedWindowClient == cs) {
mCurFocusedWindowClient = null;
+ mCurFocusedWindowEditorInfo = null;
}
}
}
@@ -2689,6 +2709,14 @@
}
@AnyThread
+ void schedulePrepareStylusHandwritingDelegation(
+ @NonNull String delegatePackageName, @NonNull String delegatorPackageName) {
+ mHandler.obtainMessage(
+ MSG_PREPARE_HANDWRITING_DELEGATION,
+ new Pair<>(delegatePackageName, delegatorPackageName)).sendToTarget();
+ }
+
+ @AnyThread
void scheduleRemoveStylusHandwritingWindow() {
mHandler.obtainMessage(MSG_REMOVE_HANDWRITING_WINDOW).sendToTarget();
}
@@ -3304,6 +3332,7 @@
"InputMethodManagerService#startStylusHandwriting");
int uid = Binder.getCallingUid();
synchronized (ImfLock.class) {
+ mHwController.clearPendingHandwritingDelegation();
if (!canInteractWithImeLocked(uid, client, "startStylusHandwriting",
null /* statsToken */)) {
return;
@@ -3331,6 +3360,13 @@
"There is no ongoing stylus gesture to start stylus handwriting.");
return;
}
+ if (mHwController.hasOngoingStylusHandwritingSession()) {
+ // prevent duplicate calls to startStylusHandwriting().
+ Slog.e(TAG,
+ "Stylus handwriting session is already ongoing."
+ + " Ignoring startStylusHandwriting().");
+ return;
+ }
if (DEBUG) Slog.v(TAG, "Client requesting Stylus Handwriting to be started");
final IInputMethodInvoker curMethod = getCurMethodLocked();
if (curMethod != null) {
@@ -3345,6 +3381,68 @@
}
}
+ @Override
+ public void prepareStylusHandwritingDelegation(
+ @NonNull IInputMethodClient client,
+ @NonNull String delegatePackageName,
+ @NonNull String delegatorPackageName) {
+ if (!verifyClientAndPackageMatch(client, delegatorPackageName)) {
+ Slog.w(TAG, "prepareStylusHandwritingDelegation() fail");
+ throw new IllegalArgumentException("Delegator doesn't match Uid");
+ }
+ schedulePrepareStylusHandwritingDelegation(delegatePackageName, delegatorPackageName);
+ }
+
+ @Override
+ public boolean acceptStylusHandwritingDelegation(
+ @NonNull IInputMethodClient client,
+ @NonNull String delegatePackageName,
+ @NonNull String delegatorPackageName) {
+ if (!verifyDelegator(client, delegatePackageName, delegatorPackageName)) {
+ return false;
+ }
+
+ startStylusHandwriting(client);
+ return true;
+ }
+
+ private boolean verifyClientAndPackageMatch(
+ @NonNull IInputMethodClient client, @NonNull String packageName) {
+ ClientState cs;
+ synchronized (ImfLock.class) {
+ cs = mClients.get(client.asBinder());
+ }
+ if (cs == null) {
+ throw new IllegalArgumentException("unknown client " + client.asBinder());
+ }
+ return InputMethodUtils.checkIfPackageBelongsToUid(
+ mPackageManagerInternal, cs.mUid, packageName);
+ }
+
+ private boolean verifyDelegator(
+ @NonNull IInputMethodClient client,
+ @NonNull String delegatePackageName,
+ @NonNull String delegatorPackageName) {
+ if (!verifyClientAndPackageMatch(client, delegatePackageName)) {
+ Slog.w(TAG, "Delegate package does not belong to the same user. Ignoring"
+ + " startStylusHandwriting");
+ return false;
+ }
+ synchronized (ImfLock.class) {
+ if (!delegatorPackageName.equals(mHwController.getDelegatorPackageName())) {
+ Slog.w(TAG,
+ "Delegator package does not match. Ignoring startStylusHandwriting");
+ return false;
+ }
+ if (!delegatePackageName.equals(mHwController.getDelegatePackageName())) {
+ Slog.w(TAG,
+ "Delegate package does not match. Ignoring startStylusHandwriting");
+ return false;
+ }
+ }
+ return true;
+ }
+
@BinderThread
@Override
public void reportPerceptibleAsync(IBinder windowToken, boolean perceptible) {
@@ -3373,10 +3471,7 @@
ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
// Create statsToken is none exists.
if (statsToken == null) {
- // TODO(b/261565259): to avoid using null, add package name in ClientState
- final String packageName = (mCurEditorInfo != null) ? mCurEditorInfo.packageName : null;
- final int uid = mCurClient != null ? mCurClient.mUid : -1;
- statsToken = ImeTracker.forLogging().onRequestShow(packageName, uid,
+ statsToken = createStatsTokenForFocusedClient(true /* show */,
ImeTracker.ORIGIN_SERVER_START_INPUT, reason);
}
@@ -3450,17 +3545,7 @@
int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
// Create statsToken is none exists.
if (statsToken == null) {
- // TODO(b/261565259): to avoid using null, add package name in ClientState
- final String packageName = (mCurEditorInfo != null) ? mCurEditorInfo.packageName : null;
- final int uid;
- if (mCurClient != null) {
- uid = mCurClient.mUid;
- } else if (mCurFocusedWindowClient != null) {
- uid = mCurFocusedWindowClient.mUid;
- } else {
- uid = -1;
- }
- statsToken = ImeTracker.forLogging().onRequestHide(packageName, uid,
+ statsToken = createStatsTokenForFocusedClient(false /* show */,
ImeTracker.ORIGIN_SERVER_HIDE_INPUT, reason);
}
@@ -3695,6 +3780,7 @@
mCurFocusedWindow = windowToken;
mCurFocusedWindowSoftInputMode = softInputMode;
mCurFocusedWindowClient = cs;
+ mCurFocusedWindowEditorInfo = editorInfo;
mCurPerceptible = true;
// We want to start input before showing the IME, but after closing
@@ -4623,13 +4709,13 @@
mWindowManagerInternal.onToggleImeRequested(
show, mCurFocusedWindow, requestToken, mCurTokenDisplayId);
mSoftInputShowHideHistory.addEntry(new SoftInputShowHideHistory.Entry(
- mCurFocusedWindowClient, mCurEditorInfo, info.focusedWindowName,
+ mCurFocusedWindowClient, mCurFocusedWindowEditorInfo, info.focusedWindowName,
mCurFocusedWindowSoftInputMode, reason, mInFullscreenMode,
info.requestWindowName, info.imeControlTargetName, info.imeLayerTargetName,
info.imeSurfaceParentName));
if (statsToken != null) {
- mImeTrackerService.onImmsUpdate(statsToken.mBinder, info.requestWindowName);
+ mImeTrackerService.onImmsUpdate(statsToken, info.requestWindowName);
}
}
@@ -4852,6 +4938,13 @@
}
return true;
}
+ case MSG_PREPARE_HANDWRITING_DELEGATION:
+ synchronized (ImfLock.class) {
+ String delegate = (String) ((Pair) msg.obj).first;
+ String delegator = (String) ((Pair) msg.obj).second;
+ mHwController.prepareStylusHandwritingDelegation(delegate, delegator);
+ }
+ return true;
case MSG_START_HANDWRITING:
synchronized (ImfLock.class) {
IInputMethodInvoker curMethod = getCurMethodLocked();
@@ -5664,9 +5757,11 @@
// We cannot simply distinguish a bad IME that reports an arbitrary package name from
// an unfortunate IME whose internal state is already obsolete due to the asynchronous
// nature of our system. Let's compare it with our internal record.
- if (!TextUtils.equals(mCurEditorInfo.packageName, packageName)) {
+ final var curPackageName = mCurEditorInfo != null
+ ? mCurEditorInfo.packageName : null;
+ if (!TextUtils.equals(curPackageName, packageName)) {
Slog.e(TAG, "Ignoring createInputContentUriToken mCurEditorInfo.packageName="
- + mCurEditorInfo.packageName + " packageName=" + packageName);
+ + curPackageName + " packageName=" + packageName);
return null;
}
// This user ID can never bee spoofed.
@@ -6427,6 +6522,30 @@
return mImeTrackerService;
}
+ /**
+ * Creates an IME request tracking token for the current focused client.
+ *
+ * @param show whether this is a show or a hide request.
+ * @param origin the origin of the IME request.
+ * @param reason the reason why the IME request was created.
+ */
+ @NonNull
+ private ImeTracker.Token createStatsTokenForFocusedClient(boolean show,
+ @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
+ final int uid = mCurFocusedWindowClient != null
+ ? mCurFocusedWindowClient.mUid
+ : -1;
+ final var packageName = mCurFocusedWindowEditorInfo != null
+ ? mCurFocusedWindowEditorInfo.packageName
+ : "uid(" + uid + ")";
+
+ if (show) {
+ return ImeTracker.forLogging().onRequestShow(packageName, uid, origin, reason);
+ } else {
+ return ImeTracker.forLogging().onRequestHide(packageName, uid, origin, reason);
+ }
+ }
+
private static final class InputMethodPrivilegedOperationsImpl
extends IInputMethodPrivilegedOperations.Stub {
private final InputMethodManagerService mImms;
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 8f65775..94f12dd 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -370,11 +370,6 @@
mLastRestartTimestampMap.put(contextHubId,
new AtomicLong(SystemClock.elapsedRealtimeNanos()));
- IContextHubClient client = mClientManager.registerClient(
- contextHubInfo, createDefaultClientCallback(contextHubId),
- /* attributionTag= */ null, mTransactionManager, mContext.getPackageName());
- defaultClientMap.put(contextHubId, client);
-
try {
mContextHubWrapper.registerCallback(contextHubId,
new ContextHubServiceCallback(contextHubId));
@@ -383,6 +378,11 @@
+ contextHubId + ")", e);
}
+ IContextHubClient client = mClientManager.registerClient(
+ contextHubInfo, createDefaultClientCallback(contextHubId),
+ /* attributionTag= */ null, mTransactionManager, mContext.getPackageName());
+ defaultClientMap.put(contextHubId, client);
+
// Do a query to initialize the service cache list of nanoapps
// TODO(b/194289715): Remove this when old API is deprecated
queryNanoAppsInternal(contextHubId);
@@ -1207,7 +1207,7 @@
pw.println("");
pw.println("=================== NANOAPPS ====================");
// Dump nanoAppHash
- mNanoAppStateManager.foreachNanoAppInstanceInfo((info) -> pw.println(info));
+ mNanoAppStateManager.foreachNanoAppInstanceInfo(pw::println);
pw.println("");
pw.println("=================== PRELOADED NANOAPPS ====================");
@@ -1255,16 +1255,17 @@
proto.flush();
}
- /**
- * Dumps preloaded nanoapps to the console
- */
+ /** Dumps preloaded nanoapps to the console */
private void dumpPreloadedNanoapps(PrintWriter pw) {
if (mContextHubWrapper == null) {
return;
}
long[] preloadedNanoappIds = mContextHubWrapper.getPreloadedNanoappIds();
- for (long preloadedNanoappId: preloadedNanoappIds) {
+ if (preloadedNanoappIds == null) {
+ return;
+ }
+ for (long preloadedNanoappId : preloadedNanoappIds) {
pw.print("ID: 0x");
pw.println(Long.toHexString(preloadedNanoappId));
}
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index dee26e38..e592a22 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -1245,8 +1245,13 @@
}
sid = sidFromPasswordHandle(pwd.passwordHandle);
}
- protectorSecret = transformUnderSecdiscardable(stretchedLskf,
- loadSecdiscardable(protectorId, userId));
+ byte[] secdiscardable = loadSecdiscardable(protectorId, userId);
+ if (secdiscardable == null) {
+ Slog.e(TAG, "secdiscardable file not found");
+ result.gkResponse = VerifyCredentialResponse.ERROR;
+ return result;
+ }
+ protectorSecret = transformUnderSecdiscardable(stretchedLskf, secdiscardable);
}
// Supplied credential passes first stage weaver/gatekeeper check so it should be correct.
// Notify the callback so the keyguard UI can proceed immediately.
@@ -1311,6 +1316,11 @@
byte[] token, int userId) {
AuthenticationResult result = new AuthenticationResult();
byte[] secdiscardable = loadSecdiscardable(protectorId, userId);
+ if (secdiscardable == null) {
+ Slog.e(TAG, "secdiscardable file not found");
+ result.gkResponse = VerifyCredentialResponse.ERROR;
+ return result;
+ }
int slotId = loadWeaverSlot(protectorId, userId);
if (slotId != INVALID_WEAVER_SLOT) {
if (!isWeaverAvailable()) {
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index c232b36..fac7748 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -678,4 +678,8 @@
@NonNull
Collection<SharedUserSetting> getAllSharedUsers();
+
+ boolean isChangeEnabled(long changeId, int uid);
+
+ boolean isChangeEnabled(long changeId, ApplicationInfo info);
}
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 5984360..21f661b 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -125,6 +125,7 @@
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.compat.PlatformCompat;
import com.android.server.pm.Installer.LegacyDexoptDisabledException;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.PackageDexUsage;
@@ -574,8 +575,7 @@
list = new ArrayList<>(1);
list.add(ri);
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
- mInjector.getCompatibility(), mComponentResolver,
- list, false, intent, resolvedType, filterCallingUid);
+ this, list, false, intent, resolvedType, filterCallingUid);
}
}
} else {
@@ -591,7 +591,7 @@
String callingPkgName = getInstantAppPackageName(filterCallingUid);
boolean isRequesterInstantApp = isInstantApp(callingPkgName, userId);
lockedResult.result = maybeAddInstantAppInstaller(
- lockedResult.result, intent, resolvedType, flags,
+ lockedResult.result, intent, resolvedType, flags, filterCallingUid,
userId, resolveForStart, isRequesterInstantApp);
}
if (lockedResult.sortResult) {
@@ -604,8 +604,7 @@
if (originalIntent != null) {
// We also have to ensure all components match the original intent
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
- mInjector.getCompatibility(), mComponentResolver,
- list, false, originalIntent, resolvedType, filterCallingUid);
+ this, list, false, originalIntent, resolvedType, filterCallingUid);
}
return skipPostResolution ? list : applyPostResolutionFilter(
@@ -687,8 +686,7 @@
list = new ArrayList<>(1);
list.add(ri);
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
- mInjector.getCompatibility(), mComponentResolver,
- list, false, intent, resolvedType, callingUid);
+ this, list, false, intent, resolvedType, callingUid);
}
}
} else {
@@ -699,8 +697,7 @@
if (originalIntent != null) {
// We also have to ensure all components match the original intent
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
- mInjector.getCompatibility(), mComponentResolver,
- list, false, originalIntent, resolvedType, callingUid);
+ this, list, false, originalIntent, resolvedType, callingUid);
}
return list;
@@ -713,7 +710,7 @@
String pkgName = intent.getPackage();
if (pkgName == null) {
final List<ResolveInfo> resolveInfos = mComponentResolver.queryServices(this, intent,
- resolvedType, flags, userId);
+ resolvedType, flags, callingUid, userId);
if (resolveInfos == null) {
return Collections.emptyList();
}
@@ -723,7 +720,7 @@
final AndroidPackage pkg = mPackages.get(pkgName);
if (pkg != null) {
final List<ResolveInfo> resolveInfos = mComponentResolver.queryServices(this, intent,
- resolvedType, flags, pkg.getServices(),
+ resolvedType, flags, pkg.getServices(), callingUid,
userId);
if (resolveInfos == null) {
return Collections.emptyList();
@@ -753,7 +750,7 @@
{@link PackageManager.SKIP_CURRENT_PROFILE} set.
*/
result.addAll(filterIfNotSystemUser(mComponentResolver.queryActivities(this,
- intent, resolvedType, flags, userId), userId));
+ intent, resolvedType, flags, filterCallingUid, userId), userId));
}
addInstant = isInstantAppResolutionAllowed(intent, result, userId,
false /*skipPackageCheck*/, flags);
@@ -777,7 +774,7 @@
|| !shouldFilterApplication(setting, filterCallingUid, userId))) {
result.addAll(filterIfNotSystemUser(mComponentResolver.queryActivities(this,
intent, resolvedType, flags, setting.getAndroidPackage().getActivities(),
- userId), userId));
+ filterCallingUid, userId), userId));
}
if (result == null || result.size() == 0) {
// the caller wants to resolve for a particular package; however, there
@@ -1108,7 +1105,7 @@
return null;
}
List<ResolveInfo> resultTargetUser = mComponentResolver.queryActivities(this, intent,
- resolvedType, flags, parentUserId);
+ resolvedType, flags, Binder.getCallingUid(), parentUserId);
if (resultTargetUser == null || resultTargetUser.isEmpty()) {
return null;
@@ -1346,7 +1343,7 @@
private List<ResolveInfo> maybeAddInstantAppInstaller(List<ResolveInfo> result,
Intent intent, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
- int userId, boolean resolveForStart, boolean isRequesterInstantApp) {
+ int callingUid, int userId, boolean resolveForStart, boolean isRequesterInstantApp) {
// first, check to see if we've got an instant app already installed
final boolean alreadyResolvedLocally = (flags & PackageManager.MATCH_INSTANT) != 0;
ResolveInfo localInstantApp = null;
@@ -1359,6 +1356,7 @@
| PackageManager.GET_RESOLVED_FILTER
| PackageManager.MATCH_INSTANT
| PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY,
+ callingUid,
userId);
for (int i = instantApps.size() - 1; i >= 0; --i) {
final ResolveInfo info = instantApps.get(i);
@@ -3693,10 +3691,13 @@
ps, callingUid, component, TYPE_ACTIVITY, userId, true /* filterUninstall */)) {
return false;
}
+ final boolean callerBlocksNullAction = isChangeEnabled(
+ IntentFilter.BLOCK_NULL_ACTION_INTENTS, callingUid);
for (int i=0; i< a.getIntents().size(); i++) {
if (a.getIntents().get(i).getIntentFilter()
.match(intent.getAction(), resolvedType, intent.getScheme(),
- intent.getData(), intent.getCategories(), TAG) >= 0) {
+ intent.getData(), intent.getCategories(), TAG,
+ /*supportWildcards*/ false, callerBlocksNullAction, null, null) >= 0) {
return true;
}
}
@@ -5794,4 +5795,37 @@
public UserInfo[] getUserInfos() {
return mInjector.getUserManagerInternal().getUserInfos();
}
+
+ @Override
+ public boolean isChangeEnabled(long changeId, int uid) {
+ final PlatformCompat compat = mInjector.getCompatibility();
+ int appId = UserHandle.getAppId(uid);
+ SettingBase s = mSettings.getSettingBase(appId);
+ if (s instanceof PackageSetting) {
+ var ps = (PackageSetting) s;
+ if (ps.getAndroidPackage() == null) return false;
+ var info = new ApplicationInfo();
+ info.packageName = ps.getPackageName();
+ info.targetSdkVersion = ps.getAndroidPackage().getTargetSdkVersion();
+ return compat.isChangeEnabledInternal(changeId, info);
+ } else if (s instanceof SharedUserSetting) {
+ var ss = (SharedUserSetting) s;
+ List<AndroidPackage> packages = ss.getPackages();
+ for (int i = 0; i < packages.size(); ++i) {
+ var pkg = packages.get(i);
+ var info = new ApplicationInfo();
+ info.packageName = pkg.getPackageName();
+ info.targetSdkVersion = pkg.getTargetSdkVersion();
+ if (compat.isChangeEnabledInternal(changeId, info)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isChangeEnabled(long changeId, ApplicationInfo info) {
+ return mInjector.getCompatibility().isChangeEnabledInternal(changeId, info);
+ }
}
diff --git a/services/core/java/com/android/server/pm/DefaultCrossProfileResolver.java b/services/core/java/com/android/server/pm/DefaultCrossProfileResolver.java
index 90d89c6..ee8f560 100644
--- a/services/core/java/com/android/server/pm/DefaultCrossProfileResolver.java
+++ b/services/core/java/com/android/server/pm/DefaultCrossProfileResolver.java
@@ -23,6 +23,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
+import android.os.Binder;
import android.util.SparseBooleanArray;
import com.android.internal.app.IntentForwarderActivity;
@@ -278,7 +279,7 @@
}
List<ResolveInfo> resultTargetUser = mComponentResolver.queryActivities(computer, intent,
- resolvedType, flags, targetUserId);
+ resolvedType, flags, Binder.getCallingUid(), targetUserId);
if (CollectionUtils.isEmpty(resultTargetUser)) {
return null;
}
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index 1bd5b99..0fd81ac 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -40,6 +40,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppGlobals;
+import android.app.role.RoleManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -272,7 +273,6 @@
* compiles it if needed.
*/
private void checkAndDexOptSystemUi(int reason) throws LegacyDexoptDisabledException {
- Installer.checkLegacyDexoptDisabled();
Computer snapshot = mPm.snapshotComputer();
String sysUiPackageName =
mPm.mContext.getString(com.android.internal.R.string.config_systemUi);
@@ -288,7 +288,7 @@
String compilerFilter;
if (isProfileGuidedCompilerFilter(targetCompilerFilter)) {
- compilerFilter = defaultCompilerFilter;
+ compilerFilter = "verify";
File profileFile = new File(getPrebuildProfilePath(pkg));
// Copy the profile to the reference profile path if it exists. Installd can only use a
@@ -312,7 +312,26 @@
compilerFilter = targetCompilerFilter;
}
- // We don't expect updates in current profiles to be significant here, but
+ performDexoptPackage(sysUiPackageName, reason, compilerFilter);
+ }
+
+ private void dexoptLauncher(int reason) throws LegacyDexoptDisabledException {
+ Computer snapshot = mPm.snapshotComputer();
+ RoleManager roleManager = mPm.mContext.getSystemService(RoleManager.class);
+ for (var packageName : roleManager.getRoleHolders(RoleManager.ROLE_HOME)) {
+ AndroidPackage pkg = snapshot.getPackage(packageName);
+ if (pkg == null) {
+ Log.w(TAG, "Launcher package " + packageName + " is not found for dexopting");
+ } else {
+ performDexoptPackage(packageName, reason, "speed-profile");
+ }
+ }
+ }
+
+ private void performDexoptPackage(@NonNull String packageName, int reason,
+ @NonNull String compilerFilter) throws LegacyDexoptDisabledException {
+ Installer.checkLegacyDexoptDisabled();
+
// DEXOPT_CHECK_FOR_PROFILES_UPDATES is set to replicate behaviour that will be
// unconditionally enabled for profile guided filters when ART Service is called instead of
// the legacy PackageDexOptimizer implementation.
@@ -320,8 +339,8 @@
? DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES
: 0;
- performDexOptTraced(new DexoptOptions(pkg.getPackageName(), REASON_BOOT_AFTER_OTA,
- compilerFilter, null /* splitName */, dexoptFlags));
+ performDexOptTraced(new DexoptOptions(
+ packageName, reason, compilerFilter, null /* splitName */, dexoptFlags));
}
/**
@@ -343,6 +362,10 @@
return;
}
+ Log.i(TAG,
+ "Starting boot dexopt for reason "
+ + DexoptOptions.convertToArtServiceDexoptReason(reason));
+
final long startTime = System.nanoTime();
if (useArtService()) {
@@ -351,9 +374,10 @@
null /* progressCallbackExecutor */, null /* progressCallback */);
} else {
try {
- // System UI is important to user experience, so we check it after a mainline update
- // or an OTA. It may need to be re-compiled in these cases.
+ // System UI and the launcher are important to user experience, so we check them
+ // after a mainline update or OTA. They may need to be re-compiled in these cases.
checkAndDexOptSystemUi(reason);
+ dexoptLauncher(reason);
if (reason != REASON_BOOT_AFTER_OTA && reason != REASON_FIRST_BOOT) {
return;
diff --git a/services/core/java/com/android/server/pm/NoFilteringResolver.java b/services/core/java/com/android/server/pm/NoFilteringResolver.java
index 3923890..e53e756 100644
--- a/services/core/java/com/android/server/pm/NoFilteringResolver.java
+++ b/services/core/java/com/android/server/pm/NoFilteringResolver.java
@@ -102,7 +102,7 @@
boolean hasNonNegativePriorityResult,
Function<String, PackageStateInternal> pkgSettingFunction) {
List<ResolveInfo> resolveInfos = mComponentResolver.queryActivities(computer,
- intent, resolvedType, flags, targetUserId);
+ intent, resolvedType, flags, Binder.getCallingUid(), targetUserId);
List<CrossProfileDomainInfo> crossProfileDomainInfos = new ArrayList<>();
if (resolveInfos != null) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 928ffa7..9036d4c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -16,6 +16,7 @@
package com.android.server.pm;
+import static android.content.IntentFilter.BLOCK_NULL_ACTION_INTENTS;
import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
import static android.content.pm.PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
@@ -94,7 +95,6 @@
import com.android.server.IntentResolver;
import com.android.server.LocalManagerRegistry;
import com.android.server.Watchdog;
-import com.android.server.compat.PlatformCompat;
import com.android.server.pm.dex.PackageDexUsage;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageStateInternal;
@@ -1166,10 +1166,8 @@
return (ps.getFlags() & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
}
- // Static to give access to ComputeEngine
public static void applyEnforceIntentFilterMatching(
- PlatformCompat compat, ComponentResolverApi resolver,
- List<ResolveInfo> resolveInfos, boolean isReceiver,
+ Computer computer, List<ResolveInfo> resolveInfos, boolean isReceiver,
Intent intent, String resolvedType, int filterCallingUid) {
if (DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.get()) return;
@@ -1177,6 +1175,11 @@
? new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM)
: null;
+ final boolean callerBlocksNullAction = computer.isChangeEnabled(
+ BLOCK_NULL_ACTION_INTENTS, filterCallingUid);
+
+ final ComponentResolverApi resolver = computer.getComponentResolver();
+
for (int i = resolveInfos.size() - 1; i >= 0; --i) {
final ComponentInfo info = resolveInfos.get(i).getComponentInfo();
@@ -1187,11 +1190,15 @@
}
// Only enforce filter matching if target app's target SDK >= T
- if (!compat.isChangeEnabledInternal(
+ if (!computer.isChangeEnabled(
ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS, info.applicationInfo)) {
continue;
}
+ // Block null action intent if either source or target app's target SDK >= U
+ final boolean blockNullAction = callerBlocksNullAction
+ || computer.isChangeEnabled(BLOCK_NULL_ACTION_INTENTS, info.applicationInfo);
+
final ParsedMainComponent comp;
if (info instanceof ActivityInfo) {
if (isReceiver) {
@@ -1213,7 +1220,8 @@
boolean match = false;
for (int j = 0, size = comp.getIntents().size(); j < size; ++j) {
IntentFilter intentFilter = comp.getIntents().get(j).getIntentFilter();
- if (IntentResolver.intentMatchesFilter(intentFilter, intent, resolvedType)) {
+ if (IntentResolver.intentMatchesFilter(
+ intentFilter, intent, resolvedType, blockNullAction)) {
match = true;
break;
}
diff --git a/services/core/java/com/android/server/pm/ResolveIntentHelper.java b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
index a13c568..8bca4a9 100644
--- a/services/core/java/com/android/server/pm/ResolveIntentHelper.java
+++ b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
@@ -466,15 +466,14 @@
list = new ArrayList<>(1);
list.add(ri);
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
- mPlatformCompat, componentResolver, list, true, intent,
- resolvedType, filterCallingUid);
+ computer, list, true, intent, resolvedType, filterCallingUid);
}
}
} else {
String pkgName = intent.getPackage();
if (pkgName == null) {
- final List<ResolveInfo> result = componentResolver
- .queryReceivers(computer, intent, resolvedType, flags, userId);
+ final List<ResolveInfo> result = componentResolver.queryReceivers(
+ computer, intent, resolvedType, flags, filterCallingUid, userId);
if (result != null) {
list = result;
}
@@ -482,7 +481,7 @@
final AndroidPackage pkg = computer.getPackage(pkgName);
if (pkg != null) {
final List<ResolveInfo> result = componentResolver.queryReceivers(computer,
- intent, resolvedType, flags, pkg.getReceivers(), userId);
+ intent, resolvedType, flags, pkg.getReceivers(), filterCallingUid, userId);
if (result != null) {
list = result;
}
@@ -492,8 +491,7 @@
if (originalIntent != null) {
// We also have to ensure all components match the original intent
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
- mPlatformCompat, componentResolver,
- list, true, originalIntent, resolvedType, filterCallingUid);
+ computer, list, true, originalIntent, resolvedType, filterCallingUid);
}
return computer.applyPostResolutionFilter(list, instantAppPkgName, false, queryingUid,
@@ -577,7 +575,7 @@
String pkgName = intent.getPackage();
if (pkgName == null) {
final List<ResolveInfo> resolveInfos = componentResolver.queryProviders(computer,
- intent, resolvedType, flags, userId);
+ intent, resolvedType, flags, callingUid, userId);
if (resolveInfos == null) {
return Collections.emptyList();
}
@@ -587,7 +585,7 @@
final AndroidPackage pkg = computer.getPackage(pkgName);
if (pkg != null) {
final List<ResolveInfo> resolveInfos = componentResolver.queryProviders(computer,
- intent, resolvedType, flags, pkg.getProviders(), userId);
+ intent, resolvedType, flags, pkg.getProviders(), callingUid, userId);
if (resolveInfos == null) {
return Collections.emptyList();
}
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 1cd9ec6..c6411ff 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -4323,8 +4323,14 @@
@NonNull ComponentName activity, @UserIdInt int userId) {
final long start = getStatStartTime();
try {
- return queryActivities(new Intent(), activity.getPackageName(), activity, userId)
- .size() > 0;
+ final ActivityInfo ai;
+ try {
+ ai = mContext.getPackageManager().getActivityInfoAsUser(activity,
+ PackageManager.ComponentInfoFlags.of(PACKAGE_MATCH_FLAGS), userId);
+ } catch (NameNotFoundException e) {
+ return false;
+ }
+ return ai.enabled && ai.exported;
} finally {
logDurationStat(Stats.IS_ACTIVITY_ENABLED, start);
}
diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolver.java b/services/core/java/com/android/server/pm/resolution/ComponentResolver.java
index fac681a..977fab1 100644
--- a/services/core/java/com/android/server/pm/resolution/ComponentResolver.java
+++ b/services/core/java/com/android/server/pm/resolution/ComponentResolver.java
@@ -807,10 +807,10 @@
}
}
- private abstract static class MimeGroupsAwareIntentResolver<F extends Pair<?
- extends ParsedComponent, ParsedIntentInfo>, R>
- extends IntentResolver<F, R> {
- private final ArrayMap<String, F[]> mMimeGroupToFilter = new ArrayMap<>();
+ private abstract static class MimeGroupsAwareIntentResolver<F extends ParsedComponent>
+ extends IntentResolver<Pair<F, ParsedIntentInfo>, ResolveInfo> {
+ private final ArrayMap<String, Pair<F, ParsedIntentInfo>[]> mMimeGroupToFilter =
+ new ArrayMap<>();
private boolean mIsUpdatingMimeGroup = false;
@NonNull
@@ -822,7 +822,7 @@
}
// Copy constructor used in creating snapshots
- MimeGroupsAwareIntentResolver(MimeGroupsAwareIntentResolver<F, R> orig,
+ MimeGroupsAwareIntentResolver(MimeGroupsAwareIntentResolver<F> orig,
@NonNull UserManagerService userManager) {
mUserManager = userManager;
copyFrom(orig);
@@ -831,7 +831,7 @@
}
@Override
- public void addFilter(@Nullable PackageDataSnapshot snapshot, F f) {
+ public void addFilter(@Nullable PackageDataSnapshot snapshot, Pair<F, ParsedIntentInfo> f) {
IntentFilter intentFilter = getIntentFilter(f);
// We assume Computer is available for this class and all subclasses. Because this class
// uses subclass method override to handle logic, the Computer parameter must be in the
@@ -846,7 +846,7 @@
}
@Override
- protected void removeFilterInternal(F f) {
+ protected void removeFilterInternal(Pair<F, ParsedIntentInfo> f) {
IntentFilter intentFilter = getIntentFilter(f);
if (!mIsUpdatingMimeGroup) {
unregister_intent_filter(f, intentFilter.mimeGroupsIterator(), mMimeGroupToFilter,
@@ -857,6 +857,86 @@
intentFilter.clearDynamicDataTypes();
}
+ @Override
+ public List<ResolveInfo> queryIntent(@NonNull PackageDataSnapshot snapshot, Intent intent,
+ String resolvedType, boolean defaultOnly, int callingUid, @UserIdInt int userId) {
+ if (!mUserManager.exists(userId)) return null;
+ long flags = (defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0);
+ return super.queryIntent(snapshot, intent, resolvedType, defaultOnly, callingUid,
+ userId, flags);
+ }
+
+ List<ResolveInfo> queryIntent(@NonNull Computer computer, Intent intent,
+ String resolvedType, long flags, int callingUid, int userId) {
+ if (!mUserManager.exists(userId)) return null;
+ return super.queryIntent(computer, intent, resolvedType,
+ (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, callingUid, userId, flags);
+ }
+
+ List<ResolveInfo> queryIntentForPackage(@NonNull Computer computer, Intent intent,
+ String resolvedType, long flags, List<F> packageComponents,
+ int callingUid, int userId) {
+ if (!mUserManager.exists(userId)) {
+ return null;
+ }
+ if (packageComponents == null) {
+ return Collections.emptyList();
+ }
+ final boolean defaultOnly = (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0;
+ final int componentsSize = packageComponents.size();
+ ArrayList<Pair<F, ParsedIntentInfo>[]> listCut = new ArrayList<>(componentsSize);
+
+ List<ParsedIntentInfo> intentFilters;
+ for (int i = 0; i < componentsSize; ++i) {
+ F component = packageComponents.get(i);
+ intentFilters = component.getIntents();
+ if (!intentFilters.isEmpty()) {
+ Pair<F, ParsedIntentInfo>[] array = newArray(intentFilters.size());
+ for (int arrayIndex = 0; arrayIndex < intentFilters.size(); arrayIndex++) {
+ array[arrayIndex] = Pair.create(component, intentFilters.get(arrayIndex));
+ }
+ listCut.add(array);
+ }
+ }
+ return super.queryIntentFromList(computer, intent, resolvedType,
+ defaultOnly, listCut, callingUid, userId, flags);
+ }
+
+ @Override
+ protected boolean isPackageForFilter(String packageName, Pair<F, ParsedIntentInfo> info) {
+ return packageName.equals(info.first.getPackageName());
+ }
+
+ @Override
+ protected void sortResults(List<ResolveInfo> results) {
+ results.sort(RESOLVE_PRIORITY_SORTER);
+ }
+
+ @Override
+ protected void filterResults(@NonNull Computer computer, @NonNull Intent intent,
+ List<ResolveInfo> results) {
+ if (intent.getAction() != null) return;
+ // When the resolved component is targeting U+, block null action intents
+ for (int i = results.size() - 1; i >= 0; --i) {
+ if (computer.isChangeEnabled(IntentFilter.BLOCK_NULL_ACTION_INTENTS,
+ results.get(i).getComponentInfo().applicationInfo)) {
+ results.remove(i);
+ }
+ }
+ }
+
+ @Override
+ protected Pair<F, ParsedIntentInfo>[] newArray(int size) {
+ //noinspection unchecked
+ return (Pair<F, ParsedIntentInfo>[]) new Pair<?, ?>[size];
+ }
+
+ @Override
+ protected IntentFilter getIntentFilter(
+ @NonNull Pair<F, ParsedIntentInfo> input) {
+ return input.second.getIntentFilter();
+ }
+
/**
* Updates MIME group by applying changes to all IntentFilters
* that contain the group and repopulating m*ToFilter maps accordingly
@@ -867,12 +947,12 @@
*/
public boolean updateMimeGroup(@NonNull Computer computer, String packageName,
String mimeGroup) {
- F[] filters = mMimeGroupToFilter.get(mimeGroup);
+ Pair<F, ParsedIntentInfo>[] filters = mMimeGroupToFilter.get(mimeGroup);
int n = filters != null ? filters.length : 0;
mIsUpdatingMimeGroup = true;
boolean hasChanges = false;
- F filter;
+ Pair<F, ParsedIntentInfo> filter;
for (int i = 0; i < n && (filter = filters[i]) != null; i++) {
if (isPackageForFilter(packageName, filter)) {
hasChanges |= updateFilter(computer, filter);
@@ -882,7 +962,7 @@
return hasChanges;
}
- private boolean updateFilter(@NonNull Computer computer, F f) {
+ private boolean updateFilter(@NonNull Computer computer, Pair<F, ParsedIntentInfo> f) {
IntentFilter filter = getIntentFilter(f);
List<String> oldTypes = filter.dataTypes();
removeFilter(f);
@@ -907,7 +987,7 @@
return first.equals(second);
}
- private void applyMimeGroups(@NonNull Computer computer, F f) {
+ private void applyMimeGroups(@NonNull Computer computer, Pair<F, ParsedIntentInfo> f) {
IntentFilter filter = getIntentFilter(f);
for (int i = filter.countMimeGroups() - 1; i >= 0; i--) {
@@ -931,8 +1011,8 @@
}
@Override
- protected boolean isFilterStopped(@NonNull Computer computer, F filter,
- @UserIdInt int userId) {
+ protected boolean isFilterStopped(@NonNull Computer computer,
+ Pair<F, ParsedIntentInfo> filter, @UserIdInt int userId) {
if (!mUserManager.exists(userId)) {
return true;
}
@@ -948,7 +1028,7 @@
}
public static class ActivityIntentResolver
- extends MimeGroupsAwareIntentResolver<Pair<ParsedActivity, ParsedIntentInfo>, ResolveInfo> {
+ extends MimeGroupsAwareIntentResolver<ParsedActivity> {
@NonNull
private UserNeedsBadgingCache mUserNeedsBadging;
@@ -969,53 +1049,6 @@
mUserNeedsBadging = userNeedsBadgingCache;
}
- @Override
- public List<ResolveInfo> queryIntent(@NonNull PackageDataSnapshot snapshot, Intent intent,
- String resolvedType, boolean defaultOnly, @UserIdInt int userId) {
- if (!mUserManager.exists(userId)) return null;
- long flags = (defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0);
- return super.queryIntent(snapshot, intent, resolvedType, defaultOnly, userId, flags);
- }
-
- List<ResolveInfo> queryIntent(@NonNull Computer computer, Intent intent,
- String resolvedType, long flags, int userId) {
- if (!mUserManager.exists(userId)) {
- return null;
- }
- return super.queryIntent(computer, intent, resolvedType,
- (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, userId, flags);
- }
-
- List<ResolveInfo> queryIntentForPackage(@NonNull Computer computer, Intent intent,
- String resolvedType, long flags, List<ParsedActivity> packageActivities,
- int userId) {
- if (!mUserManager.exists(userId)) {
- return null;
- }
- if (packageActivities == null) {
- return Collections.emptyList();
- }
- final boolean defaultOnly = (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0;
- final int activitiesSize = packageActivities.size();
- ArrayList<Pair<ParsedActivity, ParsedIntentInfo>[]> listCut =
- new ArrayList<>(activitiesSize);
-
- List<ParsedIntentInfo> intentFilters;
- for (int i = 0; i < activitiesSize; ++i) {
- ParsedActivity activity = packageActivities.get(i);
- intentFilters = activity.getIntents();
- if (!intentFilters.isEmpty()) {
- Pair<ParsedActivity, ParsedIntentInfo>[] array = newArray(intentFilters.size());
- for (int arrayIndex = 0; arrayIndex < intentFilters.size(); arrayIndex++) {
- array[arrayIndex] = Pair.create(activity, intentFilters.get(arrayIndex));
- }
- listCut.add(array);
- }
- }
- return super.queryIntentFromList(computer, intent, resolvedType,
- defaultOnly, listCut, userId, flags);
- }
-
protected void addActivity(@NonNull Computer computer, ParsedActivity a, String type,
List<Pair<ParsedActivity, ParsedIntentInfo>> newIntents) {
mActivities.put(a.getComponentName(), a);
@@ -1072,18 +1105,6 @@
return true;
}
- @Override
- protected Pair<ParsedActivity, ParsedIntentInfo>[] newArray(int size) {
- //noinspection unchecked
- return (Pair<ParsedActivity, ParsedIntentInfo>[]) new Pair<?, ?>[size];
- }
-
- @Override
- protected boolean isPackageForFilter(String packageName,
- Pair<ParsedActivity, ParsedIntentInfo> info) {
- return packageName.equals(info.first.getPackageName());
- }
-
private void log(String reason, ParsedIntentInfo info, int match,
int userId) {
Slog.w(TAG, reason
@@ -1199,11 +1220,6 @@
}
@Override
- protected void sortResults(List<ResolveInfo> results) {
- results.sort(RESOLVE_PRIORITY_SORTER);
- }
-
- @Override
protected void dumpFilter(PrintWriter out, String prefix,
Pair<ParsedActivity, ParsedIntentInfo> pair) {
ParsedActivity activity = pair.first;
@@ -1237,12 +1253,6 @@
out.println();
}
- @Override
- protected IntentFilter getIntentFilter(
- @NonNull Pair<ParsedActivity, ParsedIntentInfo> input) {
- return input.second.getIntentFilter();
- }
-
protected List<ParsedActivity> getResolveList(AndroidPackage pkg) {
return pkg.getActivities();
}
@@ -1278,7 +1288,7 @@
}
public static final class ProviderIntentResolver
- extends MimeGroupsAwareIntentResolver<Pair<ParsedProvider, ParsedIntentInfo>, ResolveInfo> {
+ extends MimeGroupsAwareIntentResolver<ParsedProvider> {
// Default constructor
ProviderIntentResolver(@NonNull UserManagerService userManager) {
super(userManager);
@@ -1291,57 +1301,6 @@
mProviders.putAll(orig.mProviders);
}
- @Override
- public List<ResolveInfo> queryIntent(@NonNull PackageDataSnapshot snapshot, Intent intent,
- String resolvedType, boolean defaultOnly, @UserIdInt int userId) {
- if (!mUserManager.exists(userId)) {
- return null;
- }
- long flags = defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0;
- return super.queryIntent(snapshot, intent, resolvedType, defaultOnly, userId, flags);
- }
-
- @Nullable
- List<ResolveInfo> queryIntent(@NonNull Computer computer, Intent intent,
- String resolvedType, long flags, int userId) {
- if (!mUserManager.exists(userId)) {
- return null;
- }
- return super.queryIntent(computer, intent, resolvedType,
- (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, userId, flags);
- }
-
- @Nullable
- List<ResolveInfo> queryIntentForPackage(@NonNull Computer computer, Intent intent,
- String resolvedType, long flags, List<ParsedProvider> packageProviders,
- int userId) {
- if (!mUserManager.exists(userId)) {
- return null;
- }
- if (packageProviders == null) {
- return Collections.emptyList();
- }
- final boolean defaultOnly = (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0;
- final int providersSize = packageProviders.size();
- ArrayList<Pair<ParsedProvider, ParsedIntentInfo>[]> listCut =
- new ArrayList<>(providersSize);
-
- List<ParsedIntentInfo> intentFilters;
- for (int i = 0; i < providersSize; ++i) {
- ParsedProvider provider = packageProviders.get(i);
- intentFilters = provider.getIntents();
- if (!intentFilters.isEmpty()) {
- Pair<ParsedProvider, ParsedIntentInfo>[] array = newArray(intentFilters.size());
- for (int arrayIndex = 0; arrayIndex < intentFilters.size(); arrayIndex++) {
- array[arrayIndex] = Pair.create(provider, intentFilters.get(arrayIndex));
- }
- listCut.add(array);
- }
- }
- return super.queryIntentFromList(computer, intent, resolvedType,
- defaultOnly, listCut, userId, flags);
- }
-
void addProvider(@NonNull Computer computer, ParsedProvider p) {
if (mProviders.containsKey(p.getComponentName())) {
Slog.w(TAG, "Provider " + p.getComponentName() + " already defined; ignoring");
@@ -1402,18 +1361,6 @@
}
@Override
- protected Pair<ParsedProvider, ParsedIntentInfo>[] newArray(int size) {
- //noinspection unchecked
- return (Pair<ParsedProvider, ParsedIntentInfo>[]) new Pair<?, ?>[size];
- }
-
- @Override
- protected boolean isPackageForFilter(String packageName,
- Pair<ParsedProvider, ParsedIntentInfo> info) {
- return packageName.equals(info.first.getPackageName());
- }
-
- @Override
protected ResolveInfo newResult(@NonNull Computer computer,
Pair<ParsedProvider, ParsedIntentInfo> pair, int match, int userId,
long customFlags) {
@@ -1479,11 +1426,6 @@
}
@Override
- protected void sortResults(List<ResolveInfo> results) {
- results.sort(RESOLVE_PRIORITY_SORTER);
- }
-
- @Override
protected void dumpFilter(PrintWriter out, String prefix,
Pair<ParsedProvider, ParsedIntentInfo> pair) {
ParsedProvider provider = pair.first;
@@ -1518,17 +1460,11 @@
out.println();
}
- @Override
- protected IntentFilter getIntentFilter(
- @NonNull Pair<ParsedProvider, ParsedIntentInfo> input) {
- return input.second.getIntentFilter();
- }
-
final ArrayMap<ComponentName, ParsedProvider> mProviders = new ArrayMap<>();
}
public static final class ServiceIntentResolver
- extends MimeGroupsAwareIntentResolver<Pair<ParsedService, ParsedIntentInfo>, ResolveInfo> {
+ extends MimeGroupsAwareIntentResolver<ParsedService> {
// Default constructor
ServiceIntentResolver(@NonNull UserManagerService userManager) {
super(userManager);
@@ -1541,50 +1477,6 @@
mServices.putAll(orig.mServices);
}
- @Override
- public List<ResolveInfo> queryIntent(@NonNull PackageDataSnapshot snapshot, Intent intent,
- String resolvedType, boolean defaultOnly, @UserIdInt int userId) {
- if (!mUserManager.exists(userId)) {
- return null;
- }
- long flags = defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0;
- return super.queryIntent(snapshot, intent, resolvedType, defaultOnly, userId, flags);
- }
-
- List<ResolveInfo> queryIntent(@NonNull Computer computer, Intent intent,
- String resolvedType, long flags, int userId) {
- if (!mUserManager.exists(userId)) return null;
- return super.queryIntent(computer, intent, resolvedType,
- (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, userId, flags);
- }
-
- List<ResolveInfo> queryIntentForPackage(@NonNull Computer computer, Intent intent,
- String resolvedType, long flags, List<ParsedService> packageServices, int userId) {
- if (!mUserManager.exists(userId)) return null;
- if (packageServices == null) {
- return Collections.emptyList();
- }
- final boolean defaultOnly = (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0;
- final int servicesSize = packageServices.size();
- ArrayList<Pair<ParsedService, ParsedIntentInfo>[]> listCut =
- new ArrayList<>(servicesSize);
-
- List<ParsedIntentInfo> intentFilters;
- for (int i = 0; i < servicesSize; ++i) {
- ParsedService service = packageServices.get(i);
- intentFilters = service.getIntents();
- if (intentFilters.size() > 0) {
- Pair<ParsedService, ParsedIntentInfo>[] array = newArray(intentFilters.size());
- for (int arrayIndex = 0; arrayIndex < intentFilters.size(); arrayIndex++) {
- array[arrayIndex] = Pair.create(service, intentFilters.get(arrayIndex));
- }
- listCut.add(array);
- }
- }
- return super.queryIntentFromList(computer, intent, resolvedType,
- defaultOnly, listCut, userId, flags);
- }
-
void addService(@NonNull Computer computer, ParsedService s) {
mServices.put(s.getComponentName(), s);
if (DEBUG_SHOW_INFO) {
@@ -1640,18 +1532,6 @@
}
@Override
- protected Pair<ParsedService, ParsedIntentInfo>[] newArray(int size) {
- //noinspection unchecked
- return (Pair<ParsedService, ParsedIntentInfo>[]) new Pair<?, ?>[size];
- }
-
- @Override
- protected boolean isPackageForFilter(String packageName,
- Pair<ParsedService, ParsedIntentInfo> info) {
- return packageName.equals(info.first.getPackageName());
- }
-
- @Override
protected ResolveInfo newResult(@NonNull Computer computer,
Pair<ParsedService, ParsedIntentInfo> pair, int match, int userId,
long customFlags) {
@@ -1710,11 +1590,6 @@
}
@Override
- protected void sortResults(List<ResolveInfo> results) {
- results.sort(RESOLVE_PRIORITY_SORTER);
- }
-
- @Override
protected void dumpFilter(PrintWriter out, String prefix,
Pair<ParsedService, ParsedIntentInfo> pair) {
ParsedService service = pair.first;
@@ -1752,12 +1627,6 @@
out.println();
}
- @Override
- protected IntentFilter getIntentFilter(
- @NonNull Pair<ParsedService, ParsedIntentInfo> input) {
- return input.second.getIntentFilter();
- }
-
// Keys are String (activity class name), values are Activity.
final ArrayMap<ComponentName, ParsedService> mServices = new ArrayMap<>();
}
@@ -1821,7 +1690,8 @@
}
@Override
- protected void filterResults(List<AuxiliaryResolveInfo.AuxiliaryFilter> results) {
+ protected void filterResults(@NonNull Computer computer,
+ @NonNull Intent intent, List<AuxiliaryResolveInfo.AuxiliaryFilter> results) {
// only do work if ordering is enabled [most of the time it won't be]
if (mOrderResult.size() == 0) {
return;
diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolverApi.java b/services/core/java/com/android/server/pm/resolution/ComponentResolverApi.java
index b8e4c8d..7f88660 100644
--- a/services/core/java/com/android/server/pm/resolution/ComponentResolverApi.java
+++ b/services/core/java/com/android/server/pm/resolution/ComponentResolverApi.java
@@ -55,12 +55,12 @@
@Nullable
List<ResolveInfo> queryActivities(@NonNull Computer computer, @NonNull Intent intent,
- @Nullable String resolvedType, long flags, @UserIdInt int userId);
+ @Nullable String resolvedType, long flags, int callingUid, @UserIdInt int userId);
@Nullable
List<ResolveInfo> queryActivities(@NonNull Computer computer, @NonNull Intent intent,
@Nullable String resolvedType, long flags, @NonNull List<ParsedActivity> activities,
- @UserIdInt int userId);
+ int callingUid, @UserIdInt int userId);
@Nullable
ProviderInfo queryProvider(@NonNull Computer computer, @NonNull String authority, long flags,
@@ -68,12 +68,12 @@
@Nullable
List<ResolveInfo> queryProviders(@NonNull Computer computer, @NonNull Intent intent,
- @Nullable String resolvedType, long flags, @UserIdInt int userId);
+ @Nullable String resolvedType, long flags, int callingUid, @UserIdInt int userId);
@Nullable
List<ResolveInfo> queryProviders(@NonNull Computer computer, @NonNull Intent intent,
@Nullable String resolvedType, long flags, @NonNull List<ParsedProvider> providers,
- @UserIdInt int userId);
+ int callingUid, @UserIdInt int userId);
@Nullable
List<ProviderInfo> queryProviders(@NonNull Computer computer, @Nullable String processName,
@@ -81,21 +81,21 @@
@Nullable
List<ResolveInfo> queryReceivers(@NonNull Computer computer, @NonNull Intent intent,
- @Nullable String resolvedType, long flags, @UserIdInt int userId);
+ @Nullable String resolvedType, long flags, int callingUid, @UserIdInt int userId);
@Nullable
List<ResolveInfo> queryReceivers(@NonNull Computer computer, @NonNull Intent intent,
@Nullable String resolvedType, long flags, @NonNull List<ParsedActivity> receivers,
- @UserIdInt int userId);
+ int callingUid, @UserIdInt int userId);
@Nullable
List<ResolveInfo> queryServices(@NonNull Computer computer, @NonNull Intent intent,
- @Nullable String resolvedType, long flags, @UserIdInt int userId);
+ @Nullable String resolvedType, long flags, int callingUid, @UserIdInt int userId);
@Nullable
List<ResolveInfo> queryServices(@NonNull Computer computer, @NonNull Intent intent,
@Nullable String resolvedType, long flags, @NonNull List<ParsedService> services,
- @UserIdInt int userId);
+ int callingUid, @UserIdInt int userId);
void querySyncProviders(@NonNull Computer computer, @NonNull List<String> outNames,
@NonNull List<ProviderInfo> outInfo, boolean safeMode, @UserIdInt int userId);
diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java b/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java
index 9115775..6899924 100644
--- a/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java
+++ b/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java
@@ -126,17 +126,17 @@
@Nullable
@Override
public List<ResolveInfo> queryActivities(@NonNull Computer computer, @NonNull Intent intent,
- @Nullable String resolvedType, long flags, int userId) {
- return mActivities.queryIntent(computer, intent, resolvedType, flags, userId);
+ @Nullable String resolvedType, long flags, int callingUid, int userId) {
+ return mActivities.queryIntent(computer, intent, resolvedType, flags, callingUid, userId);
}
@Nullable
@Override
public List<ResolveInfo> queryActivities(@NonNull Computer computer, @NonNull Intent intent,
@Nullable String resolvedType, long flags, @NonNull List<ParsedActivity> activities,
- int userId) {
+ int callingUid, int userId) {
return mActivities.queryIntentForPackage(computer, intent, resolvedType, flags, activities,
- userId);
+ callingUid, userId);
}
@Nullable
@@ -168,17 +168,17 @@
@Nullable
@Override
public List<ResolveInfo> queryProviders(@NonNull Computer computer, @NonNull Intent intent,
- @Nullable String resolvedType, long flags, int userId) {
- return mProviders.queryIntent(computer, intent, resolvedType, flags, userId);
+ @Nullable String resolvedType, long flags, int callingUid, int userId) {
+ return mProviders.queryIntent(computer, intent, resolvedType, flags, callingUid, userId);
}
@Nullable
@Override
public List<ResolveInfo> queryProviders(@NonNull Computer computer, @NonNull Intent intent,
@Nullable String resolvedType, long flags, @NonNull List<ParsedProvider> providers,
- @UserIdInt int userId) {
+ int callingUid, @UserIdInt int userId) {
return mProviders.queryIntentForPackage(computer, intent, resolvedType, flags, providers,
- userId);
+ callingUid, userId);
}
@Nullable
@@ -241,33 +241,33 @@
@Nullable
@Override
public List<ResolveInfo> queryReceivers(@NonNull Computer computer, @NonNull Intent intent,
- @Nullable String resolvedType, long flags, int userId) {
- return mReceivers.queryIntent(computer, intent, resolvedType, flags, userId);
+ @Nullable String resolvedType, long flags, int callingUid, int userId) {
+ return mReceivers.queryIntent(computer, intent, resolvedType, flags, callingUid, userId);
}
@Nullable
@Override
public List<ResolveInfo> queryReceivers(@NonNull Computer computer, @NonNull Intent intent,
@Nullable String resolvedType, long flags, @NonNull List<ParsedActivity> receivers,
- @UserIdInt int userId) {
+ int callingUid, @UserIdInt int userId) {
return mReceivers.queryIntentForPackage(computer, intent, resolvedType, flags, receivers,
- userId);
+ callingUid, userId);
}
@Nullable
@Override
public List<ResolveInfo> queryServices(@NonNull Computer computer, @NonNull Intent intent,
- @Nullable String resolvedType, long flags, @UserIdInt int userId) {
- return mServices.queryIntent(computer, intent, resolvedType, flags, userId);
+ @Nullable String resolvedType, long flags, int callingUid, @UserIdInt int userId) {
+ return mServices.queryIntent(computer, intent, resolvedType, flags, callingUid, userId);
}
@Nullable
@Override
public List<ResolveInfo> queryServices(@NonNull Computer computer, @NonNull Intent intent,
@Nullable String resolvedType, long flags, @NonNull List<ParsedService> services,
- @UserIdInt int userId) {
+ int callingUid, @UserIdInt int userId) {
return mServices.queryIntentForPackage(computer, intent, resolvedType, flags, services,
- userId);
+ callingUid, userId);
}
@Override
diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolverLocked.java b/services/core/java/com/android/server/pm/resolution/ComponentResolverLocked.java
index 0c84f4c..5bfb135 100644
--- a/services/core/java/com/android/server/pm/resolution/ComponentResolverLocked.java
+++ b/services/core/java/com/android/server/pm/resolution/ComponentResolverLocked.java
@@ -92,9 +92,9 @@
@Nullable
@Override
public List<ResolveInfo> queryActivities(@NonNull Computer computer, @NonNull Intent intent,
- @Nullable String resolvedType, long flags, @UserIdInt int userId) {
+ @Nullable String resolvedType, long flags, int callingUid, @UserIdInt int userId) {
synchronized (mLock) {
- return super.queryActivities(computer, intent, resolvedType, flags, userId);
+ return super.queryActivities(computer, intent, resolvedType, flags, callingUid, userId);
}
}
@@ -102,9 +102,10 @@
@Override
public List<ResolveInfo> queryActivities(@NonNull Computer computer, @NonNull Intent intent,
@Nullable String resolvedType, long flags, @NonNull List<ParsedActivity> activities,
- @UserIdInt int userId) {
+ int callingUid, @UserIdInt int userId) {
synchronized (mLock) {
- return super.queryActivities(computer, intent, resolvedType, flags, activities, userId);
+ return super.queryActivities(computer, intent, resolvedType, flags, activities,
+ callingUid, userId);
}
}
@@ -120,9 +121,9 @@
@Nullable
@Override
public List<ResolveInfo> queryProviders(@NonNull Computer computer, @NonNull Intent intent,
- @Nullable String resolvedType, long flags, @UserIdInt int userId) {
+ @Nullable String resolvedType, long flags, int callingUid, @UserIdInt int userId) {
synchronized (mLock) {
- return super.queryProviders(computer, intent, resolvedType, flags, userId);
+ return super.queryProviders(computer, intent, resolvedType, flags, callingUid, userId);
}
}
@@ -130,9 +131,10 @@
@Override
public List<ResolveInfo> queryProviders(@NonNull Computer computer, @NonNull Intent intent,
@Nullable String resolvedType, long flags, @NonNull List<ParsedProvider> providers,
- @UserIdInt int userId) {
+ int callingUid, @UserIdInt int userId) {
synchronized (mLock) {
- return super.queryProviders(computer, intent, resolvedType, flags, providers, userId);
+ return super.queryProviders(computer, intent, resolvedType, flags, providers,
+ callingUid, userId);
}
}
@@ -149,9 +151,9 @@
@Nullable
@Override
public List<ResolveInfo> queryReceivers(@NonNull Computer computer, @NonNull Intent intent,
- @Nullable String resolvedType, long flags, @UserIdInt int userId) {
+ @Nullable String resolvedType, long flags, int callingUid, @UserIdInt int userId) {
synchronized (mLock) {
- return super.queryReceivers(computer, intent, resolvedType, flags, userId);
+ return super.queryReceivers(computer, intent, resolvedType, flags, callingUid, userId);
}
}
@@ -159,18 +161,19 @@
@Override
public List<ResolveInfo> queryReceivers(@NonNull Computer computer, @NonNull Intent intent,
@Nullable String resolvedType, long flags, @NonNull List<ParsedActivity> receivers,
- @UserIdInt int userId) {
+ int callingUid, @UserIdInt int userId) {
synchronized (mLock) {
- return super.queryReceivers(computer, intent, resolvedType, flags, receivers, userId);
+ return super.queryReceivers(computer, intent, resolvedType, flags, receivers,
+ callingUid, userId);
}
}
@Nullable
@Override
public List<ResolveInfo> queryServices(@NonNull Computer computer, @NonNull Intent intent,
- @Nullable String resolvedType, long flags, @UserIdInt int userId) {
+ @Nullable String resolvedType, long flags, int callingUid, @UserIdInt int userId) {
synchronized (mLock) {
- return super.queryServices(computer, intent, resolvedType, flags, userId);
+ return super.queryServices(computer, intent, resolvedType, flags, callingUid, userId);
}
}
@@ -178,9 +181,10 @@
@Override
public List<ResolveInfo> queryServices(@NonNull Computer computer, @NonNull Intent intent,
@Nullable String resolvedType, long flags, @NonNull List<ParsedService> services,
- @UserIdInt int userId) {
+ int callingUid, @UserIdInt int userId) {
synchronized (mLock) {
- return super.queryServices(computer, intent, resolvedType, flags, services, userId);
+ return super.queryServices(computer, intent, resolvedType, flags, services, callingUid,
+ userId);
}
}
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index db44e14..0e99e7e 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -194,27 +194,38 @@
mPackageManagerInternal.getPackageList(new PackageListObserver() {
@Override
- public void onPackageAdded(String packageName, int uid) {
- final int userId = UserHandle.getUserId(uid);
- if (isStarted(userId)) {
- synchronizePackagePermissionsAndAppOpsForUser(packageName, userId);
+ public void onPackageAdded(String packageName, int appId) {
+ final int[] userIds = LocalServices.getService(UserManagerInternal.class)
+ .getUserIds();
+ for (final int userId : userIds) {
+ if (isStarted(userId)) {
+ synchronizePackagePermissionsAndAppOpsForUser(packageName, userId);
+ }
}
}
@Override
- public void onPackageChanged(String packageName, int uid) {
- final int userId = UserHandle.getUserId(uid);
- if (isStarted(userId)) {
- synchronizePackagePermissionsAndAppOpsForUser(packageName, userId);
- resetAppOpPermissionsIfNotRequestedForUid(uid);
+ public void onPackageChanged(String packageName, int appId) {
+ final int[] userIds = LocalServices.getService(UserManagerInternal.class)
+ .getUserIds();
+ for (final int userId : userIds) {
+ if (isStarted(userId)) {
+ synchronizePackagePermissionsAndAppOpsForUser(packageName, userId);
+ final int uid = UserHandle.getUid(userId, appId);
+ resetAppOpPermissionsIfNotRequestedForUid(uid);
+ }
}
}
@Override
- public void onPackageRemoved(String packageName, int uid) {
- final int userId = UserHandle.getUserId(uid);
- if (isStarted(userId)) {
- resetAppOpPermissionsIfNotRequestedForUid(uid);
+ public void onPackageRemoved(String packageName, int appId) {
+ final int[] userIds = LocalServices.getService(UserManagerInternal.class)
+ .getUserIds();
+ for (final int userId : userIds) {
+ if (isStarted(userId)) {
+ final int uid = UserHandle.getUid(userId, appId);
+ resetAppOpPermissionsIfNotRequestedForUid(uid);
+ }
}
}
});
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 336ad75..a24f129 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -161,6 +161,7 @@
import android.speech.RecognizerIntent;
import android.telecom.TelecomManager;
import android.util.Log;
+import android.util.MathUtils;
import android.util.MutableBoolean;
import android.util.PrintWriterPrinter;
import android.util.Slog;
@@ -210,6 +211,7 @@
import com.android.server.LocalServices;
import com.android.server.SystemServiceManager;
import com.android.server.UiThread;
+import com.android.server.display.BrightnessUtils;
import com.android.server.input.InputManagerInternal;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.policy.KeyCombinationManager.TwoKeysCombinationRule;
@@ -2993,6 +2995,12 @@
return key_consumed;
}
break;
+ case KeyEvent.KEYCODE_T:
+ if (down && event.isMetaPressed()) {
+ toggleTaskbar();
+ return key_consumed;
+ }
+ break;
case KeyEvent.KEYCODE_DPAD_UP:
if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
@@ -3075,19 +3083,22 @@
Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL,
UserHandle.USER_CURRENT_OR_SELF);
}
- float min = mPowerManager.getBrightnessConstraint(
- PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
- float max = mPowerManager.getBrightnessConstraint(
- PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
- float step = (max - min) / BRIGHTNESS_STEPS * direction;
int screenDisplayId = displayId < 0 ? DEFAULT_DISPLAY : displayId;
- float brightness = mDisplayManager.getBrightness(screenDisplayId);
- brightness += step;
- // Make sure we don't go beyond the limits.
- brightness = Math.min(max, brightness);
- brightness = Math.max(min, brightness);
- mDisplayManager.setBrightness(screenDisplayId, brightness);
+ float linearBrightness = mDisplayManager.getBrightness(screenDisplayId);
+
+ float gammaBrightness = BrightnessUtils.convertLinearToGamma(linearBrightness);
+ float adjustedGammaBrightness =
+ gammaBrightness + 1f / BRIGHTNESS_STEPS * direction;
+
+ float adjustedLinearBrightness = BrightnessUtils.convertGammaToLinear(
+ adjustedGammaBrightness);
+
+ adjustedLinearBrightness = MathUtils.constrain(adjustedLinearBrightness, 0f,
+ 1f);
+
+ mDisplayManager.setBrightness(screenDisplayId, adjustedLinearBrightness);
+
startActivityAsUser(new Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG),
UserHandle.CURRENT_OR_SELF);
}
@@ -3664,6 +3675,13 @@
}
}
+ private void toggleTaskbar() {
+ StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
+ if (statusbar != null) {
+ statusbar.toggleTaskbar();
+ }
+ }
+
private void toggleRecentApps() {
mPreloadedRecentApps = false; // preloading no longer needs to be canceled
StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 3d3c5e2..f11c864 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -1743,7 +1743,8 @@
if (historyDirectory == null) {
mCheckinFile = null;
mStatsFile = null;
- mHistory = new BatteryStatsHistory(mStepDetailsCalculator, mClock);
+ mHistory = new BatteryStatsHistory(mConstants.MAX_HISTORY_FILES,
+ mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock);
} else {
mCheckinFile = new AtomicFile(new File(historyDirectory, "batterystats-checkin.bin"));
mStatsFile = new AtomicFile(new File(historyDirectory, "batterystats.bin"));
@@ -10881,14 +10882,17 @@
if (systemDir == null) {
mStatsFile = null;
- mHistory = new BatteryStatsHistory(mStepDetailsCalculator, mClock);
+ mCheckinFile = null;
+ mDailyFile = null;
+ mHistory = new BatteryStatsHistory(mConstants.MAX_HISTORY_FILES,
+ mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock);
} else {
mStatsFile = new AtomicFile(new File(systemDir, "batterystats.bin"));
+ mCheckinFile = new AtomicFile(new File(systemDir, "batterystats-checkin.bin"));
+ mDailyFile = new AtomicFile(new File(systemDir, "batterystats-daily.xml"));
mHistory = new BatteryStatsHistory(systemDir, mConstants.MAX_HISTORY_FILES,
mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock);
}
- mCheckinFile = new AtomicFile(new File(systemDir, "batterystats-checkin.bin"));
- mDailyFile = new AtomicFile(new File(systemDir, "batterystats-daily.xml"));
mStartCount++;
initTimersAndCounters();
mOnBattery = mOnBatteryInternal = false;
diff --git a/services/core/java/com/android/server/rollback/OWNERS b/services/core/java/com/android/server/rollback/OWNERS
index 7feb85f..daa0211 100644
--- a/services/core/java/com/android/server/rollback/OWNERS
+++ b/services/core/java/com/android/server/rollback/OWNERS
@@ -1 +1,3 @@
-olilan@google.com
+ancr@google.com
+harshitmahajan@google.com
+robertogil@google.com
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index ec052ec..efd8b6d 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -122,6 +122,10 @@
*/
void onEmergencyActionLaunchGestureDetected();
+ /** Toggle the task bar stash state. */
+ void toggleTaskbar();
+
+ /** Toggle recents. */
void toggleRecentApps();
void setCurrentUser(int newUserId);
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 4489ba9..5bace0e 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -418,6 +418,15 @@
}
@Override
+ public void toggleTaskbar() {
+ if (mBar != null) {
+ try {
+ mBar.toggleTaskbar();
+ } catch (RemoteException ex) {}
+ }
+ }
+
+ @Override
public void toggleRecentApps() {
if (mBar != null) {
try {
diff --git a/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java b/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java
index 4a6c794..de631bb 100644
--- a/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java
+++ b/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java
@@ -65,7 +65,7 @@
* If a request fails, it retries a number of times with a "short" interval and then resets to the
* normal interval. The process then repeats.
*
- * <p>When a valid network time is available, the time is always suggested to the {@link
+ * <p>When a valid network time is available, the network time is always suggested to the {@link
* com.android.server.timedetector.TimeDetectorService} where it may be used to set the device
* system clock, depending on user settings and what other signals are available.
*/
@@ -181,24 +181,6 @@
}
/**
- * Clears the cached NTP time. For use during tests to simulate when no NTP time is available.
- *
- * <p>This operation takes place in the calling thread rather than the service's handler thread.
- */
- @RequiresPermission(android.Manifest.permission.SET_TIME)
- void clearTimeForTests() {
- mContext.enforceCallingPermission(
- android.Manifest.permission.SET_TIME, "clear latest network time");
-
- final long token = Binder.clearCallingIdentity();
- try {
- mNtpTrustedTime.clearCachedTimeResult();
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- /**
* Forces the service to refresh the NTP time.
*
* <p>This operation takes place in the calling thread rather than the service's handler thread.
@@ -286,7 +268,7 @@
}
/**
- * Checks if the user prefers to automatically set the time.
+ * Checks if the user prefers to automatically set the device's system clock time.
*/
private boolean isAutomaticTimeEnabled() {
ContentResolver resolver = mContext.getContentResolver();
@@ -313,7 +295,7 @@
}
/**
- * The interface the service uses to interact with the time refresh logic.
+ * The interface the service uses to interact with the network time refresh logic.
* Extracted for testing.
*/
@VisibleForTesting
@@ -387,10 +369,10 @@
private final NtpTrustedTime mNtpTrustedTime;
/**
- * Records the time of the last refresh attempt (successful or otherwise) by this service.
- * This is used when scheduling the next refresh attempt. In cases where {@link
- * #refreshAndRescheduleIfRequired} is called too frequently, this will prevent each call
- * resulting in a network request. See also {@link #mShortPollingIntervalMillis}.
+ * Records the elapsed realtime of the last refresh attempt (successful or otherwise) by
+ * this service. This is used when scheduling the next refresh attempt. In cases where
+ * {@link #refreshAndRescheduleIfRequired} is called too frequently, this will prevent each
+ * call resulting in a network request. See also {@link #mShortPollingIntervalMillis}.
*
* <p>Time servers are a shared resource and so Android should avoid loading them.
* Generally, a refresh attempt will succeed and the service won't need to make further
@@ -454,8 +436,11 @@
return;
}
- // Attempt to refresh the network time if there is no latest time result, or if the
- // latest time result is considered too old.
+ // Step 1: Work out if the latest time result, if any, needs to be refreshed and handle
+ // the refresh.
+
+ // A refresh should be attempted if there is no latest time result, or if the latest
+ // time result is considered too old.
NtpTrustedTime.TimeResult initialTimeResult = mNtpTrustedTime.getCachedTimeResult();
boolean shouldAttemptRefresh;
synchronized (this) {
@@ -472,22 +457,42 @@
boolean refreshSuccessful = false;
if (shouldAttemptRefresh) {
// This is a blocking call. Deliberately invoked without holding the "this" monitor
- // to avoid blocking logic that wants to use the "this" monitor.
+ // to avoid blocking other logic that wants to use the "this" monitor, e.g. dump().
refreshSuccessful = tryRefresh(network);
}
synchronized (this) {
- // Manage mTryAgainCounter.
+ // This section of code deliberately doesn't assume it is the only component using
+ // the NtpTrustedTime singleton to obtain NTP times: another component in the same
+ // process could be gathering NTP signals (which then won't have been suggested to
+ // the time detector).
+ // TODO(b/222295093): Make this class the sole user of the NtpTrustedTime singleton
+ // and simplify / reduce duplicate suggestions and other logic.
+ NtpTrustedTime.TimeResult latestTimeResult = mNtpTrustedTime.getCachedTimeResult();
+
+ // currentElapsedRealtimeMillis is used to evaluate ages and refresh scheduling
+ // below. Capturing this after obtaining the cached time result ensures that latest
+ // time result ages will be >= 0.
+ long currentElapsedRealtimeMillis = mElapsedRealtimeMillisSupplier.get();
+
+ long latestTimeResultAgeMillis = calculateTimeResultAgeMillis(
+ latestTimeResult, currentElapsedRealtimeMillis);
+
+ // Step 2: Set mTryAgainCounter.
+ // + == 0: The last attempt was successful OR the latest time result is acceptable
+ // OR the mTryAgainCounter exceeded mTryAgainTimesMax and has been reset
+ // to 0. In all these cases the normal refresh interval should be used.
+ // + > 0: The last refresh attempt was unsuccessful. Some number of retries are
+ // allowed using the short interval depending on mTryAgainTimesMax.
if (shouldAttemptRefresh) {
if (refreshSuccessful) {
- // Reset failure tracking.
mTryAgainCounter = 0;
} else {
if (mTryAgainTimesMax < 0) {
// When mTryAgainTimesMax is negative there's no enforced maximum and
// short intervals should be used until a successful refresh. Setting
// mTryAgainCounter to 1 is sufficient for the interval calculations
- // below. There's no need to increment.
+ // below, i.e. there's no need to increment.
mTryAgainCounter = 1;
} else {
mTryAgainCounter++;
@@ -497,58 +502,97 @@
}
}
}
-
- // currentElapsedRealtimeMillis is used to evaluate ages and refresh scheduling
- // below. Capturing this after a possible successful refresh ensures that latest
- // time result ages will be >= 0.
- long currentElapsedRealtimeMillis = mElapsedRealtimeMillisSupplier.get();
-
- // This section of code deliberately doesn't assume it is the only component using
- // mNtpTrustedTime to obtain NTP times: another component in the same process could
- // be gathering NTP signals (which then won't have been suggested to the time
- // detector).
- // TODO(b/222295093): Make this class the sole owner of mNtpTrustedTime and
- // simplify / reduce duplicate suggestions.
- NtpTrustedTime.TimeResult latestTimeResult = mNtpTrustedTime.getCachedTimeResult();
- long latestTimeResultAgeMillis = calculateTimeResultAgeMillis(
- latestTimeResult, currentElapsedRealtimeMillis);
-
- // Suggest the latest time result to the time detector if it is fresh regardless of
- // whether refresh happened above.
if (latestTimeResultAgeMillis < mNormalPollingIntervalMillis) {
- // We assume the time detector service will detect duplicate suggestions and not
- // do more work than it has to, so no need to avoid making duplicate
- // suggestions.
+ // The latest time result may indicate a successful refresh has been achieved by
+ // another user of the NtpTrustedTime singleton. This could be an "else if", but
+ // this is deliberately done defensively in all cases to maintain the invariant
+ // that mTryAgainCounter will be 0 if the latest time result is currently ok.
+ mTryAgainCounter = 0;
+ }
+
+ // Step 3: Suggest the latest time result to the time detector if it is fresh
+ // regardless of whether a refresh happened / succeeded above. The time detector
+ // service can detect duplicate suggestions and not do more work than it has to, so
+ // there is no need to avoid making duplicate suggestions.
+ if (latestTimeResultAgeMillis < mNormalPollingIntervalMillis) {
makeNetworkTimeSuggestion(latestTimeResult, reason, refreshCallbacks);
}
- // (Re)schedule the next refresh based on the latest state.
- // Determine which refresh delay to use by using the current value of
- // mTryAgainCounter. The refresh delay is applied to a different point in time
- // depending on whether the latest available time result (if any) is still
- // considered fresh to ensure the delay acts correctly.
- long refreshDelayMillis = mTryAgainCounter > 0
+ // Step 4: (Re)schedule the next refresh attempt based on the latest state.
+
+ // Determine which refresh attempt delay to use by using the current value of
+ // mTryAgainCounter.
+ long refreshAttemptDelayMillis = mTryAgainCounter > 0
? mShortPollingIntervalMillis : mNormalPollingIntervalMillis;
+
+ // The refresh attempt delay is applied to a different point in time depending on
+ // whether a refresh attempt is overdue to ensure the refresh attempt scheduling
+ // acts correctly / safely, i.e. won't schedule actions for immediate execution or
+ // in the past.
long nextRefreshElapsedRealtimeMillis;
- if (latestTimeResultAgeMillis < mNormalPollingIntervalMillis) {
- // The latest time result is fresh, use it to determine when next to refresh.
+ if (latestTimeResultAgeMillis < refreshAttemptDelayMillis) {
+ // The latestTimeResultAgeMillis and refreshAttemptDelayMillis indicate a
+ // refresh attempt is not yet due. This branch uses the elapsed realtime of the
+ // latest time result to calculate when the latest time result will become too
+ // old and the next refresh attempt will be due.
+ //
+ // Possibilities:
+ // + A refresh was attempted and successful, mTryAgainCounter will be set
+ // to 0, refreshAttemptDelayMillis == mNormalPollingIntervalMillis, and this
+ // branch will execute.
+ // + No refresh was attempted, but something else refreshed the latest time
+ // result held by the NtpTrustedTime.
+ //
+ // If a refresh was attempted but was unsuccessful, latestTimeResultAgeMillis >=
+ // mNormalPollingIntervalMillis (because otherwise it wouldn't be attempted),
+ // this branch won't be executed, and the one below will be instead.
nextRefreshElapsedRealtimeMillis =
- latestTimeResult.getElapsedRealtimeMillis() + refreshDelayMillis;
+ latestTimeResult.getElapsedRealtimeMillis() + refreshAttemptDelayMillis;
} else if (mLastRefreshAttemptElapsedRealtimeMillis != null) {
- // The latest time result is missing or old and still needs to be refreshed.
- // mLastRefreshAttemptElapsedRealtimeMillis, which should always be set by this
- // point because there's no fresh time result, should be very close to
- // currentElapsedRealtimeMillis unless the refresh was not allowed.
+ // This branch is executed when the latest time result is missing, or it's older
+ // than refreshAttemptDelayMillis. There may already have been attempts to
+ // refresh the network time that have failed, so the important point for this
+ // branch is not how old the latest time result is, but when the last refresh
+ // attempt took place:
+ // + If a refresh was just attempted (and failed), then
+ // mLastRefreshAttemptElapsedRealtimeMillis will be close to
+ // currentElapsedRealtimeMillis.
+ // + If a refresh was not just attempted, for a refresh not to have been
+ // attempted EITHER:
+ // + The latest time result must be < mNormalPollingIntervalMillis ago
+ // (would be handled by the branch above)
+ // + A refresh wasn't allowed because {time since last refresh attempt}
+ // < mShortPollingIntervalMillis, so
+ // (mLastRefreshAttemptElapsedRealtimeMillis + refreshAttemptDelayMillis)
+ // would have to be in the future regardless of the
+ // refreshAttemptDelayMillis value. This ignores the execution time
+ // between the "current time" used to work out whether a refresh needed to
+ // happen, and "current time" used to compute the last time result age,
+ // but a single short interval shouldn't matter.
nextRefreshElapsedRealtimeMillis =
- mLastRefreshAttemptElapsedRealtimeMillis + refreshDelayMillis;
+ mLastRefreshAttemptElapsedRealtimeMillis + refreshAttemptDelayMillis;
} else {
- // This should not happen: mLastRefreshAttemptElapsedRealtimeMillis should
- // always be non-null by this point.
- logToDebugAndDumpsys(
- "mLastRefreshAttemptElapsedRealtimeMillis unexpectedly missing."
- + " Scheduling using currentElapsedRealtimeMillis");
+ // This branch should never execute: mLastRefreshAttemptElapsedRealtimeMillis
+ // should always be non-null because a refresh should always be attempted at
+ // least once above. Regardelss, the calculation below should result in safe
+ // scheduling behavior.
+ String logMsg = "mLastRefreshAttemptElapsedRealtimeMillis unexpectedly missing."
+ + " Scheduling using currentElapsedRealtimeMillis";
+ Log.w(TAG, logMsg);
+ logToDebugAndDumpsys(logMsg);
nextRefreshElapsedRealtimeMillis =
- currentElapsedRealtimeMillis + refreshDelayMillis;
+ currentElapsedRealtimeMillis + refreshAttemptDelayMillis;
+ }
+
+ // Defensive coding to guard against bad scheduling / logic errors above: Try to
+ // ensure that alarms aren't scheduled in the past.
+ if (nextRefreshElapsedRealtimeMillis <= currentElapsedRealtimeMillis) {
+ String logMsg = "nextRefreshElapsedRealtimeMillis is a time in the past."
+ + " Scheduling using currentElapsedRealtimeMillis instead";
+ Log.w(TAG, logMsg);
+ logToDebugAndDumpsys(logMsg);
+ nextRefreshElapsedRealtimeMillis =
+ currentElapsedRealtimeMillis + refreshAttemptDelayMillis;
}
refreshCallbacks.scheduleNextRefresh(nextRefreshElapsedRealtimeMillis);
@@ -562,7 +606,7 @@
+ formatElapsedRealtimeMillis(currentElapsedRealtimeMillis)
+ ", latestTimeResult=" + latestTimeResult
+ ", mTryAgainCounter=" + mTryAgainCounter
- + ", refreshDelayMillis=" + refreshDelayMillis
+ + ", refreshAttemptDelayMillis=" + refreshAttemptDelayMillis
+ ", nextRefreshElapsedRealtimeMillis="
+ formatElapsedRealtimeMillis(nextRefreshElapsedRealtimeMillis));
}
@@ -592,6 +636,12 @@
return currentElapsedRealtimeMillis >= nextRefreshAllowedElapsedRealtimeMillis;
}
+ /**
+ * Attempts a network time refresh. Updates {@link
+ * #mLastRefreshAttemptElapsedRealtimeMillis} regardless of the outcome and returns whether
+ * the attempt was successful. The latest successful refresh result can be found in {@link
+ * NtpTrustedTime#getCachedTimeResult()}.
+ */
private boolean tryRefresh(@NonNull Network network) {
long currentElapsedRealtimeMillis = mElapsedRealtimeMillisSupplier.get();
synchronized (this) {
@@ -600,15 +650,18 @@
return mNtpTrustedTime.forceRefresh(network);
}
- /** Suggests the time to the time detector. It may choose use it to set the system clock. */
- private void makeNetworkTimeSuggestion(@NonNull TimeResult ntpResult,
+ /**
+ * Suggests the network time to the time detector. It may choose use it to set the system
+ * clock.
+ */
+ private void makeNetworkTimeSuggestion(@NonNull TimeResult timeResult,
@NonNull String debugInfo, @NonNull RefreshCallbacks refreshCallbacks) {
UnixEpochTime timeSignal = new UnixEpochTime(
- ntpResult.getElapsedRealtimeMillis(), ntpResult.getTimeMillis());
+ timeResult.getElapsedRealtimeMillis(), timeResult.getTimeMillis());
NetworkTimeSuggestion timeSuggestion =
- new NetworkTimeSuggestion(timeSignal, ntpResult.getUncertaintyMillis());
+ new NetworkTimeSuggestion(timeSignal, timeResult.getUncertaintyMillis());
timeSuggestion.addDebugInfo(debugInfo);
- timeSuggestion.addDebugInfo(ntpResult.toString());
+ timeSuggestion.addDebugInfo(timeResult.toString());
refreshCallbacks.submitSuggestion(timeSuggestion);
}
diff --git a/services/core/java/com/android/server/timedetector/NetworkTimeUpdateServiceShellCommand.java b/services/core/java/com/android/server/timedetector/NetworkTimeUpdateServiceShellCommand.java
index afc0bdd..cfc95df 100644
--- a/services/core/java/com/android/server/timedetector/NetworkTimeUpdateServiceShellCommand.java
+++ b/services/core/java/com/android/server/timedetector/NetworkTimeUpdateServiceShellCommand.java
@@ -37,11 +37,6 @@
private static final String SHELL_COMMAND_SERVICE_NAME = "network_time_update_service";
/**
- * A shell command that clears the time signal received from the network.
- */
- private static final String SHELL_COMMAND_CLEAR_TIME = "clear_time";
-
- /**
* A shell command that forces the time signal to be refreshed from the network.
*/
private static final String SHELL_COMMAND_FORCE_REFRESH = "force_refresh";
@@ -73,8 +68,6 @@
}
switch (cmd) {
- case SHELL_COMMAND_CLEAR_TIME:
- return runClearTime();
case SHELL_COMMAND_FORCE_REFRESH:
return runForceRefresh();
case SHELL_COMMAND_SET_SERVER_CONFIG:
@@ -87,11 +80,6 @@
}
}
- private int runClearTime() {
- mNetworkTimeUpdateService.clearTimeForTests();
- return 0;
- }
-
private int runForceRefresh() {
boolean success = mNetworkTimeUpdateService.forceRefreshForTests();
getOutPrintWriter().println(success);
@@ -147,8 +135,6 @@
pw.printf("Network Time Update Service (%s) commands:\n", SHELL_COMMAND_SERVICE_NAME);
pw.printf(" help\n");
pw.printf(" Print this help text.\n");
- pw.printf(" %s\n", SHELL_COMMAND_CLEAR_TIME);
- pw.printf(" Clears the latest time.\n");
pw.printf(" %s\n", SHELL_COMMAND_FORCE_REFRESH);
pw.printf(" Refreshes the latest time. Prints whether it was successful.\n");
pw.printf(" %s\n", SHELL_COMMAND_SET_SERVER_CONFIG);
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index 0da967a..22f096b 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -37,6 +37,7 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
+import android.os.SystemClock;
import android.util.ArrayMap;
import android.util.IndentingPrintWriter;
import android.util.NtpTrustedTime;
@@ -53,6 +54,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.net.InetSocketAddress;
import java.time.DateTimeException;
import java.util.Objects;
@@ -377,7 +379,7 @@
*
* <p>This operation takes place in the calling thread.
*/
- void clearNetworkTime() {
+ void clearLatestNetworkTime() {
enforceSuggestNetworkTimePermission();
final long token = Binder.clearCallingIdentity();
@@ -390,12 +392,29 @@
@Override
public UnixEpochTime latestNetworkTime() {
- NetworkTimeSuggestion suggestion = getLatestNetworkSuggestion();
- if (suggestion != null) {
- return suggestion.getUnixEpochTime();
+ NetworkTimeSuggestion latestNetworkTime;
+ // TODO(b/222295093): Remove this condition once we can be sure that all uses of
+ // NtpTrustedTime result in a suggestion being made to the time detector.
+ // mNtpTrustedTime can be removed once this happens.
+ if (TimeDetectorNetworkTimeHelper.isInUse()) {
+ // The new implementation.
+ latestNetworkTime = mTimeDetectorStrategy.getLatestNetworkSuggestion();
} else {
+ // The old implementation.
+ NtpTrustedTime.TimeResult ntpResult = mNtpTrustedTime.getCachedTimeResult();
+ if (ntpResult != null) {
+ latestNetworkTime = new NetworkTimeSuggestion(
+ new UnixEpochTime(
+ ntpResult.getElapsedRealtimeMillis(), ntpResult.getTimeMillis()),
+ ntpResult.getUncertaintyMillis());
+ } else {
+ latestNetworkTime = null;
+ }
+ }
+ if (latestNetworkTime == null) {
throw new ParcelableException(new DateTimeException("Missing network time fix"));
}
+ return latestNetworkTime.getUnixEpochTime();
}
/**
@@ -403,23 +422,7 @@
*/
@Nullable
NetworkTimeSuggestion getLatestNetworkSuggestion() {
- // TODO(b/222295093): Return the latest network time from mTimeDetectorStrategy once we can
- // be sure that all uses of NtpTrustedTime results in a suggestion being made to the time
- // detector. mNtpTrustedTime can be removed once this happens.
- if (TimeDetectorNetworkTimeHelper.isInUse()) {
- // The new implementation.
- return mTimeDetectorStrategy.getLatestNetworkSuggestion();
- } else {
- // The old implementation.
- NtpTrustedTime.TimeResult ntpResult = mNtpTrustedTime.getCachedTimeResult();
- if (ntpResult != null) {
- UnixEpochTime unixEpochTime = new UnixEpochTime(
- ntpResult.getElapsedRealtimeMillis(), ntpResult.getTimeMillis());
- return new NetworkTimeSuggestion(unixEpochTime, ntpResult.getUncertaintyMillis());
- } else {
- return null;
- }
- }
+ return mTimeDetectorStrategy.getLatestNetworkSuggestion();
}
/**
@@ -440,6 +443,57 @@
mHandler.post(() -> mTimeDetectorStrategy.suggestExternalTime(timeSignal));
}
+ /**
+ * Sets the network time for testing {@link SystemClock#currentNetworkTimeClock()}.
+ *
+ * <p>This operation takes place in the calling thread.
+ */
+ void setNetworkTimeForSystemClockForTests(
+ @NonNull UnixEpochTime unixEpochTime, int uncertaintyMillis) {
+ enforceSuggestNetworkTimePermission();
+
+ // TODO(b/222295093): Remove this condition once we can be sure that all uses of
+ // NtpTrustedTime result in a suggestion being made to the time detector.
+ // mNtpTrustedTime can be removed once this happens.
+ if (TimeDetectorNetworkTimeHelper.isInUse()) {
+ NetworkTimeSuggestion suggestion =
+ new NetworkTimeSuggestion(unixEpochTime, uncertaintyMillis);
+ suggestion.addDebugInfo("Injected for tests");
+ mTimeDetectorStrategy.suggestNetworkTime(suggestion);
+ } else {
+ NtpTrustedTime.TimeResult timeResult = new NtpTrustedTime.TimeResult(
+ unixEpochTime.getUnixEpochTimeMillis(),
+ unixEpochTime.getElapsedRealtimeMillis(),
+ uncertaintyMillis,
+ InetSocketAddress.createUnresolved("time.set.for.tests", 123));
+ mNtpTrustedTime.setCachedTimeResult(timeResult);
+ }
+ }
+
+ /**
+ * Clears the network time for testing {@link SystemClock#currentNetworkTimeClock()}.
+ *
+ * <p>This operation takes place in the calling thread.
+ */
+ void clearNetworkTimeForSystemClockForTests() {
+ enforceSuggestNetworkTimePermission();
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ // TODO(b/222295093): Remove this condition once we can be sure that all uses of
+ // NtpTrustedTime result in a suggestion being made to the time detector.
+ // mNtpTrustedTime can be removed once this happens.
+ if (TimeDetectorNetworkTimeHelper.isInUse()) {
+ // Clear the latest network suggestion. Done in all c
+ mTimeDetectorStrategy.clearLatestNetworkSuggestion();
+ } else {
+ mNtpTrustedTime.clearCachedTimeResult();
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
@Override
protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw,
@Nullable String[] args) {
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java b/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java
index cce5709..fe0127f 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorShellCommand.java
@@ -16,12 +16,14 @@
package com.android.server.timedetector;
import static android.app.timedetector.TimeDetector.SHELL_COMMAND_CLEAR_NETWORK_TIME;
+import static android.app.timedetector.TimeDetector.SHELL_COMMAND_CLEAR_SYSTEM_CLOCK_NETWORK_TIME;
import static android.app.timedetector.TimeDetector.SHELL_COMMAND_CONFIRM_TIME;
import static android.app.timedetector.TimeDetector.SHELL_COMMAND_GET_NETWORK_TIME;
import static android.app.timedetector.TimeDetector.SHELL_COMMAND_GET_TIME_STATE;
import static android.app.timedetector.TimeDetector.SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED;
import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SERVICE_NAME;
import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED;
+import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SET_SYSTEM_CLOCK_NETWORK_TIME;
import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SET_TIME_STATE;
import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SUGGEST_EXTERNAL_TIME;
import static android.app.timedetector.TimeDetector.SHELL_COMMAND_SUGGEST_GNSS_TIME;
@@ -73,9 +75,9 @@
case SHELL_COMMAND_SUGGEST_NETWORK_TIME:
return runSuggestNetworkTime();
case SHELL_COMMAND_GET_NETWORK_TIME:
- return runGetNetworkTime();
+ return runGetLatestNetworkTime();
case SHELL_COMMAND_CLEAR_NETWORK_TIME:
- return runClearNetworkTime();
+ return runClearLatestNetworkTime();
case SHELL_COMMAND_SUGGEST_GNSS_TIME:
return runSuggestGnssTime();
case SHELL_COMMAND_SUGGEST_EXTERNAL_TIME:
@@ -86,6 +88,10 @@
return runSetTimeState();
case SHELL_COMMAND_CONFIRM_TIME:
return runConfirmTime();
+ case SHELL_COMMAND_CLEAR_SYSTEM_CLOCK_NETWORK_TIME:
+ return runClearSystemClockNetworkTime();
+ case SHELL_COMMAND_SET_SYSTEM_CLOCK_NETWORK_TIME:
+ return runSetSystemClockNetworkTime();
default: {
return handleDefaultCommands(cmd);
}
@@ -128,15 +134,15 @@
mInterface::suggestNetworkTime);
}
- private int runGetNetworkTime() {
+ private int runGetLatestNetworkTime() {
NetworkTimeSuggestion networkTimeSuggestion = mInterface.getLatestNetworkSuggestion();
final PrintWriter pw = getOutPrintWriter();
pw.println(networkTimeSuggestion);
return 0;
}
- private int runClearNetworkTime() {
- mInterface.clearNetworkTime();
+ private int runClearLatestNetworkTime() {
+ mInterface.clearLatestNetworkTime();
return 0;
}
@@ -187,6 +193,20 @@
return 0;
}
+ private int runClearSystemClockNetworkTime() {
+ mInterface.clearNetworkTimeForSystemClockForTests();
+ return 0;
+ }
+
+ private int runSetSystemClockNetworkTime() {
+ NetworkTimeSuggestion networkTimeSuggestion =
+ NetworkTimeSuggestion.parseCommandLineArg(this);
+ mInterface.setNetworkTimeForSystemClockForTests(
+ networkTimeSuggestion.getUnixEpochTime(),
+ networkTimeSuggestion.getUncertaintyMillis());
+ return 0;
+ }
+
@Override
public void onHelp() {
final PrintWriter pw = getOutPrintWriter();
@@ -218,6 +238,16 @@
pw.printf(" Prints the network time information held by the detector.\n");
pw.printf(" %s\n", SHELL_COMMAND_CLEAR_NETWORK_TIME);
pw.printf(" Clears the network time information held by the detector.\n");
+ // TODO(b/222295093) Remove these "system_clock" commands when
+ // SystemClock.currentNetworkTimeClock() is guaranteed to use the latest network
+ // suggestion. Then, commands above can be used instead.
+ pw.printf(" %s <network suggestion opts>\n",
+ SHELL_COMMAND_SET_SYSTEM_CLOCK_NETWORK_TIME);
+ pw.printf(" Sets the network time information used for"
+ + " SystemClock.currentNetworkTimeClock().\n");
+ pw.printf(" %s\n", SHELL_COMMAND_CLEAR_SYSTEM_CLOCK_NETWORK_TIME);
+ pw.printf(" Clears the network time information used for"
+ + " SystemClock.currentNetworkTimeClock().\n");
pw.println();
ManualTimeSuggestion.printCommandLineOpts(pw);
pw.println();
diff --git a/services/core/java/com/android/server/utils/Slogf.java b/services/core/java/com/android/server/utils/Slogf.java
index 6efbd89..a4b2bfb 100644
--- a/services/core/java/com/android/server/utils/Slogf.java
+++ b/services/core/java/com/android/server/utils/Slogf.java
@@ -30,11 +30,13 @@
/**
* Extends {@link Slog} by providing overloaded methods that take string formatting.
*
- * <p><strong>Note: </strong>the overloaded methods won't create the formatted message if the
- * respective logging level is disabled for the tag, but the compiler will still create an
- * intermediate array of the objects for the {@code vargars}, which could affect garbage collection.
- * So, if you're calling these method in a critical path, make sure to explicitly check for the
- * level before calling them.
+ * <p><strong>Note: </strong>Like the other logging classes, e.g. {@link Log} and {@link Slog}, the
+ * methods in this class log unconditionally regardless of {@link Log#isLoggable(String, int)}.
+ * Therefore, these methods exist just for the convenience of handling formatting. (Even if they
+ * did check {@link Log#isLoggable(String, int)} before formatting and logging, calling a varargs
+ * method in Java still involves an array allocation.) If you need to avoid the overhead of logging
+ * on a performance-critical path, either don't use logging in that place, or make the logging
+ * conditional on a static boolean defaulting to false.
*/
public final class Slogf {
@@ -56,11 +58,6 @@
throw new UnsupportedOperationException("provides only static methods");
}
- /** Same as {@link Log#isLoggable(String, int)}. */
- public static boolean isLoggable(String tag, int level) {
- return Log.isLoggable(tag, level);
- }
-
/** Same as {@link Slog#v(String, String)}. */
public static int v(String tag, String msg) {
return Slog.v(tag, msg);
@@ -146,166 +143,62 @@
return Slog.println(priority, tag, msg);
}
- /**
- * Logs a {@link Log.VERBOSE} message.
- *
- * <p><strong>Note: </strong>the message will only be formatted if {@link Log#VERBOSE} logging
- * is enabled for the given {@code tag}, but the compiler will still create an intermediate
- * array of the objects for the {@code vargars}, which could affect garbage collection. So, if
- * you're calling this method in a critical path, make sure to explicitly do the check before
- * calling it.
- */
+ /** Logs a {@link Log.VERBOSE} message. */
public static void v(String tag, String format, @Nullable Object... args) {
- if (!isLoggable(tag, Log.VERBOSE)) return;
-
v(tag, getMessage(format, args));
}
- /**
- * Logs a {@link Log.VEBOSE} message with a throwable
- *
- * <p><strong>Note: </strong>the message will only be formatted if {@link Log#VERBOSE} logging
- * is enabled for the given {@code tag}, but the compiler will still create an intermediate
- * array of the objects for the {@code vargars}, which could affect garbage collection. So, if
- * you're calling this method in a critical path, make sure to explicitly do the check before
- * calling it.
- */
+ /** Logs a {@link Log.VERBOSE} message with a throwable. */
public static void v(String tag, Throwable throwable, String format, @Nullable Object... args) {
- if (!isLoggable(tag, Log.VERBOSE)) return;
-
v(tag, getMessage(format, args), throwable);
}
- /**
- * Logs a {@link Log.DEBUG} message.
- *
- * <p><strong>Note: </strong>the message will only be formatted if {@link Log#DEBUG} logging is
- * enabled for the given {@code tag}, but the compiler will still create an intermediate array
- * of the objects for the {@code vargars}, which could affect garbage collection. So, if you're
- * calling this method in a critical path, make sure to explicitly do the check before calling
- * it.
- */
+ /** Logs a {@link Log.DEBUG} message. */
public static void d(String tag, String format, @Nullable Object... args) {
- if (!isLoggable(tag, Log.DEBUG)) return;
-
d(tag, getMessage(format, args));
}
- /**
- * Logs a {@link Log.DEBUG} message with a throwable
- *
- * <p><strong>Note: </strong>the message will only be formatted if {@link Log#DEBUG} logging
- * is enabled for the given {@code tag}, but the compiler will still create an intermediate
- * array of the objects for the {@code vargars}, which could affect garbage collection. So, if
- * you're calling this method in a critical path, make sure to explicitly do the check before
- * calling it.
- */
+ /** Logs a {@link Log.DEBUG} message with a throwable. */
public static void d(String tag, Throwable throwable, String format, @Nullable Object... args) {
- if (!isLoggable(tag, Log.DEBUG)) return;
-
d(tag, getMessage(format, args), throwable);
}
- /**
- * Logs a {@link Log.INFO} message.
- *
- * <p><strong>Note: </strong>the message will only be formatted if {@link Log#INFO} logging is
- * enabled for the given {@code tag}, but the compiler will still create an intermediate array
- * of the objects for the {@code vargars}, which could affect garbage collection. So, if you're
- * calling this method in a critical path, make sure to explicitly do the check before calling
- * it.
- */
+ /** Logs a {@link Log.INFO} message. */
public static void i(String tag, String format, @Nullable Object... args) {
- if (!isLoggable(tag, Log.INFO)) return;
-
i(tag, getMessage(format, args));
}
- /**
- * Logs a {@link Log.INFO} message with a throwable
- *
- * <p><strong>Note: </strong>the message will only be formatted if {@link Log#INFO} logging
- * is enabled for the given {@code tag}, but the compiler will still create an intermediate
- * array of the objects for the {@code vargars}, which could affect garbage collection. So, if
- * you're calling this method in a critical path, make sure to explicitly do the check before
- * calling it.
- */
+ /** Logs a {@link Log.INFO} message with a throwable. */
public static void i(String tag, Throwable throwable, String format, @Nullable Object... args) {
- if (!isLoggable(tag, Log.INFO)) return;
-
i(tag, getMessage(format, args), throwable);
}
- /**
- * Logs a {@link Log.WARN} message.
- *
- * <p><strong>Note: </strong>the message will only be formatted if {@link Log#WARN} logging is
- * enabled for the given {@code tag}, but the compiler will still create an intermediate array
- * of the objects for the {@code vargars}, which could affect garbage collection. So, if you're
- * calling this method in a critical path, make sure to explicitly do the check before calling
- * it.
- */
+ /** Logs a {@link Log.WARN} message. */
public static void w(String tag, String format, @Nullable Object... args) {
- if (!isLoggable(tag, Log.WARN)) return;
-
w(tag, getMessage(format, args));
}
- /**
- * Logs a {@link Log.WARN} message with a throwable
- *
- * <p><strong>Note: </strong>the message will only be formatted if {@link Log#WARN} logging is
- * enabled for the given {@code tag}, but the compiler will still create an intermediate array
- * of the objects for the {@code vargars}, which could affect garbage collection. So, if you're
- * calling this method in a critical path, make sure to explicitly do the check before calling
- * it.
- */
+ /** Logs a {@link Log.WARN} message with a throwable. */
public static void w(String tag, Throwable throwable, String format, @Nullable Object... args) {
- if (!isLoggable(tag, Log.WARN)) return;
-
w(tag, getMessage(format, args), throwable);
}
- /**
- * Logs a {@link Log.ERROR} message.
- *
- * <p><strong>Note: </strong>the message will only be formatted if {@link Log#ERROR} logging is
- * enabled for the given {@code tag}, but the compiler will still create an intermediate array
- * of the objects for the {@code vargars}, which could affect garbage collection. So, if you're
- * calling this method in a critical path, make sure to explicitly do the check before calling
- * it.
- */
+ /** Logs a {@link Log.ERROR} message. */
public static void e(String tag, String format, @Nullable Object... args) {
- if (!isLoggable(tag, Log.ERROR)) return;
-
e(tag, getMessage(format, args));
}
- /**
- * Logs a {@link Log.ERROR} message with a throwable
- *
- * <p><strong>Note: </strong>the message will only be formatted if {@link Log#ERROR} logging is
- * enabled for the given {@code tag}, but the compiler will still create an intermediate array
- * of the objects for the {@code vargars}, which could affect garbage collection. So, if you're
- * calling this method in a critical path, make sure to explicitly do the check before calling
- * it.
- */
+ /** Logs a {@link Log.ERROR} message with a throwable. */
public static void e(String tag, Throwable throwable, String format, @Nullable Object... args) {
- if (!isLoggable(tag, Log.ERROR)) return;
-
e(tag, getMessage(format, args), throwable);
}
- /**
- * Logs a {@code wtf} message.
- */
+ /** Logs a {@code wtf} message. */
public static void wtf(String tag, String format, @Nullable Object... args) {
wtf(tag, getMessage(format, args));
}
- /**
- * Logs a {@code wtf} message with a throwable.
- */
+ /** Logs a {@code wtf} message with a throwable. */
public static void wtf(String tag, Throwable throwable, String format,
@Nullable Object... args) {
wtf(tag, getMessage(format, args), throwable);
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index a0d8dfb..9aa7e56 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -19,7 +19,6 @@
import static android.accessibilityservice.AccessibilityTrace.FLAGS_MAGNIFICATION_CALLBACK;
import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK;
import static android.os.Build.IS_USER;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
@@ -85,7 +84,6 @@
import android.util.TypedValue;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
-import android.view.InsetsSource;
import android.view.MagnificationSpec;
import android.view.Surface;
import android.view.Surface.OutOfResourcesException;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index d21274a..e62d305 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -344,6 +344,7 @@
import android.window.WindowContainerToken;
import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ResolverActivity;
import com.android.internal.content.ReferrerIntent;
@@ -508,7 +509,10 @@
private RemoteTransition mPendingRemoteTransition;
ActivityOptions returningOptions; // options that are coming back via convertToTranslucent
AppTimeTracker appTimeTracker; // set if we are tracking the time in this app/task/activity
+ @GuardedBy("this")
ActivityServiceConnectionsHolder mServiceConnectionsHolder; // Service connections.
+ /** @see android.content.Context#BIND_ADJUST_WITH_ACTIVITY */
+ volatile boolean mVisibleForServiceConnection;
UriPermissionOwner uriPermissions; // current special URI access perms.
WindowProcessController app; // if non-null, hosting application
private State mState; // current state we are in
@@ -557,7 +561,8 @@
long lastLaunchTime; // time of last launch of this activity
ComponentName requestedVrComponent; // the requested component for handling VR mode.
- boolean inHistory; // are we in the history task?
+ /** Whether this activity is reachable from hierarchy. */
+ volatile boolean inHistory;
final ActivityTaskSupervisor mTaskSupervisor;
final RootWindowContainer mRootWindowContainer;
@@ -1917,7 +1922,9 @@
}
}
- static @Nullable ActivityRecord forTokenLocked(IBinder token) {
+ /** Gets the corresponding record by the token. Note that it may not exist in the hierarchy. */
+ @Nullable
+ static ActivityRecord forToken(IBinder token) {
if (token == null) return null;
final Token activityToken;
try {
@@ -1926,7 +1933,11 @@
Slog.w(TAG, "Bad activity token: " + token, e);
return null;
}
- final ActivityRecord r = activityToken.mActivityRef.get();
+ return activityToken.mActivityRef.get();
+ }
+
+ static @Nullable ActivityRecord forTokenLocked(IBinder token) {
+ final ActivityRecord r = forToken(token);
return r == null || r.getRootTask() == null ? null : r;
}
@@ -4059,17 +4070,32 @@
mDisplayContent.getDisplayPolicy().removeRelaunchingApp(this);
}
+ ActivityServiceConnectionsHolder getOrCreateServiceConnectionsHolder() {
+ synchronized (this) {
+ if (mServiceConnectionsHolder == null) {
+ mServiceConnectionsHolder = new ActivityServiceConnectionsHolder(this);
+ }
+ return mServiceConnectionsHolder;
+ }
+ }
+
/**
* Perform clean-up of service connections in an activity record.
*/
private void cleanUpActivityServices() {
- if (mServiceConnectionsHolder == null) {
- return;
+ synchronized (this) {
+ if (mServiceConnectionsHolder == null) {
+ return;
+ }
+ // Throw away any services that have been bound by this activity.
+ mServiceConnectionsHolder.disconnectActivityFromServices();
+ // This activity record is removing, make sure not to disconnect twice.
+ mServiceConnectionsHolder = null;
}
- // Throw away any services that have been bound by this activity.
- mServiceConnectionsHolder.disconnectActivityFromServices();
- // This activity record is removing, make sure not to disconnect twice.
- mServiceConnectionsHolder = null;
+ }
+
+ private void updateVisibleForServiceConnection() {
+ mVisibleForServiceConnection = mVisibleRequested || mState == RESUMED || mState == PAUSING;
}
/**
@@ -5155,6 +5181,7 @@
boolean setVisibleRequested(boolean visible) {
if (!super.setVisibleRequested(visible)) return false;
setInsetsFrozen(!visible);
+ updateVisibleForServiceConnection();
if (app != null) {
mTaskSupervisor.onProcessActivityStateChanged(app, false /* forceBatch */);
}
@@ -5660,6 +5687,7 @@
return;
}
}
+ updateVisibleForServiceConnection();
if (app != null) {
mTaskSupervisor.onProcessActivityStateChanged(app, false /* forceBatch */);
}
diff --git a/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java b/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java
index 0859d40..5f56af7 100644
--- a/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java
+++ b/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java
@@ -16,14 +16,14 @@
package com.android.server.wm;
-import static com.android.server.wm.ActivityRecord.State.PAUSING;
-import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CLEANUP;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import android.util.ArraySet;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
+
import java.io.PrintWriter;
import java.util.function.Consumer;
@@ -38,8 +38,6 @@
*/
public class ActivityServiceConnectionsHolder<T> {
- private final ActivityTaskManagerService mService;
-
/** The activity the owns this service connection object. */
private final ActivityRecord mActivity;
@@ -49,19 +47,19 @@
* on the WM side since we don't perform operations on the object. Mainly here for communication
* and booking with the AM side.
*/
+ @GuardedBy("mActivity")
private ArraySet<T> mConnections;
/** Whether all connections of {@link #mActivity} are being removed. */
private volatile boolean mIsDisconnecting;
- ActivityServiceConnectionsHolder(ActivityTaskManagerService service, ActivityRecord activity) {
- mService = service;
+ ActivityServiceConnectionsHolder(ActivityRecord activity) {
mActivity = activity;
}
/** Adds a connection record that the activity has bound to a specific service. */
public void addConnection(T c) {
- synchronized (mService.mGlobalLock) {
+ synchronized (mActivity) {
if (mIsDisconnecting) {
// This is unlikely to happen because the caller should create a new holder.
if (DEBUG_CLEANUP) {
@@ -79,7 +77,7 @@
/** Removed a connection record between the activity and a specific service. */
public void removeConnection(T c) {
- synchronized (mService.mGlobalLock) {
+ synchronized (mActivity) {
if (mConnections == null) {
return;
}
@@ -90,20 +88,18 @@
}
}
+ /** @see android.content.Context#BIND_ADJUST_WITH_ACTIVITY */
public boolean isActivityVisible() {
- synchronized (mService.mGlobalLock) {
- return mActivity.isVisibleRequested() || mActivity.isState(RESUMED, PAUSING);
- }
+ return mActivity.mVisibleForServiceConnection;
}
public int getActivityPid() {
- synchronized (mService.mGlobalLock) {
- return mActivity.hasProcess() ? mActivity.app.getPid() : -1;
- }
+ final WindowProcessController wpc = mActivity.app;
+ return wpc != null ? wpc.getPid() : -1;
}
public void forEachConnection(Consumer<T> consumer) {
- synchronized (mService.mGlobalLock) {
+ synchronized (mActivity) {
if (mConnections == null || mConnections.isEmpty()) {
return;
}
@@ -118,6 +114,7 @@
* general, this method is used to clean up if the activity didn't unbind services before it
* is destroyed.
*/
+ @GuardedBy("mActivity")
void disconnectActivityFromServices() {
if (mConnections == null || mConnections.isEmpty() || mIsDisconnecting) {
return;
@@ -130,16 +127,14 @@
// still in the message queue, so keep the reference of {@link #mConnections} to make sure
// the connection list is up-to-date.
mIsDisconnecting = true;
- mService.mH.post(() -> {
- mService.mAmInternal.disconnectActivityFromServices(this);
+ mActivity.mAtmService.mH.post(() -> {
+ mActivity.mAtmService.mAmInternal.disconnectActivityFromServices(this);
mIsDisconnecting = false;
});
}
public void dump(PrintWriter pw, String prefix) {
- synchronized (mService.mGlobalLock) {
- pw.println(prefix + "activity=" + mActivity);
- }
+ pw.println(prefix + "activity=" + mActivity);
}
/** Used by {@link ActivityRecord#dump}. */
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 8d671f7..18be2a3 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -65,6 +65,7 @@
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_PIP;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
@@ -201,6 +202,7 @@
import android.os.PowerManager;
import android.os.PowerManagerInternal;
import android.os.Process;
+import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.StrictMode;
@@ -227,7 +229,6 @@
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
import android.view.IRecentsAnimationRunner;
-import android.view.IWindowFocusObserver;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationDefinition;
import android.view.WindowManager;
@@ -1852,11 +1853,11 @@
@Override
public BackNavigationInfo startBackNavigation(
- IWindowFocusObserver observer, BackAnimationAdapter adapter) {
+ RemoteCallback navigationObserver, BackAnimationAdapter adapter) {
mAmInternal.enforceCallingPermission(START_TASKS_FROM_RECENTS,
"startBackNavigation()");
- return mBackNavigationController.startBackNavigation(observer, adapter);
+ return mBackNavigationController.startBackNavigation(navigationObserver, adapter);
}
/**
@@ -2034,7 +2035,20 @@
return;
}
- if (r.moveFocusableActivityToTop("setFocusedTask")) {
+ final Transition transition = (getTransitionController().isCollecting()
+ || !getTransitionController().isShellTransitionsEnabled()) ? null
+ : getTransitionController().createTransition(TRANSIT_TO_FRONT);
+ if (transition != null) {
+ // Set ready before doing anything. If order does change, then that will set it unready
+ // so that we wait for the new lifecycles to complete.
+ transition.setReady(task, true /* ready */);
+ }
+ final boolean movedToTop = r.moveFocusableActivityToTop("setFocusedTask");
+ if (movedToTop) {
+ if (transition != null) {
+ getTransitionController().requestStartTransition(
+ transition, null /* startTask */, null /* remote */, null /* display */);
+ }
mRootWindowContainer.resumeFocusedTasksTopActivities();
} else if (touchedActivity != null && touchedActivity.isFocusable()) {
final TaskFragment parent = touchedActivity.getTaskFragment();
@@ -2046,6 +2060,10 @@
true /* updateInputWindows */);
}
}
+ if (transition != null && !movedToTop) {
+ // No order changes and focus-changes, alone, aren't captured in transitions.
+ transition.abort();
+ }
}
@Override
@@ -6081,18 +6099,11 @@
@Override
public ActivityServiceConnectionsHolder getServiceConnectionsHolder(IBinder token) {
- synchronized (mGlobalLock) {
- final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
- if (r == null) {
- return null;
- }
- if (r.mServiceConnectionsHolder == null) {
- r.mServiceConnectionsHolder = new ActivityServiceConnectionsHolder(
- ActivityTaskManagerService.this, r);
- }
-
- return r.mServiceConnectionsHolder;
+ final ActivityRecord r = ActivityRecord.forToken(token);
+ if (r == null || !r.inHistory) {
+ return null;
}
+ return r.getOrCreateServiceConnectionsHolder();
}
@Override
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 75c85d7..4e94f96 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -253,7 +253,7 @@
ArraySet<ActivityRecord> tmpOpenApps = mDisplayContent.mOpeningApps;
ArraySet<ActivityRecord> tmpCloseApps = mDisplayContent.mClosingApps;
- if (mDisplayContent.mAtmService.mBackNavigationController.isWaitBackTransition()) {
+ if (mDisplayContent.mAtmService.mBackNavigationController.isMonitoringTransition()) {
tmpOpenApps = new ArraySet<>(mDisplayContent.mOpeningApps);
tmpCloseApps = new ArraySet<>(mDisplayContent.mClosingApps);
if (mDisplayContent.mAtmService.mBackNavigationController
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index bc5f67b..a229fc5 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -41,7 +41,6 @@
import android.util.ArraySet;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
-import android.view.IWindowFocusObserver;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.WindowInsets;
@@ -66,11 +65,11 @@
class BackNavigationController {
private static final String TAG = "BackNavigationController";
private WindowManagerService mWindowManagerService;
- private IWindowFocusObserver mFocusObserver;
private boolean mBackAnimationInProgress;
private @BackNavigationInfo.BackTargetType int mLastBackType;
private boolean mShowWallpaper;
private Runnable mPendingAnimation;
+ private final NavigationMonitor mNavigationMonitor = new NavigationMonitor();
private AnimationHandler mAnimationHandler;
private final ArrayList<WindowContainer> mTmpOpenApps = new ArrayList<>();
@@ -86,6 +85,11 @@
return SystemProperties.getInt("persist.wm.debug.predictive_back_screenshot", 0) != 0;
}
+ // Notify focus window changed
+ void onFocusChanged(WindowState newFocus) {
+ mNavigationMonitor.onFocusWindowChanged(newFocus);
+ }
+
/**
* Set up the necessary leashes and build a {@link BackNavigationInfo} instance for an upcoming
* back gesture animation.
@@ -96,13 +100,12 @@
*/
@VisibleForTesting
@Nullable
- BackNavigationInfo startBackNavigation(
- IWindowFocusObserver observer, BackAnimationAdapter adapter) {
+ BackNavigationInfo startBackNavigation(@NonNull RemoteCallback navigationObserver,
+ BackAnimationAdapter adapter) {
if (!sPredictBackEnable) {
return null;
}
final WindowManagerService wmService = mWindowManagerService;
- mFocusObserver = observer;
int backType = BackNavigationInfo.TYPE_UNDEFINED;
@@ -202,9 +205,7 @@
backType = BackNavigationInfo.TYPE_CALLBACK;
}
infoBuilder.setOnBackInvokedCallback(callbackInfo.getCallback());
- if (mFocusObserver != null) {
- window.registerFocusObserver(mFocusObserver);
- }
+ mNavigationMonitor.startMonitor(window, navigationObserver);
}
ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "startBackNavigation currentTask=%s, "
@@ -228,10 +229,8 @@
|| currentActivity.isActivityTypeHome()
|| currentActivity.mHasSceneTransition) {
infoBuilder.setType(BackNavigationInfo.TYPE_CALLBACK);
- final WindowState finalFocusedWindow = window;
infoBuilder.setOnBackNavigationDone(new RemoteCallback(result ->
- onBackNavigationDone(result, finalFocusedWindow,
- BackNavigationInfo.TYPE_CALLBACK)));
+ onBackNavigationDone(result, BackNavigationInfo.TYPE_CALLBACK)));
mLastBackType = BackNavigationInfo.TYPE_CALLBACK;
return infoBuilder.build();
}
@@ -334,16 +333,19 @@
WindowContainer<?> finalRemovedWindowContainer = removedWindowContainer;
if (finalRemovedWindowContainer != null) {
final int finalBackType = backType;
- final WindowState finalFocusedWindow = window;
RemoteCallback onBackNavigationDone = new RemoteCallback(result -> onBackNavigationDone(
- result, finalFocusedWindow, finalBackType));
+ result, finalBackType));
infoBuilder.setOnBackNavigationDone(onBackNavigationDone);
}
mLastBackType = backType;
return infoBuilder.build();
}
- boolean isWaitBackTransition() {
+ boolean isMonitoringTransition() {
+ return isWaitBackTransition() || mNavigationMonitor.isMonitoring();
+ }
+
+ private boolean isWaitBackTransition() {
return mAnimationHandler.mComposed && mAnimationHandler.mWaitTransition;
}
@@ -363,11 +365,23 @@
*/
boolean removeIfContainsBackAnimationTargets(ArraySet<ActivityRecord> openApps,
ArraySet<ActivityRecord> closeApps) {
- if (!isWaitBackTransition()) {
+ if (!isMonitoringTransition()) {
return false;
}
mTmpCloseApps.addAll(closeApps);
- boolean result = false;
+ final boolean matchAnimationTargets = removeIfWaitForBackTransition(openApps, closeApps);
+ if (!matchAnimationTargets) {
+ mNavigationMonitor.onTransitionReadyWhileNavigate(mTmpOpenApps, mTmpCloseApps);
+ }
+ mTmpCloseApps.clear();
+ return matchAnimationTargets;
+ }
+
+ boolean removeIfWaitForBackTransition(ArraySet<ActivityRecord> openApps,
+ ArraySet<ActivityRecord> closeApps) {
+ if (!isWaitBackTransition()) {
+ return false;
+ }
// Note: TmpOpenApps is empty. Unlike shell transition, the open apps will be removed from
// mOpeningApps if there is no visibility change.
if (mAnimationHandler.containsBackAnimationTargets(mTmpOpenApps, mTmpCloseApps)) {
@@ -386,10 +400,76 @@
closeApps.removeAt(i);
}
}
- result = true;
+ return true;
}
- mTmpCloseApps.clear();
- return result;
+ return false;
+ }
+
+ private static class NavigationMonitor {
+ // The window which triggering the back navigation.
+ private WindowState mNavigatingWindow;
+ private RemoteCallback mObserver;
+
+ void startMonitor(@NonNull WindowState window, @NonNull RemoteCallback observer) {
+ mNavigatingWindow = window;
+ mObserver = observer;
+ }
+
+ void stopMonitor() {
+ mNavigatingWindow = null;
+ mObserver = null;
+ }
+
+ boolean isMonitoring() {
+ return mNavigatingWindow != null && mObserver != null;
+ }
+
+ /**
+ * Notify focus window changed during back navigation. This will cancel the gesture for
+ * scenarios like: a system window popup, or when an activity add a new window.
+ *
+ * This method should only be used to check window-level change, otherwise it may cause
+ * misjudgment in multi-window mode. For example: in split-screen, when user is
+ * navigating on the top task, bottom task can start a new task, which will gain focus for
+ * a short time, but we should not cancel the navigation.
+ */
+ private void onFocusWindowChanged(WindowState newFocus) {
+ if (!isMonitoring() || !atSameDisplay(newFocus)) {
+ return;
+ }
+ // Keep navigating if either new focus == navigating window or null.
+ if (newFocus != null && newFocus != mNavigatingWindow
+ && (newFocus.mActivityRecord == null
+ || (newFocus.mActivityRecord == mNavigatingWindow.mActivityRecord))) {
+ EventLogTags.writeWmBackNaviCanceled("focusWindowChanged");
+ mObserver.sendResult(null /* result */);
+ }
+ }
+
+ /**
+ * Notify an unexpected transition has happened during back navigation.
+ */
+ private void onTransitionReadyWhileNavigate(ArrayList<WindowContainer> opening,
+ ArrayList<WindowContainer> closing) {
+ if (!isMonitoring()) {
+ return;
+ }
+ final ArrayList<WindowContainer> all = new ArrayList<>(opening);
+ all.addAll(closing);
+ for (WindowContainer app : all) {
+ if (app.hasChild(mNavigatingWindow)) {
+ EventLogTags.writeWmBackNaviCanceled("transitionHappens");
+ mObserver.sendResult(null /* result */);
+ break;
+ }
+ }
+
+ }
+
+ private boolean atSameDisplay(WindowState newFocus) {
+ final int navigatingDisplayId = mNavigatingWindow.getDisplayId();
+ return newFocus == null || newFocus.getDisplayId() == navigatingDisplayId;
+ }
}
// For shell transition
@@ -403,8 +483,7 @@
* animations, and shouldn't join next transition.
*/
boolean containsBackAnimationTargets(Transition transition) {
- if (!mAnimationHandler.mComposed
- || (transition.mType != TRANSIT_CLOSE && transition.mType != TRANSIT_TO_BACK)) {
+ if (!isMonitoringTransition()) {
return false;
}
final ArraySet<WindowContainer> targets = transition.mParticipants;
@@ -420,19 +499,19 @@
mTmpCloseApps.add(wc);
}
}
- final boolean result = mAnimationHandler.containsBackAnimationTargets(
- mTmpOpenApps, mTmpCloseApps);
- if (result) {
- mAnimationHandler.mOpenTransitionTargetMatch =
- mAnimationHandler.containTarget(mTmpOpenApps, true);
+ final boolean matchAnimationTargets = isWaitBackTransition()
+ && (transition.mType == TRANSIT_CLOSE || transition.mType == TRANSIT_TO_BACK)
+ && mAnimationHandler.containsBackAnimationTargets(mTmpOpenApps, mTmpCloseApps);
+ if (!matchAnimationTargets) {
+ mNavigationMonitor.onTransitionReadyWhileNavigate(mTmpOpenApps, mTmpCloseApps);
}
mTmpOpenApps.clear();
mTmpCloseApps.clear();
- return result;
+ return matchAnimationTargets;
}
boolean isMonitorTransitionTarget(WindowContainer wc) {
- if (!mAnimationHandler.mComposed || !mAnimationHandler.mWaitTransition) {
+ if (!isWaitBackTransition()) {
return false;
}
return mAnimationHandler.isTarget(wc, wc.isVisibleRequested() /* open */);
@@ -951,16 +1030,13 @@
}
}
- private void onBackNavigationDone(Bundle result, WindowState focusedWindow, int backType) {
+ private void onBackNavigationDone(Bundle result, int backType) {
boolean triggerBack = result != null && result.getBoolean(
BackNavigationInfo.KEY_TRIGGER_BACK);
ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "onBackNavigationDone backType=%s, "
+ "triggerBack=%b", backType, triggerBack);
- if (mFocusObserver != null) {
- focusedWindow.unregisterFocusObserver(mFocusObserver);
- mFocusObserver = null;
- }
+ mNavigationMonitor.stopMonitor();
mBackAnimationInProgress = false;
mShowWallpaper = false;
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 17ec9cb..86c4e0f 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3809,6 +3809,7 @@
}
getDisplayPolicy().focusChangedLw(oldFocus, newFocus);
+ mAtmService.mBackNavigationController.onFocusChanged(newFocus);
if (imWindowChanged && oldFocus != mInputMethodWindow) {
// Focus of the input method window changed. Perform layout if needed.
@@ -4589,8 +4590,7 @@
*/
@VisibleForTesting
SurfaceControl computeImeParent() {
- if (!ImeTargetVisibilityPolicy.isValidToComputeImeParent(mImeLayeringTarget,
- mImeInputTarget)) {
+ if (!ImeTargetVisibilityPolicy.canComputeImeParent(mImeLayeringTarget, mImeInputTarget)) {
return null;
}
// Attach it to app if the target is part of an app and such app is covering the entire
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 2b34660..ce3379e 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -931,7 +931,7 @@
}
final InsetsSourceProvider provider = win.getControllableInsetProvider();
- if (provider != null && provider.getSource().getInsetsRoundedCornerFrame()
+ if (provider != null && provider.getSource().insetsRoundedCornerFrame()
!= attrs.insetsRoundedCornerFrame) {
provider.getSource().setInsetsRoundedCornerFrame(attrs.insetsRoundedCornerFrame);
}
@@ -1364,9 +1364,8 @@
applyKeyguardPolicy(win, imeTarget);
// Check if the freeform window overlaps with the navigation bar area.
- final boolean isOverlappingWithNavBar = isOverlappingWithNavBar(win);
- if (isOverlappingWithNavBar && !mIsFreeformWindowOverlappingWithNavBar
- && win.inFreeformWindowingMode()) {
+ if (!mIsFreeformWindowOverlappingWithNavBar && win.inFreeformWindowingMode()
+ && win.mActivityRecord != null && isOverlappingWithNavBar(win)) {
mIsFreeformWindowOverlappingWithNavBar = true;
}
@@ -1454,7 +1453,7 @@
// mode; if it's in gesture navigation mode, the navigation bar will be
// NAV_BAR_FORCE_TRANSPARENT and its appearance won't be decided by overlapping
// windows.
- if (isOverlappingWithNavBar) {
+ if (isOverlappingWithNavBar(win)) {
if (mNavBarColorWindowCandidate == null) {
mNavBarColorWindowCandidate = win;
addSystemBarColorApp(win);
@@ -1482,7 +1481,7 @@
addSystemBarColorApp(win);
}
}
- if (isOverlappingWithNavBar && mNavBarColorWindowCandidate == null) {
+ if (isOverlappingWithNavBar(win) && mNavBarColorWindowCandidate == null) {
mNavBarColorWindowCandidate = win;
}
}
@@ -2591,6 +2590,7 @@
lp.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+ lp.privateFlags |= LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
lp.setFitInsetsTypes(0);
lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
if (ActivityManager.isHighEndGfx()) {
@@ -2646,7 +2646,7 @@
@VisibleForTesting
static boolean isOverlappingWithNavBar(@NonNull WindowState win) {
- if (win.mActivityRecord == null || !win.isVisible()) {
+ if (!win.isVisible()) {
return false;
}
diff --git a/services/core/java/com/android/server/wm/EventLogTags.logtags b/services/core/java/com/android/server/wm/EventLogTags.logtags
index 031e022..594929b 100644
--- a/services/core/java/com/android/server/wm/EventLogTags.logtags
+++ b/services/core/java/com/android/server/wm/EventLogTags.logtags
@@ -66,6 +66,8 @@
# bootanim finished:
31007 wm_boot_animation_done (time|2|3)
+# Back navigation.
+31100 wm_back_navi_canceled (Reason|3)
# IME surface parent is updated.
32003 imf_update_ime_parent (surface name|3)
diff --git a/services/core/java/com/android/server/wm/ImeTargetVisibilityPolicy.java b/services/core/java/com/android/server/wm/ImeTargetVisibilityPolicy.java
index 471bdf2..71dd917 100644
--- a/services/core/java/com/android/server/wm/ImeTargetVisibilityPolicy.java
+++ b/services/core/java/com/android/server/wm/ImeTargetVisibilityPolicy.java
@@ -55,11 +55,14 @@
* @param imeInputTarget The window which start the input connection, receive input from IME.
* @return {@code true} to keep computing the ime parent, {@code false} to defer this operation
*/
- public static boolean isValidToComputeImeParent(@Nullable WindowState imeLayeringTarget,
+ public static boolean canComputeImeParent(@Nullable WindowState imeLayeringTarget,
@Nullable InputTarget imeInputTarget) {
if (imeLayeringTarget == null) {
return false;
}
+ if (shouldComputeImeParentForEmbeddedActivity(imeLayeringTarget, imeInputTarget)) {
+ return true;
+ }
// Ensure changing the IME parent when the layering target that may use IME has
// became to the input target for preventing IME flickers.
// Note that:
@@ -72,9 +75,6 @@
boolean imeLayeringTargetMayUseIme =
WindowManager.LayoutParams.mayUseInputMethod(imeLayeringTarget.mAttrs.flags)
|| imeLayeringTarget.mAttrs.type == TYPE_APPLICATION_STARTING;
- if (isImeTargetMismatchOnEmbedding(imeLayeringTarget, imeInputTarget)) {
- return true;
- }
// Do not change parent if the window hasn't requested IME.
var inputAndLayeringTargetsDisagree = (imeInputTarget == null
|| imeLayeringTarget.mActivityRecord != imeInputTarget.getActivityRecord());
@@ -83,26 +83,45 @@
return !inputTargetStale;
}
- private static boolean isImeTargetMismatchOnEmbedding(
+
+ /**
+ * Called from {@link DisplayContent#computeImeParent()} to check the given IME targets if the
+ * IME surface parent should be updated in ActivityEmbeddings.
+ *
+ * As the IME layering target is calculated according to the window hierarchy by
+ * {@link DisplayContent#computeImeTarget}, the layering target and input target may be
+ * different when the window hasn't started input connection, WindowManagerService hasn't yet
+ * received the input target which reported from InputMethodManagerService. To make the IME
+ * surface will be shown on the best fit IME layering target, we basically won't update IME
+ * parent until both IME input and layering target updated for better IME transition.
+ *
+ * However, in activity embedding, tapping a window won't update it to the top window so the
+ * calculated IME layering target may higher than input target. Update IME parent for this case.
+ *
+ * @return {@code true} means the layer of IME layering target is higher than the input target
+ * and {@link DisplayContent#computeImeParent()} should keep progressing to update the IME
+ * surface parent on the display in case the IME surface left behind.
+ */
+ private static boolean shouldComputeImeParentForEmbeddedActivity(
@Nullable WindowState imeLayeringTarget, @Nullable InputTarget imeInputTarget) {
if (imeInputTarget == null || imeLayeringTarget == null) {
return false;
}
- final ActivityRecord inputTargetRecord = imeInputTarget.getActivityRecord();
- final ActivityRecord layeringTargetRecord = imeLayeringTarget.getActivityRecord();
final WindowState inputTargetWindow = imeInputTarget.getWindowState();
- if (inputTargetRecord == null || layeringTargetRecord == null
- || inputTargetWindow == null) {
+ if (inputTargetWindow == null || !imeLayeringTarget.isAttached()
+ || !inputTargetWindow.isAttached()) {
return false;
}
- final boolean isImeTargetEmbedded = inputTargetRecord.isEmbedded()
- && layeringTargetRecord.isEmbedded();
- // The IME layering target is calculated by the window hierarchy in DisplayContent.
- // The layering target and input target may be different when the window hasn't started
- // input connection, WMS hasn't received the target which reported from IMMS. We basically
- // won't update IME parent for better IME transition.
- // But in activity embedding, tapping a window won't update it to the top window so the IME
- // layering target may higher than input target. Update IME parent for this case.
- return isImeTargetEmbedded && imeLayeringTarget.compareTo(inputTargetWindow) > 0;
+
+ final ActivityRecord inputTargetRecord = imeInputTarget.getActivityRecord();
+ final ActivityRecord layeringTargetRecord = imeLayeringTarget.getActivityRecord();
+ if (inputTargetRecord == null || layeringTargetRecord == null
+ || inputTargetRecord == layeringTargetRecord
+ || (inputTargetRecord.getTask() != layeringTargetRecord.getTask())
+ || !inputTargetRecord.isEmbedded() || !layeringTargetRecord.isEmbedded()) {
+ // Check whether the input target and layering target are embedded in the same Task.
+ return false;
+ }
+ return imeLayeringTarget.compareTo(inputTargetWindow) > 0;
}
}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index e1dbe01a..dc674c2 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -97,6 +97,7 @@
import android.view.RoundedCorner;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
+import android.view.WindowInsets;
import android.view.WindowManager;
import com.android.internal.R;
@@ -131,12 +132,6 @@
private final ActivityRecord mActivityRecord;
- /**
- * Taskbar expanded height. Used to determine when to crop an app window to display the
- * rounded corners above the expanded taskbar.
- */
- private final float mExpandedTaskBarHeight;
-
// TODO(b/265576778): Cache other overrides as well.
// Corresponds to OVERRIDE_ANY_ORIENTATION
@@ -253,9 +248,6 @@
/* checkDeviceConfig */ true),
PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE);
- mExpandedTaskBarHeight =
- getResources().getDimensionPixelSize(R.dimen.taskbar_frame_height);
-
mBooleanPropertyAllowOrientationOverride =
readComponentProperty(packageManager, mActivityRecord.packageName,
/* gatingCondition */ null,
@@ -1138,11 +1130,13 @@
@VisibleForTesting
@Nullable
InsetsSource getExpandedTaskbarOrNull(final WindowState mainWindow) {
- final InsetsSource taskbar = mainWindow.getInsetsState().peekSource(
- InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
- if (taskbar != null && taskbar.isVisible()
- && taskbar.getFrame().height() >= mExpandedTaskBarHeight) {
- return taskbar;
+ final InsetsState state = mainWindow.getInsetsState();
+ for (int i = state.sourceSize() - 1; i >= 0; i--) {
+ final InsetsSource source = state.sourceAt(i);
+ if (source.getType() == WindowInsets.Type.navigationBars()
+ && source.insetsRoundedCornerFrame() && source.isVisible()) {
+ return source;
+ }
}
return null;
}
diff --git a/services/core/java/com/android/server/wm/OWNERS b/services/core/java/com/android/server/wm/OWNERS
index 4f506a5..07f3bc6 100644
--- a/services/core/java/com/android/server/wm/OWNERS
+++ b/services/core/java/com/android/server/wm/OWNERS
@@ -14,6 +14,7 @@
tigerhuang@google.com
lihongyu@google.com
mariiasand@google.com
+rgl@google.com
per-file BackgroundActivityStartController.java = set noparent
per-file BackgroundActivityStartController.java = brufino@google.com, ogunwale@google.com, louischang@google.com, lus@google.com, rickywai@google.com
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 2af5460..cc81d52 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -2825,19 +2825,23 @@
* Account for specified insets to crop the animation bounds by to avoid the animation
* occurring over "out of bounds" regions
*
- * For example this is used to make sure the tasks are cropped to be fully above the
+ * For example this is used to make sure the tasks are cropped to be fully above the expanded
* taskbar when animating.
*
- * @param animationBounds The animations bounds to adjust to account for the custom spec insets.
+ * TEMPORARY FIELD (b/202383002)
+ * TODO: Remove once we use surfaceflinger rounded corners on tasks rather than taskbar overlays
+ * or when shell transitions are fully enabled
+ *
+ * @param animationBounds The animation bounds to adjust to account for the custom spec insets.
*/
void adjustAnimationBoundsForTransition(Rect animationBounds) {
TaskTransitionSpec spec = mWmService.mTaskTransitionSpec;
if (spec != null) {
final InsetsState state =
getDisplayContent().getInsetsStateController().getRawInsetsState();
- for (int id : spec.animationBoundInsets) {
- final InsetsSource source = state.peekSource(id);
- if (source != null) {
+ for (int i = state.sourceSize() - 1; i >= 0; i--) {
+ final InsetsSource source = state.sourceAt(i);
+ if (source.insetsRoundedCornerFrame()) {
animationBounds.inset(source.calculateVisibleInsets(animationBounds));
}
}
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 8cbd553..3cec3aa 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -1225,7 +1225,7 @@
// Clear last paused activity if focused root task changed while sleeping, so that the
// top activity of current focused task can be resumed.
- if (mDisplayContent.isSleeping()) {
+ if (mDisplayContent.isSleeping() && currentFocusedTask != null) {
currentFocusedTask.clearLastPausedActivity();
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index c2afeaf..808e79d 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1933,7 +1933,7 @@
1f);
mBackScreenshots.put(r.mActivityComponent.flattenToString(), backBuffer);
}
- child.asActivityRecord().inHistory = true;
+ addingActivity.inHistory = true;
task.onDescendantActivityAdded(taskHadActivity, activityType, addingActivity);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index bb706ec..da7400c 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -123,7 +123,6 @@
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
-import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
import java.util.function.Consumer;
@@ -3193,8 +3192,7 @@
animationRunnerBuilder.setTaskBackgroundColor(getTaskAnimationBackgroundColor());
// TODO: Remove when we migrate to shell (b/202383002)
if (mWmService.mTaskTransitionSpec != null) {
- animationRunnerBuilder.hideInsetSourceViewOverflows(
- mWmService.mTaskTransitionSpec.animationBoundInsets);
+ animationRunnerBuilder.hideInsetSourceViewOverflows();
}
}
@@ -4115,11 +4113,12 @@
}
}
- private void hideInsetSourceViewOverflows(Set<Integer> sourceIds) {
- final InsetsStateController controller = getDisplayContent().getInsetsStateController();
- for (int id : sourceIds) {
- final InsetsSourceProvider insetProvider = controller.peekSourceProvider(id);
- if (insetProvider == null) {
+ private void hideInsetSourceViewOverflows() {
+ final SparseArray<WindowContainerInsetsSourceProvider> providers =
+ getDisplayContent().getInsetsStateController().getSourceProviders();
+ for (int i = providers.size(); i >= 0; i--) {
+ final InsetsSourceProvider insetProvider = providers.valueAt(i);
+ if (!insetProvider.getSource().insetsRoundedCornerFrame()) {
return;
}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java b/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java
index 3d504ef..9c50a5a 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java
@@ -49,11 +49,14 @@
/** Represents the results of a given query into the registry. */
public static final class FilterResult {
final String mPackageName;
+ final String mFlattenedRequest;
final List<CredentialEntry> mCredentialEntries;
private FilterResult(String packageName,
+ String flattenedRequest,
List<CredentialEntry> credentialEntries) {
mPackageName = packageName;
+ mFlattenedRequest = flattenedRequest;
mCredentialEntries = credentialEntries;
}
}
@@ -133,12 +136,13 @@
/** Returns package names and entries of a CredentialProviders that can satisfy a given
* {@link CredentialDescription}. */
public Set<FilterResult> getFilteredResultForProvider(String packageName,
- List<String> flatRequestStrings) {
+ String flatRequestStrings) {
Set<FilterResult> result = new HashSet<>();
Set<CredentialDescription> currentSet = mCredentialDescriptions.get(packageName);
for (CredentialDescription containedDescription: currentSet) {
- if (flatRequestStrings.contains(containedDescription.getFlattenedRequestString())) {
- result.add(new FilterResult(packageName, containedDescription
+ if (flatRequestStrings.equals(containedDescription.getFlattenedRequestString())) {
+ result.add(new FilterResult(packageName,
+ containedDescription.getFlattenedRequestString(), containedDescription
.getCredentialEntries()));
}
}
@@ -147,13 +151,15 @@
/** Returns package names of CredentialProviders that can satisfy a given
* {@link CredentialDescription}. */
- public Set<String> getMatchingProviders(Set<String> flatRequestString) {
- Set<String> result = new HashSet<>();
+ public Set<FilterResult> getMatchingProviders(Set<String> flatRequestString) {
+ Set<FilterResult> result = new HashSet<>();
for (String packageName: mCredentialDescriptions.keySet()) {
Set<CredentialDescription> currentSet = mCredentialDescriptions.get(packageName);
for (CredentialDescription containedDescription : currentSet) {
if (flatRequestString.contains(containedDescription.getFlattenedRequestString())) {
- result.add(packageName);
+ result.add(new FilterResult(packageName,
+ containedDescription.getFlattenedRequestString(), containedDescription
+ .getCredentialEntries()));
}
}
}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index cce12a2..7a4e7df 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -17,6 +17,7 @@
package com.android.server.credentials;
import static android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN;
+import static android.Manifest.permission.LAUNCH_CREDENTIAL_SELECTOR;
import static android.content.Context.CREDENTIAL_SERVICE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -58,6 +59,7 @@
import android.service.credentials.CredentialProviderInfo;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
@@ -66,6 +68,7 @@
import com.android.server.infra.SecureSettingsServiceNameResolver;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
@@ -274,24 +277,25 @@
// to be guarded by 'service.mLock', which is the same as mLock.
private List<ProviderSession> initiateProviderSessionsWithActiveContainers(
GetRequestSession session,
- List<String> requestOptions,
- Set<String> activeCredentialContainers) {
+ Set<Pair<CredentialOption, CredentialDescriptionRegistry.FilterResult>>
+ activeCredentialContainers) {
List<ProviderSession> providerSessions = new ArrayList<>();
- // Invoke all services of a user to initiate a provider session
- for (String packageName : activeCredentialContainers) {
+ for (Pair<CredentialOption, CredentialDescriptionRegistry.FilterResult> result :
+ activeCredentialContainers) {
providerSessions.add(
ProviderRegistryGetSession.createNewSession(
mContext,
UserHandle.getCallingUserId(),
session,
- packageName,
- requestOptions));
+ result.second.mPackageName,
+ result.first));
}
return providerSessions;
}
@NonNull
- private Set<String> getFilteredResultFromRegistry(List<CredentialOption> options) {
+ private Set<Pair<CredentialOption, CredentialDescriptionRegistry.FilterResult>>
+ getFilteredResultFromRegistry(List<CredentialOption> options) {
// Session for active/provisioned credential descriptions;
CredentialDescriptionRegistry registry =
CredentialDescriptionRegistry.forUser(UserHandle.getCallingUserId());
@@ -307,7 +311,22 @@
.collect(Collectors.toSet());
// All requested credential descriptions based on the given request.
- return registry.getMatchingProviders(requestedCredentialDescriptions);
+ Set<CredentialDescriptionRegistry.FilterResult> filterResults =
+ registry.getMatchingProviders(requestedCredentialDescriptions);
+
+ Set<Pair<CredentialOption, CredentialDescriptionRegistry.FilterResult>> result =
+ new HashSet<>();
+
+ for (CredentialDescriptionRegistry.FilterResult filterResult: filterResults) {
+ for (CredentialOption credentialOption: options) {
+ if (filterResult.mFlattenedRequest.equals(credentialOption
+ .getCredentialRetrievalData()
+ .getString(CredentialOption.FLATTENED_REQUEST))) {
+ result.add(new Pair<>(credentialOption, filterResult));
+ }
+ }
+ }
+ return result;
}
@SuppressWarnings("GuardedBy") // ErrorProne requires initiateProviderSessionForRequestLocked
@@ -342,21 +361,22 @@
int userId,
@Nullable String origin) {
final PackageInfo packageInfo;
- String actualPackageName = origin == null ? realPackageName : origin;
+ CallingAppInfo callingAppInfo;
try {
packageInfo =
getContext()
.getPackageManager()
.getPackageInfoAsUser(
- actualPackageName,
+ realPackageName,
PackageManager.PackageInfoFlags.of(
PackageManager.GET_SIGNING_CERTIFICATES),
userId);
+ callingAppInfo = new CallingAppInfo(realPackageName, packageInfo.signingInfo, origin);
} catch (PackageManager.NameNotFoundException e) {
Log.i(TAG, "Issue while retrieving signatureInfo : " + e.getMessage());
- return new CallingAppInfo(actualPackageName, null);
+ callingAppInfo = new CallingAppInfo(realPackageName, null, origin);
}
- return new CallingAppInfo(actualPackageName, packageInfo.signingInfo);
+ return callingAppInfo;
}
final class CredentialManagerServiceStub extends ICredentialManager.Stub {
@@ -368,9 +388,15 @@
Log.i(TAG, "starting executeGetCredential with callingPackage: " + callingPackage);
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+ if (request.getOrigin() != null) {
+ // Check privileged permissions
+ mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ORIGIN, null);
+ }
+
final int userId = UserHandle.getCallingUserId();
final int callingUid = Binder.getCallingUid();
enforceCallingPackage(callingPackage, callingUid);
+
// New request session, scoped for this request only.
final GetRequestSession session =
new GetRequestSession(
@@ -379,51 +405,20 @@
callingUid,
callback,
request,
- constructCallingAppInfo(callingPackage, userId, null),
+ constructCallingAppInfo(callingPackage, userId, request.getOrigin()),
CancellationSignal.fromTransport(cancelTransport));
processGetCredential(request, callback, session);
return cancelTransport;
}
- public ICancellationSignal executeGetCredentialWithOrigin(
- GetCredentialRequest request,
- IGetCredentialCallback callback,
- final String callingPackage,
- final String origin) {
- Log.i(TAG, "starting executeGetCredential with callingPackage: " + callingPackage);
- ICancellationSignal cancelTransport = CancellationSignal.createTransport();
-
- // Check privileged permissions
- mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ORIGIN, null);
-
- final int userId = UserHandle.getCallingUserId();
- final int callingUid = Binder.getCallingUid();
- enforceCallingPackage(callingPackage, callingUid);
-
- // New request session, scoped for this request only.
- final GetRequestSession session =
- new GetRequestSession(
- getContext(),
- userId,
- callingUid,
- callback,
- request,
- constructCallingAppInfo(callingPackage, userId, origin),
- CancellationSignal.fromTransport(cancelTransport));
-
- processGetCredential(request, callback, session);
- return cancelTransport;
- }
-
private void processGetCredential(
GetCredentialRequest request,
IGetCredentialCallback callback,
GetRequestSession session) {
List<ProviderSession> providerSessions;
- // TODO(b/268143699): temporarily disable the flag due to bug.
- if (false) {
+ if (isCredentialDescriptionApiEnabled()) {
List<CredentialOption> optionsThatRequireActiveCredentials =
request.getCredentialOptions().stream()
.filter(
@@ -453,15 +448,6 @@
List<ProviderSession> sessionsWithoutRemoteService =
initiateProviderSessionsWithActiveContainers(
session,
- optionsThatRequireActiveCredentials.stream()
- .map(
- getCredentialOption ->
- getCredentialOption
- .getCredentialRetrievalData()
- .getString(
- CredentialOption
- .FLATTENED_REQUEST))
- .collect(Collectors.toList()),
getFilteredResultFromRegistry(optionsThatRequireActiveCredentials));
List<ProviderSession> sessionsWithRemoteService =
@@ -511,6 +497,11 @@
+ callingPackage);
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+ if (request.getOrigin() != null) {
+ // Check privileged permissions
+ mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ORIGIN, null);
+ }
+
final int userId = UserHandle.getCallingUserId();
final int callingUid = Binder.getCallingUid();
enforceCallingPackage(callingPackage, callingUid);
@@ -523,43 +514,13 @@
callingUid,
request,
callback,
- constructCallingAppInfo(callingPackage, userId, null),
+ constructCallingAppInfo(callingPackage, userId, request.getOrigin()),
CancellationSignal.fromTransport(cancelTransport));
processCreateCredential(request, callback, session);
return cancelTransport;
}
- public ICancellationSignal executeCreateCredentialWithOrigin(
- CreateCredentialRequest request,
- ICreateCredentialCallback callback,
- String callingPackage,
- String origin) {
- Log.i(TAG, "starting executeCreateCredential with callingPackage: " + callingPackage);
- ICancellationSignal cancelTransport = CancellationSignal.createTransport();
-
- // Check privileged permissions
- mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ORIGIN, null);
-
- final int userId = UserHandle.getCallingUserId();
- final int callingUid = Binder.getCallingUid();
- enforceCallingPackage(callingPackage, callingUid);
-
- // New request session, scoped for this request only.
- final CreateRequestSession session =
- new CreateRequestSession(
- getContext(),
- userId,
- callingUid,
- request,
- callback,
- constructCallingAppInfo(callingPackage, userId, origin),
- CancellationSignal.fromTransport(cancelTransport));
-
- processCreateCredential(request, callback, session);
- return cancelTransport;
- }
-
private void processCreateCredential(
CreateCredentialRequest request,
ICreateCredentialCallback callback,
@@ -671,7 +632,8 @@
}
// Send an intent to the UI that we have new enabled providers.
- getContext().sendBroadcast(IntentFactory.createProviderUpdateIntent());
+ getContext().sendBroadcast(IntentFactory.createProviderUpdateIntent(),
+ LAUNCH_CREDENTIAL_SELECTOR);
}
@Override
@@ -776,6 +738,10 @@
throws IllegalArgumentException, NonCredentialProviderCallerException {
Log.i(TAG, "registerCredentialDescription");
+ if (!isCredentialDescriptionApiEnabled()) {
+ throw new UnsupportedOperationException();
+ }
+
enforceCallingPackage(callingPackage, Binder.getCallingUid());
List<CredentialProviderInfo> services =
@@ -828,6 +794,10 @@
throws IllegalArgumentException {
Log.i(TAG, "registerCredentialDescription");
+ if (!isCredentialDescriptionApiEnabled()) {
+ throw new UnsupportedOperationException();
+ }
+
enforceCallingPackage(callingPackage, Binder.getCallingUid());
List<CredentialProviderInfo> services =
diff --git a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
index 5af8080..a57cb5f 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
@@ -23,7 +23,6 @@
import android.content.Intent;
import android.credentials.CredentialOption;
import android.credentials.GetCredentialException;
-import android.credentials.GetCredentialRequest;
import android.credentials.GetCredentialResponse;
import android.credentials.ui.Entry;
import android.credentials.ui.GetCredentialProviderData;
@@ -49,7 +48,7 @@
*
* @hide
*/
-public class ProviderRegistryGetSession extends ProviderSession<GetCredentialRequest,
+public class ProviderRegistryGetSession extends ProviderSession<CredentialOption,
Set<CredentialDescriptionRegistry.FilterResult>> {
private static final String TAG = "ProviderRegistryGetSession";
@@ -62,15 +61,14 @@
@UserIdInt int userId,
@NonNull GetRequestSession getRequestSession,
@NonNull String credentialProviderPackageName,
- @NonNull List<String> requestOptions) {
+ @NonNull CredentialOption requestOption) {
return new ProviderRegistryGetSession(
context,
userId,
getRequestSession,
- getRequestSession.mClientRequest,
getRequestSession.mClientAppInfo,
credentialProviderPackageName,
- requestOptions);
+ requestOption);
}
@NonNull
@@ -82,24 +80,22 @@
@NonNull
private final String mCredentialProviderPackageName;
@NonNull
- private final GetRequestSession mGetRequestSession;
- @NonNull
- private final List<String> mRequestOptions;
+ private final String mFlattenedRequestOptionString;
private List<CredentialEntry> mCredentialEntries;
protected ProviderRegistryGetSession(@NonNull Context context,
@NonNull int userId,
@NonNull GetRequestSession session,
- @NonNull GetCredentialRequest request,
@NonNull CallingAppInfo callingAppInfo,
@NonNull String servicePackageName,
- @NonNull List<String> requestOptions) {
- super(context, null, request, session, userId, null);
- mGetRequestSession = session;
+ @NonNull CredentialOption requestOption) {
+ super(context, null, requestOption, session, userId, null);
mCredentialDescriptionRegistry = CredentialDescriptionRegistry.forUser(userId);
mCallingAppInfo = callingAppInfo;
mCredentialProviderPackageName = servicePackageName;
- mRequestOptions = requestOptions;
+ mFlattenedRequestOptionString = requestOption
+ .getCredentialRetrievalData()
+ .getString(CredentialOption.FLATTENED_REQUEST);
}
private List<Entry> prepareUiCredentialEntries(
@@ -114,23 +110,18 @@
Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId);
credentialUiEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId,
credentialEntry.getSlice(),
- setUpFillInIntent(credentialEntry.getType())));
+ setUpFillInIntent()));
}
return credentialUiEntries;
}
- private Intent setUpFillInIntent(String type) {
+ private Intent setUpFillInIntent() {
Intent intent = new Intent();
- for (CredentialOption option : mProviderRequest.getCredentialOptions()) {
- if (option.getType().equals(type)) {
- intent.putExtra(
- CredentialProviderService
- .EXTRA_GET_CREDENTIAL_REQUEST,
- new android.service.credentials.GetCredentialRequest(
- mCallingAppInfo, option));
- return intent;
- }
- }
+ intent.putExtra(
+ CredentialProviderService
+ .EXTRA_GET_CREDENTIAL_REQUEST,
+ new android.service.credentials.GetCredentialRequest(
+ mCallingAppInfo, mProviderRequest));
return intent;
}
@@ -176,11 +167,6 @@
private void onCredentialEntrySelected(CredentialEntry credentialEntry,
ProviderPendingIntentResponse providerPendingIntentResponse) {
- if (!mCredentialEntries.contains(credentialEntry)) {
- invokeCallbackWithError("",
- "");
- }
-
if (providerPendingIntentResponse != null) {
// Check if pending intent has an error
GetCredentialException exception = maybeGetPendingIntentException(
@@ -228,13 +214,13 @@
protected void invokeSession() {
mProviderResponse = mCredentialDescriptionRegistry
.getFilteredResultForProvider(mCredentialProviderPackageName,
- mRequestOptions);
+ mFlattenedRequestOptionString);
mCredentialEntries = mProviderResponse.stream().flatMap(
(Function<CredentialDescriptionRegistry.FilterResult,
Stream<CredentialEntry>>) filterResult
-> filterResult.mCredentialEntries.stream())
.collect(Collectors.toList());
- setStatus(Status.CREDENTIALS_RECEIVED);
+ updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED);
// TODO(use metric later)
}
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index 0ca4dfc..17e1744 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -113,6 +113,7 @@
private static final long USAGE_STATS_QUERY_INTERVAL_SEC = 120L;
@VisibleForTesting
static final int MAX_CACHED_RECENT_SHORTCUTS = 30;
+ private static final int DEBOUNCE_LENGTH_MS = 500;
private final Context mContext;
private final Injector mInjector;
@@ -129,6 +130,7 @@
private final List<PeopleService.ConversationsListener> mConversationsListeners =
new ArrayList<>(1);
private final Handler mHandler;
+ private final PerPackageThrottler mShortcutsThrottler;
private ContentObserver mCallLogContentObserver;
private ContentObserver mMmsSmsContentObserver;
@@ -140,14 +142,17 @@
private ConversationStatusExpirationBroadcastReceiver mStatusExpReceiver;
public DataManager(Context context) {
- this(context, new Injector(), BackgroundThread.get().getLooper());
+ this(context, new Injector(), BackgroundThread.get().getLooper(),
+ new PerPackageThrottlerImpl(BackgroundThread.getHandler(), DEBOUNCE_LENGTH_MS));
}
- DataManager(Context context, Injector injector, Looper looper) {
+ DataManager(Context context, Injector injector, Looper looper,
+ PerPackageThrottler shortcutsThrottler) {
mContext = context;
mInjector = injector;
mScheduledExecutor = mInjector.createScheduledExecutor();
mHandler = new Handler(looper);
+ mShortcutsThrottler = shortcutsThrottler;
}
/** Initialization. Called when the system services are up running. */
@@ -851,12 +856,12 @@
// pair of <package name, conversation info>
List<Pair<String, ConversationInfo>> cachedConvos = new ArrayList<>();
userData.forAllPackages(packageData -> {
- packageData.forAllConversations(conversationInfo -> {
- if (isEligibleForCleanUp(conversationInfo)) {
- cachedConvos.add(
- Pair.create(packageData.getPackageName(), conversationInfo));
- }
- });
+ packageData.forAllConversations(conversationInfo -> {
+ if (isEligibleForCleanUp(conversationInfo)) {
+ cachedConvos.add(
+ Pair.create(packageData.getPackageName(), conversationInfo));
+ }
+ });
});
if (cachedConvos.size() <= targetCachedCount) {
return;
@@ -867,8 +872,8 @@
numToUncache + 1,
Comparator.comparingLong((Pair<String, ConversationInfo> pair) ->
Math.max(
- pair.second.getLastEventTimestamp(),
- pair.second.getCreationTimestamp())).reversed());
+ pair.second.getLastEventTimestamp(),
+ pair.second.getCreationTimestamp())).reversed());
for (Pair<String, ConversationInfo> cached : cachedConvos) {
if (hasActiveNotifications(cached.first, userId, cached.second.getShortcutId())) {
continue;
@@ -1104,26 +1109,35 @@
@Override
public void onShortcutsAddedOrUpdated(@NonNull String packageName,
@NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
- mInjector.getBackgroundExecutor().execute(() -> {
- PackageData packageData = getPackage(packageName, user.getIdentifier());
- for (ShortcutInfo shortcut : shortcuts) {
- if (ShortcutHelper.isConversationShortcut(
- shortcut, mShortcutServiceInternal, user.getIdentifier())) {
- if (shortcut.isCached()) {
- ConversationInfo conversationInfo = packageData != null
- ? packageData.getConversationInfo(shortcut.getId()) : null;
- if (conversationInfo == null
- || !conversationInfo.isShortcutCachedForNotification()) {
- // This is a newly cached shortcut. Clean up the existing cached
- // shortcuts to ensure the cache size is under the limit.
- cleanupCachedShortcuts(user.getIdentifier(),
- MAX_CACHED_RECENT_SHORTCUTS - 1);
+ mShortcutsThrottler.scheduleDebounced(
+ new Pair<>(packageName, user.getIdentifier()),
+ () -> {
+ PackageData packageData = getPackage(packageName, user.getIdentifier());
+ List<ShortcutInfo> queriedShortcuts = getShortcuts(packageName,
+ user.getIdentifier(), null);
+ boolean hasCachedShortcut = false;
+ for (ShortcutInfo shortcut : queriedShortcuts) {
+ if (ShortcutHelper.isConversationShortcut(
+ shortcut, mShortcutServiceInternal, user.getIdentifier())) {
+ if (shortcut.isCached()) {
+ ConversationInfo info = packageData != null
+ ? packageData.getConversationInfo(shortcut.getId())
+ : null;
+ if (info == null
+ || !info.isShortcutCachedForNotification()) {
+ hasCachedShortcut = true;
+ }
+ }
+ addOrUpdateConversationInfo(shortcut);
}
}
- addOrUpdateConversationInfo(shortcut);
- }
- }
- });
+ // Added at least one new conversation. Uncache older existing cached
+ // shortcuts to ensure the cache size is under the limit.
+ if (hasCachedShortcut) {
+ cleanupCachedShortcuts(user.getIdentifier(),
+ MAX_CACHED_RECENT_SHORTCUTS);
+ }
+ });
}
@Override
diff --git a/services/people/java/com/android/server/people/data/PerPackageThrottler.java b/services/people/java/com/android/server/people/data/PerPackageThrottler.java
new file mode 100644
index 0000000..3d6cd84
--- /dev/null
+++ b/services/people/java/com/android/server/people/data/PerPackageThrottler.java
@@ -0,0 +1,28 @@
+/*
+ * 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.server.people.data;
+
+import android.util.Pair;
+
+/** The interface for throttling expensive runnables per package. */
+interface PerPackageThrottler {
+ /**
+ * Schedule a runnable to run in the future, and debounce runnables for same {@code pkgUserId}
+ * that occur until that future has run.
+ */
+ void scheduleDebounced(Pair<String, Integer> pkgUserId, Runnable runnable);
+}
diff --git a/services/people/java/com/android/server/people/data/PerPackageThrottlerImpl.java b/services/people/java/com/android/server/people/data/PerPackageThrottlerImpl.java
new file mode 100644
index 0000000..fa5a67b
--- /dev/null
+++ b/services/people/java/com/android/server/people/data/PerPackageThrottlerImpl.java
@@ -0,0 +1,52 @@
+/*
+ * 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.server.people.data;
+
+import android.os.Handler;
+import android.util.Pair;
+
+import java.util.HashSet;
+
+/**
+ * A class that implements a per-package throttler that prevents a runnable from executing more than
+ * once every {@code debounceTime}.
+ */
+public class PerPackageThrottlerImpl implements PerPackageThrottler {
+ private final Handler mBackgroundHandler;
+ private final int mDebounceTime;
+ private final HashSet<Pair<String, Integer>> mPkgScheduledTasks = new HashSet<>();
+
+ PerPackageThrottlerImpl(Handler backgroundHandler, int debounceTime) {
+ mBackgroundHandler = backgroundHandler;
+ mDebounceTime = debounceTime;
+ }
+
+ @Override
+ public synchronized void scheduleDebounced(
+ Pair<String, Integer> pkgUserId, Runnable runnable) {
+ if (mPkgScheduledTasks.contains(pkgUserId)) {
+ return;
+ }
+ mPkgScheduledTasks.add(pkgUserId);
+ mBackgroundHandler.postDelayed(() -> {
+ synchronized (this) {
+ mPkgScheduledTasks.remove(pkgUserId);
+ runnable.run();
+ }
+ }, mDebounceTime);
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/ExpectableTestCase.java b/services/tests/mockingservicestests/src/com/android/server/ExpectableTestCase.java
new file mode 100644
index 0000000..a329f5a
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/ExpectableTestCase.java
@@ -0,0 +1,181 @@
+/*
+ * 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.server;
+
+import com.google.common.annotations.GwtIncompatible;
+import com.google.common.base.Optional;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Multiset;
+import com.google.common.collect.Table;
+import com.google.common.truth.BigDecimalSubject;
+import com.google.common.truth.BooleanSubject;
+import com.google.common.truth.ClassSubject;
+import com.google.common.truth.ComparableSubject;
+import com.google.common.truth.DoubleSubject;
+import com.google.common.truth.Expect;
+import com.google.common.truth.FloatSubject;
+import com.google.common.truth.GuavaOptionalSubject;
+import com.google.common.truth.IntegerSubject;
+import com.google.common.truth.IterableSubject;
+import com.google.common.truth.LongSubject;
+import com.google.common.truth.MapSubject;
+import com.google.common.truth.MultimapSubject;
+import com.google.common.truth.MultisetSubject;
+import com.google.common.truth.ObjectArraySubject;
+import com.google.common.truth.PrimitiveBooleanArraySubject;
+import com.google.common.truth.PrimitiveByteArraySubject;
+import com.google.common.truth.PrimitiveCharArraySubject;
+import com.google.common.truth.PrimitiveDoubleArraySubject;
+import com.google.common.truth.PrimitiveFloatArraySubject;
+import com.google.common.truth.PrimitiveIntArraySubject;
+import com.google.common.truth.PrimitiveLongArraySubject;
+import com.google.common.truth.PrimitiveShortArraySubject;
+import com.google.common.truth.StandardSubjectBuilder;
+import com.google.common.truth.StringSubject;
+import com.google.common.truth.Subject;
+import com.google.common.truth.TableSubject;
+import com.google.common.truth.ThrowableSubject;
+
+import org.junit.Rule;
+
+import java.math.BigDecimal;
+import java.util.Map;
+
+// NOTE: it could be a more generic AbstractTruthTestCase that provide similar methods
+// for assertThat() / assertWithMessage(), but then we'd need to remove all static import imports
+// from classes that indirectly extend it.
+/**
+ * Base class to make it easier to use {@code Truth} {@link Expect} assertions.
+ */
+public abstract class ExpectableTestCase {
+
+ @Rule
+ public final Expect mExpect = Expect.create();
+
+ protected final StandardSubjectBuilder expectWithMessage(String msg) {
+ return mExpect.withMessage(msg);
+ }
+
+ protected final StandardSubjectBuilder expectWithMessage(String format, Object...args) {
+ return mExpect.withMessage(format, args);
+ }
+
+ protected final <ComparableT extends Comparable<?>> ComparableSubject<ComparableT> expectThat(
+ ComparableT actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final BigDecimalSubject expectThat(BigDecimal actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final Subject expectThat(Object actual) {
+ return mExpect.that(actual);
+ }
+
+ @GwtIncompatible("ClassSubject.java")
+ protected final ClassSubject expectThat(Class<?> actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final ThrowableSubject expectThat(Throwable actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final LongSubject expectThat(Long actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final DoubleSubject expectThat(Double actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final FloatSubject expectThat(Float actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final IntegerSubject expectThat(Integer actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final BooleanSubject expectThat(Boolean actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final StringSubject expectThat(String actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final IterableSubject expectThat(Iterable<?> actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final <T> ObjectArraySubject<T> expectThat(T[] actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final PrimitiveBooleanArraySubject expectThat(boolean[] actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final PrimitiveShortArraySubject expectThat(short[] actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final PrimitiveIntArraySubject expectThat(int[] actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final PrimitiveLongArraySubject expectThat(long[] actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final PrimitiveCharArraySubject expectThat(char[] actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final PrimitiveByteArraySubject expectThat(byte[] actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final PrimitiveFloatArraySubject expectThat(float[] actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final PrimitiveDoubleArraySubject expectThat(double[] actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final GuavaOptionalSubject expectThat(Optional<?> actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final MapSubject expectThat(Map<?, ?> actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final MultimapSubject expectThat(Multimap<?, ?> actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final MultisetSubject expectThat(Multiset<?> actual) {
+ return mExpect.that(actual);
+ }
+
+ protected final TableSubject expectThat(Table<?, ?, ?> actual) {
+ return mExpect.that(actual);
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoRule.java b/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoRule.java
new file mode 100644
index 0000000..881dd50
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoRule.java
@@ -0,0 +1,183 @@
+/*
+ * 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.server;
+
+import android.annotation.Nullable;
+import android.util.Log;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+import com.android.internal.util.Preconditions;
+import com.android.modules.utils.testing.StaticMockFixture;
+import com.android.modules.utils.testing.StaticMockFixtureRule;
+
+import org.mockito.Mockito;
+import org.mockito.quality.Strictness;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Rule to make it easier to use Extended Mockito.
+ *
+ * <p>It's derived from {@link StaticMockFixtureRule}, with the additional features:
+ *
+ * <ul>
+ * <li>Easier to define which classes must be statically mocked or spied
+ * <li>Automatically starts mocking (so tests don't need a mockito runner or rule)
+ * <li>Automatically clears the inlined mocks at the end (to avoid OOM)
+ * <li>Allows other customization like strictness
+ * </ul>
+ */
+public final class ExtendedMockitoRule extends StaticMockFixtureRule {
+
+ private static final String TAG = ExtendedMockitoRule.class.getSimpleName();
+
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+ private final Object mTestClassInstance;
+ private final Strictness mStrictness;
+
+ private ExtendedMockitoRule(Builder builder) {
+ super(() -> new SimpleStatickMockFixture(builder.mMockedStaticClasses,
+ builder.mSpiedStaticClasses, builder.mDynamicSessionBuilderConfigurator,
+ builder.mAfterSessionFinishedCallback));
+ mTestClassInstance = builder.mTestClassInstance;
+ mStrictness = builder.mStrictness;
+ if (VERBOSE) {
+ Log.v(TAG, "strictness=" + mStrictness + ", testClassInstance" + mTestClassInstance
+ + ", mockedStaticClasses=" + builder.mMockedStaticClasses
+ + ", spiedStaticClasses=" + builder.mSpiedStaticClasses
+ + ", dynamicSessionBuilderConfigurator="
+ + builder.mDynamicSessionBuilderConfigurator
+ + ", afterSessionFinishedCallback=" + builder.mAfterSessionFinishedCallback);
+ }
+ }
+
+ @Override
+ public StaticMockitoSessionBuilder getSessionBuilder() {
+ StaticMockitoSessionBuilder sessionBuilder = super.getSessionBuilder();
+ if (mStrictness != null) {
+ if (VERBOSE) {
+ Log.v(TAG, "Setting strictness to " + mStrictness + " on " + sessionBuilder);
+ }
+ sessionBuilder.strictness(mStrictness);
+ }
+ return sessionBuilder.initMocks(mTestClassInstance);
+ }
+
+ public static final class Builder {
+ private final Object mTestClassInstance;
+ private @Nullable Strictness mStrictness;
+ private final List<Class<?>> mMockedStaticClasses = new ArrayList<>();
+ private final List<Class<?>> mSpiedStaticClasses = new ArrayList<>();
+ private @Nullable Visitor<StaticMockitoSessionBuilder> mDynamicSessionBuilderConfigurator;
+ private @Nullable Runnable mAfterSessionFinishedCallback;
+
+ public Builder(Object testClassInstance) {
+ mTestClassInstance = Objects.requireNonNull(testClassInstance);
+ }
+
+ public Builder setStrictness(Strictness strictness) {
+ mStrictness = Objects.requireNonNull(strictness);
+ return this;
+ }
+
+ public Builder mockStatic(Class<?> clazz) {
+ Objects.requireNonNull(clazz);
+ Preconditions.checkState(!mMockedStaticClasses.contains(clazz),
+ "class %s already mocked", clazz);
+ mMockedStaticClasses.add(clazz);
+ return this;
+ }
+
+ public Builder spyStatic(Class<?> clazz) {
+ Objects.requireNonNull(clazz);
+ Preconditions.checkState(!mSpiedStaticClasses.contains(clazz),
+ "class %s already spied", clazz);
+ mSpiedStaticClasses.add(clazz);
+ return this;
+ }
+
+ public Builder dynamiclyConfigureSessionBuilder(
+ Visitor<StaticMockitoSessionBuilder> dynamicSessionBuilderConfigurator) {
+ mDynamicSessionBuilderConfigurator = Objects
+ .requireNonNull(dynamicSessionBuilderConfigurator);
+ return this;
+ }
+
+ public Builder afterSessionFinished(Runnable runnable) {
+ mAfterSessionFinishedCallback = Objects.requireNonNull(runnable);
+ return this;
+ }
+
+ public ExtendedMockitoRule build() {
+ return new ExtendedMockitoRule(this);
+ }
+ }
+
+ private static final class SimpleStatickMockFixture implements StaticMockFixture {
+
+ private final List<Class<?>> mMockedStaticClasses;
+ private final List<Class<?>> mSpiedStaticClasses;
+ @Nullable
+ private final Visitor<StaticMockitoSessionBuilder> mDynamicSessionBuilderConfigurator;
+ @Nullable
+ private final Runnable mAfterSessionFinishedCallback;
+
+ private SimpleStatickMockFixture(List<Class<?>> mockedStaticClasses,
+ List<Class<?>> spiedStaticClasses,
+ @Nullable Visitor<StaticMockitoSessionBuilder> dynamicSessionBuilderConfigurator,
+ @Nullable Runnable afterSessionFinishedCallback) {
+ mMockedStaticClasses = mockedStaticClasses;
+ mSpiedStaticClasses = spiedStaticClasses;
+ mDynamicSessionBuilderConfigurator = dynamicSessionBuilderConfigurator;
+ mAfterSessionFinishedCallback = afterSessionFinishedCallback;
+ }
+
+ @Override
+ public StaticMockitoSessionBuilder setUpMockedClasses(
+ StaticMockitoSessionBuilder sessionBuilder) {
+ mMockedStaticClasses.forEach((c) -> sessionBuilder.mockStatic(c));
+ mSpiedStaticClasses.forEach((c) -> sessionBuilder.spyStatic(c));
+ if (mDynamicSessionBuilderConfigurator != null) {
+ mDynamicSessionBuilderConfigurator.visit(sessionBuilder);
+ }
+ return sessionBuilder;
+ }
+
+ @Override
+ public void setUpMockBehaviors() {
+ }
+
+ @Override
+ public void tearDown() {
+ try {
+ if (mAfterSessionFinishedCallback != null) {
+ mAfterSessionFinishedCallback.run();
+ }
+ } finally {
+ if (VERBOSE) {
+ Log.v(TAG, "calling Mockito.framework().clearInlineMocks()");
+ }
+ // When using inline mock maker, clean up inline mocks to prevent OutOfMemory
+ // errors. See https://github.com/mockito/mockito/issues/1614 and b/259280359.
+ Mockito.framework().clearInlineMocks();
+ }
+ }
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoTestCase.java b/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoTestCase.java
deleted file mode 100644
index 48483a1..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoTestCase.java
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-
-import android.annotation.Nullable;
-import android.util.Log;
-
-import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
-
-import com.google.common.truth.Expect;
-import com.google.common.truth.StandardSubjectBuilder;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.rules.RuleChain;
-import org.mockito.Mockito;
-import org.mockito.MockitoSession;
-import org.mockito.quality.Strictness;
-
-import java.lang.reflect.Constructor;
-
-/**
- * Base class to make it easier to write tests that uses {@code ExtendedMockito}.
- *
- */
-public abstract class ExtendedMockitoTestCase {
-
- private static final String TAG = ExtendedMockitoTestCase.class.getSimpleName();
-
- private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
-
- /**
- * Number of invocations, used to force a failure on {@link #forceFailure(int, Class, String)}.
- */
- private static int sInvocationsCounter;
-
- /**
- * Sessions follow the "Highlander Rule": There can be only one!
- *
- * <p>So, we keep track of that and force-close it if needed.
- */
- @Nullable
- private static MockitoSession sHighlanderSession;
-
- /**
- * Points to where the current session was created.
- */
- private static Exception sSessionCreationLocation;
-
- private MockitoSession mSession;
-
- protected final Expect mExpect = Expect.create();
- protected final DumpableDumperRule mDumpableDumperRule = new DumpableDumperRule();
-
- @Rule
- public final RuleChain mTwoRingsOfPowerAndOneChainToRuleThemAll = RuleChain
- .outerRule(mDumpableDumperRule)
- .around(mExpect);
-
- public ExtendedMockitoTestCase() {
- sInvocationsCounter++;
- }
-
- @Before
- public final void startSession() {
- if (VERBOSE) {
- Log.v(TAG, "startSession() for " + getTestName() + " on thread "
- + Thread.currentThread() + "; sHighlanderSession=" + sHighlanderSession);
- }
- createSessionLocation();
- finishHighlanderSessionIfNeeded("startSession()");
- StaticMockitoSessionBuilder builder = mockitoSession()
- .initMocks(this)
- .strictness(getSessionStrictness());
- initializeSession(builder);
- sHighlanderSession = mSession = builder.startMocking();
- }
-
- private void createSessionLocation() {
- try {
- sSessionCreationLocation = new Exception(getTestName());
- } catch (Exception e) {
- // Better safe than sorry...
- Log.e(TAG, "Could not create sSessionCreationLocation with " + getTestName()
- + " on thread " + Thread.currentThread(), e);
- sSessionCreationLocation = e;
- }
- }
-
- /**
- * Gets the session strictness.
- *
- * @return {@link Strictness.LENIENT} by default; subclasses can override.
- */
- protected Strictness getSessionStrictness() {
- return Strictness.LENIENT;
- }
-
- /**
- * Initializes the mockito session.
- *
- * <p>Typically used to define which classes should have static methods mocked or spied.
- */
- protected void initializeSession(StaticMockitoSessionBuilder builder) {
- if (VERBOSE) {
- Log.v(TAG, "initializeSession()");
- }
- }
-
- @After
- public final void finishSession() throws Exception {
- if (false) { // For obvious reasons, should NEVER be merged as true
- forceFailure(1, RuntimeException.class, "to simulate an unfinished session");
- }
-
- // mSession.finishMocking() must ALWAYS be called (hence the over-protective try/finally
- // statements), otherwise it would cause failures on future tests as mockito
- // cannot start a session when a previous one is not finished
- try {
- if (VERBOSE) {
- Log.v(TAG, "finishSession() for " + getTestName() + " on thread "
- + Thread.currentThread() + "; sHighlanderSession=" + sHighlanderSession);
-
- }
- } finally {
- sHighlanderSession = null;
- finishSessionMocking();
- afterSessionFinished();
- }
- }
-
- /**
- * Called after the mockito session was finished
- *
- * <p>This method should be used by subclasses that MUST do their cleanup after the session is
- * finished (as methods marked with {@link After} in the subclasses would be called BEFORE
- * that).
- */
- protected void afterSessionFinished() {
- if (VERBOSE) {
- Log.v(TAG, "afterSessionFinished()");
- }
- }
-
- private void finishSessionMocking() {
- if (mSession == null) {
- Log.w(TAG, getClass().getSimpleName() + ".finishSession(): no session");
- return;
- }
- try {
- mSession.finishMocking();
- } finally {
- // Shouldn't need to set mSession to null as JUnit always instantiate a new object,
- // but it doesn't hurt....
- mSession = null;
- // When using inline mock maker, clean up inline mocks to prevent OutOfMemory
- // errors. See https://github.com/mockito/mockito/issues/1614 and b/259280359.
- Mockito.framework().clearInlineMocks();
- }
- }
-
- private void finishHighlanderSessionIfNeeded(String where) {
- if (sHighlanderSession == null) {
- if (VERBOSE) {
- Log.v(TAG, "finishHighlanderSessionIfNeeded(): sHighlanderSession already null");
- }
- return;
- }
-
- if (sSessionCreationLocation != null) {
- if (VERBOSE) {
- Log.e(TAG, where + ": There can be only one! Closing unfinished session, "
- + "created at", sSessionCreationLocation);
- } else {
- Log.e(TAG, where + ": There can be only one! Closing unfinished session, "
- + "created at " + sSessionCreationLocation);
- }
- } else {
- Log.e(TAG, where + ": There can be only one! Closing unfinished session created at "
- + "unknown location");
- }
- try {
- sHighlanderSession.finishMocking();
- } catch (Throwable t) {
- if (VERBOSE) {
- Log.e(TAG, "Failed to close unfinished session on " + getTestName(), t);
- } else {
- Log.e(TAG, "Failed to close unfinished session on " + getTestName() + ": " + t);
- }
- } finally {
- if (VERBOSE) {
- Log.v(TAG, "Resetting sHighlanderSession at finishHighlanderSessionIfNeeded()");
- }
- sHighlanderSession = null;
- }
- }
-
- /**
- * Forces a failure at the given invocation of a test method by throwing an exception.
- */
- protected final <T extends Throwable> void forceFailure(int invocationCount,
- Class<T> failureClass, String reason) throws T {
- if (sInvocationsCounter != invocationCount) {
- Log.d(TAG, "forceFailure(" + invocationCount + "): no-op on invocation #"
- + sInvocationsCounter);
- return;
- }
- String message = "Throwing on invocation #" + sInvocationsCounter + ": " + reason;
- Log.e(TAG, message);
- T throwable;
- try {
- Constructor<T> constructor = failureClass.getConstructor(String.class);
- throwable = constructor.newInstance("Throwing on invocation #" + sInvocationsCounter
- + ": " + reason);
- } catch (Exception e) {
- throw new IllegalArgumentException("Could not create exception of class " + failureClass
- + " using msg='" + message + "' as constructor");
- }
- throw throwable;
- }
-
- protected final @Nullable String getTestName() {
- return mDumpableDumperRule.getTestName();
- }
-
- protected final StandardSubjectBuilder expectWithMessage(String msg) {
- return mExpect.withMessage(msg);
- }
-
- protected final StandardSubjectBuilder expectWithMessage(String format, Object...args) {
- return mExpect.withMessage(format, args);
- }
-}
diff --git a/services/tests/mockingservicestests/src/com/android/server/Visitor.java b/services/tests/mockingservicestests/src/com/android/server/Visitor.java
new file mode 100644
index 0000000..447910e
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/Visitor.java
@@ -0,0 +1,23 @@
+/*
+ * 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.server;
+
+/**
+ * Generic visitor.
+ */
+public interface Visitor<V> {
+ void visit(V visitee);
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index 9dd2f82..b395f42 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -159,7 +159,6 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.dx.mockito.inline.extended.MockedVoidMethod;
-import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsService;
@@ -168,7 +167,7 @@
import com.android.server.AppStateTracker;
import com.android.server.AppStateTrackerImpl;
import com.android.server.DeviceIdleInternal;
-import com.android.server.ExtendedMockitoTestCase;
+import com.android.server.ExtendedMockitoRule;
import com.android.server.LocalServices;
import com.android.server.SystemClockTime.TimeConfidence;
import com.android.server.SystemService;
@@ -182,6 +181,7 @@
import libcore.util.EmptyArray;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
@@ -204,7 +204,7 @@
@Presubmit
@SuppressWarnings("GuardedBy") // This test enforces synchronous behavior.
@RunWith(AndroidJUnit4.class)
-public final class AlarmManagerServiceTest extends ExtendedMockitoTestCase {
+public final class AlarmManagerServiceTest {
private static final String TAG = AlarmManagerServiceTest.class.getSimpleName();
private static final int SYSTEM_UI_UID = 12345;
private static final int TEST_CALLING_USER = UserHandle.getUserId(TEST_CALLING_UID);
@@ -411,14 +411,9 @@
}
}
- @Override
- protected Strictness getSessionStrictness() {
- return Strictness.WARN;
- }
-
- @Override
- protected void initializeSession(StaticMockitoSessionBuilder builder) {
- builder
+ @Rule
+ public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
+ .setStrictness(Strictness.WARN)
.spyStatic(ActivityManager.class)
.mockStatic(CompatChanges.class)
.spyStatic(DateFormat.class)
@@ -431,8 +426,10 @@
.mockStatic(ServiceManager.class)
.mockStatic(Settings.Global.class)
.mockStatic(SystemProperties.class)
- .spyStatic(UserHandle.class);
- }
+ .spyStatic(UserHandle.class)
+ .afterSessionFinished(
+ () -> LocalServices.removeServiceForTest(AlarmManagerInternal.class))
+ .build();
@Before
public final void setUp() {
@@ -3805,9 +3802,4 @@
mListener.removeListenerAlarmsForCachedUid(TEST_CALLING_UID_2);
assertEquals(2, mService.mAlarmsPerUid.get(TEST_CALLING_UID_2));
}
-
- @Override
- public void afterSessionFinished() {
- LocalServices.removeServiceForTest(AlarmManagerInternal.class);
- }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceInjectorTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceInjectorTest.java
index e01a9a9..9ceb5e7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceInjectorTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceInjectorTest.java
@@ -31,11 +31,11 @@
import android.util.Log;
import android.view.Display;
-import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
-import com.android.server.ExtendedMockitoTestCase;
+import com.android.server.ExtendedMockitoRule;
import com.android.server.am.ActivityManagerService.Injector;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
@@ -45,7 +45,7 @@
* Run as {@code atest
* FrameworksMockingServicesTests:com.android.server.am.ActivityManagerServiceInjectorTest}
*/
-public final class ActivityManagerServiceInjectorTest extends ExtendedMockitoTestCase {
+public final class ActivityManagerServiceInjectorTest {
private static final String TAG = ActivityManagerServiceInjectorTest.class.getSimpleName();
@@ -63,10 +63,10 @@
when(mContext.getSystemService(DisplayManager.class)).thenReturn(mDisplayManager);
}
- @Override
- protected void initializeSession(StaticMockitoSessionBuilder builder) {
- builder.spyStatic(UserManager.class);
- }
+ @Rule
+ public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
+ .spyStatic(UserManager.class)
+ .build();
@Test
public void testGetDisplayIdsForStartingBackgroundUsers_notSupported() {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index dcdee37..95c2ed2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -78,18 +78,15 @@
import androidx.test.filters.SmallTest;
-import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.ExtendedMockitoTestCase;
+import com.android.server.ExtendedMockitoRule;
import com.android.server.am.BroadcastQueueTest.SyncBarrier;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
-import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.junit.MockitoJUnitRunner;
import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
@@ -98,8 +95,7 @@
import java.util.List;
@SmallTest
-@RunWith(MockitoJUnitRunner.class)
-public class BroadcastQueueModernImplTest extends ExtendedMockitoTestCase {
+public final class BroadcastQueueModernImplTest {
private static final int TEST_UID = android.os.Process.FIRST_APPLICATION_UID;
private static final int TEST_UID2 = android.os.Process.FIRST_APPLICATION_UID + 1;
@@ -118,15 +114,13 @@
BroadcastProcessQueue mHead;
- @Override
- protected void initializeSession(StaticMockitoSessionBuilder builder) {
- builder.spyStatic(FrameworkStatsLog.class);
- }
+ @Rule
+ public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
+ .spyStatic(FrameworkStatsLog.class)
+ .build();
@Before
public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
-
mHandlerThread = new HandlerThread(getClass().getSimpleName());
mHandlerThread.start();
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
index f8cfdf1..ec9e5b5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
@@ -40,9 +40,8 @@
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
import com.android.modules.utils.testing.TestableDeviceConfig;
-import com.android.server.ExtendedMockitoTestCase;
+import com.android.server.ExtendedMockitoRule;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
import com.android.server.appop.AppOpsService;
@@ -69,7 +68,7 @@
* atest FrameworksMockingServicesTests:CachedAppOptimizerTest
*/
@Presubmit
-public final class CachedAppOptimizerTest extends ExtendedMockitoTestCase {
+public final class CachedAppOptimizerTest {
private ServiceThread mThread;
@@ -93,10 +92,11 @@
public final ApplicationExitInfoTest.ServiceThreadRule
mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule();
- @Override
- protected void initializeSession(StaticMockitoSessionBuilder builder) {
- mDeviceConfig.setUpMockedClasses(builder);
- }
+ @Rule
+ public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
+ .dynamiclyConfigureSessionBuilder(
+ sessionBuilder -> mDeviceConfig.setUpMockedClasses(sessionBuilder))
+ .build();
@Before
public void setUp() {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index dc7f3cd..485ce33 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -143,8 +143,8 @@
private static final String MOCKAPP5_PROCESSNAME = "test #5";
private static final String MOCKAPP5_PACKAGENAME = "com.android.test.test5";
private static final int MOCKAPP2_UID_OTHER = MOCKAPP2_UID + UserHandle.PER_USER_RANGE;
- private static final int FIRST_CACHED_ADJ = ProcessList.CACHED_APP_MIN_ADJ
- + ProcessList.CACHED_APP_IMPORTANCE_LEVELS;
+ private static int sFirstCachedAdj = ProcessList.CACHED_APP_MIN_ADJ
+ + ProcessList.CACHED_APP_IMPORTANCE_LEVELS;
private static Context sContext;
private static PackageManagerInternal sPackageManagerInternal;
private static ActivityManagerService sService;
@@ -208,6 +208,9 @@
new ActiveUids(sService, false));
sService.mOomAdjuster.mAdjSeq = 10000;
sService.mWakefulness = new AtomicInteger(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ if (sService.mConstants.USE_TIERED_CACHED_ADJ) {
+ sFirstCachedAdj = ProcessList.CACHED_APP_MIN_ADJ + 10;
+ }
}
@AfterClass
@@ -834,7 +837,7 @@
updateOomAdj(client, app);
doReturn(null).when(sService).getTopApp();
- assertProcStates(app, PROCESS_STATE_SERVICE, FIRST_CACHED_ADJ, SCHED_GROUP_BACKGROUND);
+ assertProcStates(app, PROCESS_STATE_SERVICE, sFirstCachedAdj, SCHED_GROUP_BACKGROUND);
}
@SuppressWarnings("GuardedBy")
@@ -882,7 +885,7 @@
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
- assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, FIRST_CACHED_ADJ, SCHED_GROUP_BACKGROUND);
+ assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, sFirstCachedAdj, SCHED_GROUP_BACKGROUND);
}
@SuppressWarnings("GuardedBy")
@@ -1286,7 +1289,7 @@
bindProvider(app, app, null, null, false);
updateOomAdj(app);
- assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, FIRST_CACHED_ADJ, SCHED_GROUP_BACKGROUND);
+ assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, sFirstCachedAdj, SCHED_GROUP_BACKGROUND);
}
@SuppressWarnings("GuardedBy")
@@ -1301,7 +1304,7 @@
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app, client);
- assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, FIRST_CACHED_ADJ, SCHED_GROUP_BACKGROUND);
+ assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, sFirstCachedAdj, SCHED_GROUP_BACKGROUND);
}
@SuppressWarnings("GuardedBy")
@@ -2378,8 +2381,12 @@
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
final int userOwner = 0;
final int userOther = 1;
- final int cachedAdj1 = CACHED_APP_MIN_ADJ + ProcessList.CACHED_APP_IMPORTANCE_LEVELS;
- final int cachedAdj2 = cachedAdj1 + ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2;
+ final int cachedAdj1 = sService.mConstants.USE_TIERED_CACHED_ADJ
+ ? CACHED_APP_MIN_ADJ + 10
+ : CACHED_APP_MIN_ADJ + ProcessList.CACHED_APP_IMPORTANCE_LEVELS;
+ final int cachedAdj2 = sService.mConstants.USE_TIERED_CACHED_ADJ
+ ? CACHED_APP_MIN_ADJ + 10
+ : cachedAdj1 + ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2;
doReturn(userOwner).when(sService.mUserController).getCurrentUserId();
final ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
diff --git a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java
index 9f3bc33..b214787 100644
--- a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java
@@ -29,7 +29,7 @@
import android.util.Log;
import android.util.SparseArray;
-import com.android.server.ExtendedMockitoTestCase;
+import com.android.server.ExpectableTestCase;
import libcore.io.Streams;
@@ -44,7 +44,7 @@
import java.util.Objects;
/** This class contains unit tests for the {@link CpuInfoReader}. */
-public final class CpuInfoReaderTest extends ExtendedMockitoTestCase {
+public final class CpuInfoReaderTest extends ExpectableTestCase {
private static final String TAG = CpuInfoReaderTest.class.getSimpleName();
private static final String ROOT_DIR_NAME = "CpuInfoReaderTest";
private static final String VALID_CPUSET_DIR = "valid_cpuset";
@@ -426,7 +426,7 @@
.isNull();
}
- private static void compareCpuInfos(String message,
+ private void compareCpuInfos(String message,
SparseArray<CpuInfoReader.CpuInfo> expected,
SparseArray<CpuInfoReader.CpuInfo> actual) {
assertWithMessage("%s. Total CPU infos", message).that(actual.size())
@@ -435,7 +435,7 @@
int cpuCoreId = expected.keyAt(i);
CpuInfoReader.CpuInfo expectedCpuInfo = expected.valueAt(i);
CpuInfoReader.CpuInfo actualCpuInfo = actual.get(cpuCoreId);
- assertWithMessage("%s. Core %d's CPU info", message, cpuCoreId).that(actualCpuInfo)
+ expectWithMessage("%s. Core %s's CPU info", message, cpuCoreId).that(actualCpuInfo)
.isEqualTo(expectedCpuInfo);
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java
index 7ab1363..49a2cc6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java
@@ -32,16 +32,16 @@
import android.os.Looper;
import android.os.ServiceManager;
-import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
-import com.android.server.ExtendedMockitoTestCase;
+import com.android.server.ExtendedMockitoRule;
import com.android.server.LocalServices;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
-public final class CpuMonitorServiceTest extends ExtendedMockitoTestCase {
+public final class CpuMonitorServiceTest {
private static final CpuAvailabilityMonitoringConfig TEST_CPU_AVAILABILITY_MONITORING_CONFIG =
new CpuAvailabilityMonitoringConfig.Builder(CPUSET_ALL)
.addThreshold(30).addThreshold(70).build();
@@ -56,10 +56,10 @@
private HandlerExecutor mHandlerExecutor;
private CpuMonitorInternal mLocalService;
- @Override
- protected void initializeSession(StaticMockitoSessionBuilder builder) {
- builder.mockStatic(ServiceManager.class);
- }
+ @Rule
+ public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
+ .mockStatic(ServiceManager.class)
+ .build();
@Before
public void setUp() {
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobNotificationCoordinatorTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobNotificationCoordinatorTest.java
index b4104db..03f667f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobNotificationCoordinatorTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobNotificationCoordinatorTest.java
@@ -33,6 +33,7 @@
import android.app.Notification;
import android.app.NotificationChannel;
+import android.app.job.JobParameters;
import android.app.job.JobService;
import android.graphics.drawable.Icon;
import android.os.UserHandle;
@@ -145,7 +146,7 @@
.enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
eq(notificationId), eq(notification), eq(UserHandle.getUserId(uid)));
- coordinator.removeNotificationAssociation(jsc);
+ coordinator.removeNotificationAssociation(jsc, JobParameters.STOP_REASON_UNDEFINED);
verify(mNotificationManagerInternal, never())
.cancelNotification(anyString(), anyString(), anyInt(), anyInt(), any(),
anyInt(), anyInt());
@@ -166,7 +167,7 @@
.enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
eq(notificationId), eq(notification), eq(UserHandle.getUserId(uid)));
- coordinator.removeNotificationAssociation(jsc);
+ coordinator.removeNotificationAssociation(jsc, JobParameters.STOP_REASON_UNDEFINED);
verify(mNotificationManagerInternal)
.cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
eq(notificationId), eq(UserHandle.getUserId(uid)));
@@ -288,7 +289,7 @@
eq(notificationId2), eq(notification2), eq(UserHandle.getUserId(uid)));
// Remove the first job. Only the first notification should be removed.
- coordinator.removeNotificationAssociation(jsc1);
+ coordinator.removeNotificationAssociation(jsc1, JobParameters.STOP_REASON_UNDEFINED);
inOrder.verify(mNotificationManagerInternal)
.cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
eq(notificationId1), eq(UserHandle.getUserId(uid)));
@@ -296,7 +297,7 @@
.cancelNotification(anyString(), anyString(), anyInt(), anyInt(), any(),
eq(notificationId2), anyInt());
- coordinator.removeNotificationAssociation(jsc2);
+ coordinator.removeNotificationAssociation(jsc2, JobParameters.STOP_REASON_UNDEFINED);
inOrder.verify(mNotificationManagerInternal)
.cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
eq(notificationId2), eq(UserHandle.getUserId(uid)));
@@ -331,12 +332,12 @@
eq(notificationId), eq(notification2), eq(UserHandle.getUserId(uid)));
// Remove the first job. The notification shouldn't be touched because of the 2nd job.
- coordinator.removeNotificationAssociation(jsc1);
+ coordinator.removeNotificationAssociation(jsc1, JobParameters.STOP_REASON_UNDEFINED);
inOrder.verify(mNotificationManagerInternal, never())
.cancelNotification(anyString(), anyString(), anyInt(), anyInt(), any(),
anyInt(), anyInt());
- coordinator.removeNotificationAssociation(jsc2);
+ coordinator.removeNotificationAssociation(jsc2, JobParameters.STOP_REASON_UNDEFINED);
inOrder.verify(mNotificationManagerInternal)
.cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
eq(notificationId), eq(UserHandle.getUserId(uid)));
@@ -372,7 +373,7 @@
eq(notificationId), eq(notification2), eq(UserHandle.getUserId(uid2)));
// Remove the first job. Only the first notification should be removed.
- coordinator.removeNotificationAssociation(jsc1);
+ coordinator.removeNotificationAssociation(jsc1, JobParameters.STOP_REASON_UNDEFINED);
inOrder.verify(mNotificationManagerInternal)
.cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid1), eq(pid), any(),
eq(notificationId), eq(UserHandle.getUserId(uid1)));
@@ -380,7 +381,7 @@
.cancelNotification(anyString(), anyString(), eq(uid2), anyInt(), any(),
anyInt(), anyInt());
- coordinator.removeNotificationAssociation(jsc2);
+ coordinator.removeNotificationAssociation(jsc2, JobParameters.STOP_REASON_UNDEFINED);
inOrder.verify(mNotificationManagerInternal)
.cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid2), eq(pid), any(),
eq(notificationId), eq(UserHandle.getUserId(uid2)));
@@ -417,7 +418,7 @@
eq(notificationId), eq(notification2), eq(UserHandle.getUserId(uid)));
// Remove the first job. Only the first notification should be removed.
- coordinator.removeNotificationAssociation(jsc1);
+ coordinator.removeNotificationAssociation(jsc1, JobParameters.STOP_REASON_UNDEFINED);
inOrder.verify(mNotificationManagerInternal)
.cancelNotification(eq(pkg1), eq(pkg1), eq(uid), eq(pid), any(),
eq(notificationId), eq(UserHandle.getUserId(uid)));
@@ -425,12 +426,73 @@
.cancelNotification(anyString(), anyString(), eq(uid), anyInt(), any(),
anyInt(), anyInt());
- coordinator.removeNotificationAssociation(jsc2);
+ coordinator.removeNotificationAssociation(jsc2, JobParameters.STOP_REASON_UNDEFINED);
inOrder.verify(mNotificationManagerInternal)
.cancelNotification(eq(pkg2), eq(pkg2), eq(uid), eq(pid), any(),
eq(notificationId), eq(UserHandle.getUserId(uid)));
}
+ @Test
+ public void testUserStop_SingleJob_DetachOnStop() {
+ final JobNotificationCoordinator coordinator = new JobNotificationCoordinator();
+ final JobServiceContext jsc = mock(JobServiceContext.class);
+ final Notification notification = createValidNotification();
+ final int uid = 10123;
+ final int pid = 42;
+ final int notificationId = 23;
+
+ coordinator.enqueueNotification(jsc, TEST_PACKAGE, pid, uid, notificationId, notification,
+ JobService.JOB_END_NOTIFICATION_POLICY_DETACH);
+ verify(mNotificationManagerInternal)
+ .enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
+ eq(notificationId), eq(notification), eq(UserHandle.getUserId(uid)));
+
+ coordinator.removeNotificationAssociation(jsc, JobParameters.STOP_REASON_USER);
+ verify(mNotificationManagerInternal)
+ .cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
+ eq(notificationId), eq(UserHandle.getUserId(uid)));
+ }
+
+ @Test
+ public void testUserStop_MultipleJobs_sameApp_EnqueueSameNotificationId_DetachOnStop() {
+ final JobNotificationCoordinator coordinator = new JobNotificationCoordinator();
+ final JobServiceContext jsc1 = mock(JobServiceContext.class);
+ final JobServiceContext jsc2 = mock(JobServiceContext.class);
+ final Notification notification1 = createValidNotification();
+ final Notification notification2 = createValidNotification();
+ final int uid = 10123;
+ final int pid = 42;
+ final int notificationId = 23;
+
+ InOrder inOrder = inOrder(mNotificationManagerInternal);
+
+ coordinator.enqueueNotification(jsc1, TEST_PACKAGE, pid, uid, notificationId, notification1,
+ JobService.JOB_END_NOTIFICATION_POLICY_DETACH);
+ inOrder.verify(mNotificationManagerInternal)
+ .enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
+ eq(notificationId), eq(notification1), eq(UserHandle.getUserId(uid)));
+
+ coordinator.enqueueNotification(jsc2, TEST_PACKAGE, pid, uid, notificationId, notification2,
+ JobService.JOB_END_NOTIFICATION_POLICY_DETACH);
+ inOrder.verify(mNotificationManagerInternal, never())
+ .cancelNotification(anyString(), anyString(), anyInt(), anyInt(), any(),
+ anyInt(), anyInt());
+ inOrder.verify(mNotificationManagerInternal)
+ .enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
+ eq(notificationId), eq(notification2), eq(UserHandle.getUserId(uid)));
+
+ // Remove the first job. The notification shouldn't be touched because of the 2nd job.
+ coordinator.removeNotificationAssociation(jsc1, JobParameters.STOP_REASON_USER);
+ inOrder.verify(mNotificationManagerInternal, never())
+ .cancelNotification(anyString(), anyString(), anyInt(), anyInt(), any(),
+ anyInt(), anyInt());
+
+ coordinator.removeNotificationAssociation(jsc2, JobParameters.STOP_REASON_USER);
+ inOrder.verify(mNotificationManagerInternal)
+ .cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
+ eq(notificationId), eq(UserHandle.getUserId(uid)));
+ }
+
private Notification createValidNotification() {
final Notification notification = mock(Notification.class);
doReturn(mock(Icon.class)).when(notification).getSmallIcon();
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 06ba5dd..3f5d113 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -664,7 +664,7 @@
private fun mockQueryActivities(action: String, vararg activities: ActivityInfo) {
whenever(mocks.componentResolver.queryActivities(any(),
argThat { intent: Intent? -> intent != null && (action == intent.action) },
- nullable(), anyLong(), anyInt())) {
+ nullable(), anyLong(), anyInt(), anyInt())) {
ArrayList(activities.asList().map { info: ActivityInfo? ->
ResolveInfo().apply { activityInfo = info }
})
@@ -674,7 +674,7 @@
private fun mockQueryServices(action: String, vararg services: ServiceInfo) {
whenever(mocks.componentResolver.queryServices(any(),
argThat { intent: Intent? -> intent != null && (action == intent.action) },
- nullable(), anyLong(), anyInt())) {
+ nullable(), anyLong(), anyInt(), anyInt())) {
ArrayList(services.asList().map { info ->
ResolveInfo().apply { serviceInfo = info }
})
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 1ed2f78..564893c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -40,9 +40,8 @@
import androidx.test.annotation.UiThreadTest;
-import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
import com.android.internal.widget.LockSettingsInternal;
-import com.android.server.ExtendedMockitoTestCase;
+import com.android.server.ExtendedMockitoRule;
import com.android.server.LocalServices;
import com.android.server.am.UserState;
import com.android.server.pm.UserManagerService.UserData;
@@ -50,13 +49,14 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
/**
* Run as {@code atest FrameworksMockingServicesTests:com.android.server.pm.UserManagerServiceTest}
*/
-public final class UserManagerServiceTest extends ExtendedMockitoTestCase {
+public final class UserManagerServiceTest {
private static final String TAG = UserManagerServiceTest.class.getSimpleName();
@@ -84,6 +84,13 @@
*/
private static final int PROFILE_USER_ID = 643;
+ @Rule
+ public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
+ .spyStatic(UserManager.class)
+ .spyStatic(LocalServices.class)
+ .mockStatic(Settings.Global.class)
+ .build();
+
private final Object mPackagesLock = new Object();
private final Context mRealContext = androidx.test.InstrumentationRegistry.getInstrumentation()
.getTargetContext();
@@ -109,14 +116,6 @@
*/
private UserManagerInternal mUmi;
- @Override
- protected void initializeSession(StaticMockitoSessionBuilder builder) {
- builder
- .spyStatic(UserManager.class)
- .spyStatic(LocalServices.class)
- .mockStatic(Settings.Global.class);
- }
-
@Before
@UiThreadTest // Needed to initialize main handler
public void setFixtures() {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUPANDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUPANDTest.java
index 38cf634..9aa53db 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUPANDTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUPANDTest.java
@@ -20,8 +20,6 @@
import static android.view.Display.INVALID_DISPLAY;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
-import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_ALREADY_VISIBLE;
-import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
import static com.android.server.pm.UserVisibilityChangedEvent.onInvisible;
import static com.android.server.pm.UserVisibilityChangedEvent.onVisible;
@@ -128,7 +126,8 @@
listener.verify();
}
- /* TODO: re-add
+
+ /* TODO(b/261538337): re-add after the reverted CL is merged again
@Test
public void
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
index 5176d68..1bf921c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
@@ -45,7 +45,8 @@
import android.util.Log;
import com.android.internal.util.Preconditions;
-import com.android.server.ExtendedMockitoTestCase;
+import com.android.server.DumpableDumperRule;
+import com.android.server.ExpectableTestCase;
import org.junit.Before;
import org.junit.Test;
@@ -64,7 +65,7 @@
* is visible, display associated to the user, etc...) for each scenario (full user started on fg,
* profile user started on bg, etc...).
*/
-abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase {
+abstract class UserVisibilityMediatorTestCase extends ExpectableTestCase {
private static final String TAG = UserVisibilityMediatorTestCase.class.getSimpleName();
@@ -125,6 +126,8 @@
mBackgroundUserOnDefaultDisplayAllowed = backgroundUserOnDefaultDisplayAllowed;
}
+ protected final DumpableDumperRule mDumpableDumperRule = new DumpableDumperRule();
+
@Before
public final void setFixtures() {
mHandler = Handler.getMain();
diff --git a/services/tests/mockingservicestests/src/com/android/server/utils/SlogfTest.java b/services/tests/mockingservicestests/src/com/android/server/utils/SlogfTest.java
index cb59d37..02e46bb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/utils/SlogfTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/utils/SlogfTest.java
@@ -16,16 +16,9 @@
package com.android.server.utils;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.never;
-
import android.util.Log;
import android.util.Slog;
@@ -51,7 +44,6 @@
mSession = mockitoSession()
.initMocks(this)
.mockStatic(Slog.class)
- .spyStatic(Slogf.class) // for isLoggable only
.strictness(Strictness.LENIENT)
.startMocking();
}
@@ -66,11 +58,6 @@
}
@Test
- public void testIsLoggable() {
- assertThat(Slogf.isLoggable(TAG, Log.VERBOSE)).isEqualTo(Log.isLoggable(TAG, Log.VERBOSE));
- }
-
- @Test
public void testV_msg() {
Slogf.v(TAG, "msg");
@@ -85,42 +72,20 @@
}
@Test
- public void testV_msgFormatted_enabled() {
- enableLogging(Log.VERBOSE);
-
+ public void testV_msgFormatted() {
Slogf.v(TAG, "msg in a %s", "bottle");
verify(()-> Slog.v(TAG, "msg in a bottle"));
}
@Test
- public void testV_msgFormatted_disabled() {
- disableLogging(Log.VERBOSE);
-
- Slogf.v(TAG, "msg in a %s", "bottle");
-
- verify(()-> Slog.v(eq(TAG), any()), never());
- }
-
- @Test
- public void testV_msgFormattedWithThrowable_enabled() {
- enableLogging(Log.VERBOSE);
-
+ public void testV_msgFormattedWithThrowable() {
Slogf.v(TAG, mThrowable, "msg in a %s", "bottle");
verify(()-> Slog.v(TAG, "msg in a bottle", mThrowable));
}
@Test
- public void testV_msgFormattedWithException_disabled() {
- disableLogging(Log.VERBOSE);
-
- Slogf.v(TAG, "msg in a %s", "bottle");
-
- verify(()-> Slog.v(eq(TAG), any(String.class), any(Throwable.class)), never());
- }
-
- @Test
public void testD_msg() {
Slogf.d(TAG, "msg");
@@ -135,42 +100,20 @@
}
@Test
- public void testD_msgFormatted_enabled() {
- enableLogging(Log.DEBUG);
-
+ public void testD_msgFormatted() {
Slogf.d(TAG, "msg in a %s", "bottle");
verify(()-> Slog.d(TAG, "msg in a bottle"));
}
@Test
- public void testD_msgFormatted_disabled() {
- disableLogging(Log.DEBUG);
-
- Slogf.d(TAG, "msg in a %s", "bottle");
-
- verify(()-> Slog.d(eq(TAG), any()), never());
- }
-
- @Test
- public void testD_msgFormattedWithThrowable_enabled() {
- enableLogging(Log.DEBUG);
-
+ public void testD_msgFormattedWithThrowable() {
Slogf.d(TAG, mThrowable, "msg in a %s", "bottle");
verify(()-> Slog.d(TAG, "msg in a bottle", mThrowable));
}
@Test
- public void testD_msgFormattedWithException_disabled() {
- disableLogging(Log.DEBUG);
-
- Slogf.d(TAG, mThrowable, "msg in a %s", "bottle");
-
- verify(()-> Slog.d(eq(TAG), any(String.class), any(Throwable.class)), never());
- }
-
- @Test
public void testI_msg() {
Slogf.i(TAG, "msg");
@@ -185,42 +128,20 @@
}
@Test
- public void testI_msgFormatted_enabled() {
- enableLogging(Log.INFO);
-
+ public void testI_msgFormatted() {
Slogf.i(TAG, "msg in a %s", "bottle");
verify(()-> Slog.i(TAG, "msg in a bottle"));
}
@Test
- public void testI_msgFormatted_disabled() {
- disableLogging(Log.INFO);
-
- Slogf.i(TAG, "msg in a %s", "bottle");
-
- verify(()-> Slog.i(eq(TAG), any()), never());
- }
-
- @Test
- public void testI_msgFormattedWithThrowable_enabled() {
- enableLogging(Log.INFO);
-
+ public void testI_msgFormattedWithThrowable() {
Slogf.i(TAG, mThrowable, "msg in a %s", "bottle");
verify(()-> Slog.i(TAG, "msg in a bottle", mThrowable));
}
@Test
- public void testI_msgFormattedWithException_disabled() {
- disableLogging(Log.INFO);
-
- Slogf.i(TAG, mThrowable, "msg in a %s", "bottle");
-
- verify(()-> Slog.i(eq(TAG), any(String.class), any(Throwable.class)), never());
- }
-
- @Test
public void testW_msg() {
Slogf.w(TAG, "msg");
@@ -242,42 +163,20 @@
}
@Test
- public void testW_msgFormatted_enabled() {
- enableLogging(Log.WARN);
-
+ public void testW_msgFormatted() {
Slogf.w(TAG, "msg in a %s", "bottle");
verify(()-> Slog.w(TAG, "msg in a bottle"));
}
@Test
- public void testW_msgFormatted_disabled() {
- disableLogging(Log.WARN);
-
- Slogf.w(TAG, "msg in a %s", "bottle");
-
- verify(()-> Slog.w(eq(TAG), any(String.class)), never());
- }
-
- @Test
- public void testW_msgFormattedWithThrowable_enabled() {
- enableLogging(Log.WARN);
-
+ public void testW_msgFormattedWithThrowable() {
Slogf.w(TAG, mThrowable, "msg in a %s", "bottle");
verify(()-> Slog.w(TAG, "msg in a bottle", mThrowable));
}
@Test
- public void testW_msgFormattedWithException_disabled() {
- disableLogging(Log.WARN);
-
- Slogf.w(TAG, mThrowable, "msg in a %s", "bottle");
-
- verify(()-> Slog.w(eq(TAG), any(String.class), any(Throwable.class)), never());
- }
-
- @Test
public void testE_msg() {
Slogf.e(TAG, "msg");
@@ -292,42 +191,20 @@
}
@Test
- public void testE_msgFormatted_enabled() {
- enableLogging(Log.ERROR);
-
+ public void testE_msgFormatted() {
Slogf.e(TAG, "msg in a %s", "bottle");
verify(()-> Slog.e(TAG, "msg in a bottle"));
}
@Test
- public void testE_msgFormatted_disabled() {
- disableLogging(Log.ERROR);
-
- Slogf.e(TAG, "msg in a %s", "bottle");
-
- verify(()-> Slog.e(eq(TAG), any()), never());
- }
-
- @Test
- public void testE_msgFormattedWithThrowable_enabled() {
- enableLogging(Log.ERROR);
-
+ public void testE_msgFormattedWithThrowable() {
Slogf.e(TAG, mThrowable, "msg in a %s", "bottle");
verify(()-> Slog.e(TAG, "msg in a bottle", mThrowable));
}
@Test
- public void testE_msgFormattedWithException_disabled() {
- disableLogging(Log.ERROR);
-
- Slogf.e(TAG, mThrowable, "msg in a %s", "bottle");
-
- verify(()-> Slog.e(eq(TAG), any(String.class), any(Throwable.class)), never());
- }
-
- @Test
public void testWtf_msg() {
Slogf.wtf(TAG, "msg");
@@ -382,16 +259,4 @@
verify(()-> Slog.wtf(TAG, "msg in a bottle", mThrowable));
}
-
- private void enableLogging(@Log.Level int level) {
- setIsLogging(level, true);
- }
-
- private void disableLogging(@Log.Level int level) {
- setIsLogging(level, false);
- }
-
- private void setIsLogging(@Log.Level int level, boolean value) {
- doReturn(value).when(() -> Slogf.isLoggable(TAG, level));
- }
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 4249405..503e579 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -284,7 +284,7 @@
verify(mProxyManager).registerProxy(eq(mMockServiceClient), eq(TEST_DISPLAY),
eq(mTestableContext), anyInt(), any(), eq(mMockSecurityPolicy),
eq(mA11yms), eq(mA11yms.getTraceManager()),
- eq(mMockWindowManagerService), eq(mMockA11yWindowManager));
+ eq(mMockWindowManagerService));
}
@SmallTest
@@ -296,7 +296,7 @@
assertThrows(SecurityException.class,
() -> mA11yms.registerProxyForDisplay(mMockServiceClient, TEST_DISPLAY));
verify(mProxyManager, never()).registerProxy(any(), anyInt(), any(), anyInt(), any(), any(),
- any(), any(), any(), any());
+ any(), any(), any());
}
@SmallTest
@@ -305,7 +305,7 @@
assertThrows(IllegalArgumentException.class,
() -> mA11yms.registerProxyForDisplay(mMockServiceClient, Display.DEFAULT_DISPLAY));
verify(mProxyManager, never()).registerProxy(any(), anyInt(), any(), anyInt(), any(), any(),
- any(), any(), any(), any());
+ any(), any(), any());
}
@SmallTest
@@ -314,7 +314,7 @@
assertThrows(IllegalArgumentException.class,
() -> mA11yms.registerProxyForDisplay(mMockServiceClient, Display.INVALID_DISPLAY));
verify(mProxyManager, never()).registerProxy(any(), anyInt(), any(), anyInt(), any(), any(),
- any(), any(), any(), any());
+ any(), any(), any());
}
@SmallTest
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 51d3bae..306ce4d 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -44,6 +44,8 @@
import android.graphics.PointF;
import android.os.Handler;
import android.os.Message;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
import android.testing.TestableContext;
import android.util.DebugUtils;
import android.view.InputDevice;
@@ -507,6 +509,91 @@
verify(mWindowMagnificationPromptController).showNotificationIfNeeded();
}
+ @Test
+ public void testTransitToPanningState_scaleDifferenceOverThreshold_startDetecting() {
+ final float scale = 2.0f;
+ final float threshold = FullScreenMagnificationGestureHandler.PanningScalingState
+ .CHECK_DETECTING_PASS_PERSISTED_SCALE_THRESHOLD;
+ final float persistedScale = (1.0f + threshold) * scale + 1.0f;
+ mFullScreenMagnificationController.setScale(DISPLAY_0, persistedScale, DEFAULT_X,
+ DEFAULT_Y, /* animate= */ false,
+ AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+ mFullScreenMagnificationController.persistScale(DISPLAY_0);
+ mFullScreenMagnificationController.setScale(DISPLAY_0, scale, DEFAULT_X,
+ DEFAULT_Y, /* animate= */ false,
+ AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+
+ mMgh.transitionTo(mMgh.mPanningScalingState);
+
+ assertTrue(mMgh.mPanningScalingState.mDetectingPassPersistedScale);
+
+ mMgh.clearAndTransitionToStateDetecting();
+ mFullScreenMagnificationController.reset(DISPLAY_0, /* animate= */ false);
+ }
+
+ @Test
+ public void testTransitToPanningState_scaleDifferenceLessThanThreshold_doNotDetect() {
+ final float scale = 2.0f;
+ final float threshold = FullScreenMagnificationGestureHandler.PanningScalingState
+ .CHECK_DETECTING_PASS_PERSISTED_SCALE_THRESHOLD;
+ final float persistedScale = (1.0f + threshold) * scale - 0.1f;
+ mFullScreenMagnificationController.setScale(DISPLAY_0, persistedScale, DEFAULT_X,
+ DEFAULT_Y, /* animate= */ false,
+ AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+ mFullScreenMagnificationController.persistScale(DISPLAY_0);
+ mFullScreenMagnificationController.setScale(DISPLAY_0, scale, DEFAULT_X,
+ DEFAULT_Y, /* animate= */ false,
+ AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+
+ mMgh.transitionTo(mMgh.mPanningScalingState);
+
+ assertFalse(mMgh.mPanningScalingState.mDetectingPassPersistedScale);
+
+ mMgh.clearAndTransitionToStateDetecting();
+ mFullScreenMagnificationController.reset(DISPLAY_0, /* animate= */ false);
+ }
+
+ @Test
+ public void testPanningScaleToPersistedScale_detecting_vibrateAndClear() {
+ Vibrator vibrator = mock(Vibrator.class);
+ mContext.addMockSystemService(Vibrator.class, vibrator);
+
+ mMgh.mPanningScalingState.mDetectingPassPersistedScale = true;
+
+ final float persistedScale =
+ mFullScreenMagnificationController.getPersistedScale(DISPLAY_0);
+
+ mMgh.transitionTo(mMgh.mPanningScalingState);
+ mMgh.mPanningScalingState.setScaleAndClearIfNeeded(persistedScale, DEFAULT_X, DEFAULT_Y);
+
+ verify(vibrator).vibrate(any(VibrationEffect.class));
+ assertFalse(mMgh.mPanningScalingState.mScaling);
+
+ mMgh.clearAndTransitionToStateDetecting();
+ mFullScreenMagnificationController.reset(DISPLAY_0, /* animate= */ false);
+ }
+
+ @Test
+ public void testPanningScaleOverThreshold_notDetecting_startDetecting() {
+ final float persistedScale =
+ mFullScreenMagnificationController.getPersistedScale(DISPLAY_0);
+
+ mFullScreenMagnificationController.setScale(DISPLAY_0, persistedScale, DEFAULT_X,
+ DEFAULT_Y, /* animate= */ false,
+ AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+ mMgh.transitionTo(mMgh.mPanningScalingState);
+
+ final float threshold = FullScreenMagnificationGestureHandler.PanningScalingState
+ .CHECK_DETECTING_PASS_PERSISTED_SCALE_THRESHOLD;
+ final float scale = (1.0f + threshold) * persistedScale + 1.0f;
+ mMgh.mPanningScalingState.setScaleAndClearIfNeeded(scale, DEFAULT_X, DEFAULT_Y);
+
+ assertTrue(mMgh.mPanningScalingState.mDetectingPassPersistedScale);
+
+ mMgh.clearAndTransitionToStateDetecting();
+ mFullScreenMagnificationController.reset(DISPLAY_0, /* animate= */ false);
+ }
+
private void assertActionsInOrder(List<MotionEvent> actualEvents,
List<Integer> expectedActions) {
assertTrue(actualEvents.size() == expectedActions.size());
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index b0c3a6e..cf650d2 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -37,6 +37,7 @@
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.hardware.biometrics.AuthenticateOptions;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.IBiometricService;
@@ -681,7 +682,37 @@
TestableLooper.get(this).processAllMessages();
}
- private static class TestAuthenticationClient extends AuthenticationClient<Object> {
+ private static class TestAuthenticateOptions implements AuthenticateOptions {
+ @Override
+ public int getUserId() {
+ return 0;
+ }
+
+ @Override
+ public int getSensorId() {
+ return TEST_SENSOR_ID;
+ }
+
+ @Override
+ public int getDisplayState() {
+ return DISPLAY_STATE_UNKNOWN;
+ }
+
+ @NonNull
+ @Override
+ public String getOpPackageName() {
+ return "some.test.name";
+ }
+
+ @Nullable
+ @Override
+ public String getAttributionTag() {
+ return null;
+ }
+ }
+
+ private static class TestAuthenticationClient
+ extends AuthenticationClient<Object, TestAuthenticateOptions> {
boolean mStartedHal = false;
boolean mStoppedHal = false;
boolean mDestroyed = false;
@@ -700,9 +731,10 @@
@NonNull Supplier<Object> lazyDaemon, @NonNull IBinder token,
@NonNull ClientMonitorCallbackConverter listener, int cookie,
@NonNull BiometricContext biometricContext) {
- super(context, lazyDaemon, token, listener, 0 /* targetUserId */, 0 /* operationId */,
- false /* restricted */, TAG, cookie, false /* requireConfirmation */,
- TEST_SENSOR_ID, mock(BiometricLogger.class), biometricContext,
+ super(context, lazyDaemon, token, listener, 0 /* operationId */,
+ false /* restricted */, new TestAuthenticateOptions(), cookie,
+ false /* requireConfirmation */,
+ mock(BiometricLogger.class), biometricContext,
true /* isStrongBiometric */, null /* taskStackListener */,
null /* lockoutTracker */, false /* isKeyguard */,
true /* shouldVibrate */,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
index 184a556..3ff802c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
@@ -33,6 +33,7 @@
import android.hardware.biometrics.common.ICancellationSignal;
import android.hardware.biometrics.face.ISession;
import android.hardware.face.Face;
+import android.hardware.face.FaceAuthenticateOptions;
import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
@@ -153,14 +154,18 @@
when(mHal.getInterfaceVersion()).thenReturn(version);
final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+ final FaceAuthenticateOptions options = new FaceAuthenticateOptions.Builder()
+ .setOpPackageName("test-owner")
+ .setUserId(5)
+ .setSensorId(9)
+ .build();
return new FaceAuthenticationClient(mContext, () -> aidl, mToken,
- 2 /* requestId */, mClientMonitorCallbackConverter, 5 /* targetUserId */, OP_ID,
- false /* restricted */, "test-owner", 4 /* cookie */,
- false /* requireConfirmation */, 9 /* sensorId */,
+ 2 /* requestId */, mClientMonitorCallbackConverter, OP_ID,
+ false /* restricted */, options, 4 /* cookie */,
+ false /* requireConfirmation */,
mBiometricLogger, mBiometricContext, true /* isStrongBiometric */,
mUsageStats, null /* mLockoutCache */, false /* allowBackgroundAuthentication */,
- null /* sensorPrivacyManager */,
- 0 /* biometricStrength */) {
+ null /* sensorPrivacyManager */, 0 /* biometricStrength */) {
@Override
protected ActivityTaskManager getActivityTaskManager() {
return mActivityTaskManager;
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
index e0fdb8c..c4c5505 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
@@ -25,6 +25,7 @@
import static org.mockito.Mockito.when;
import android.hardware.biometrics.face.ISession;
+import android.hardware.face.FaceAuthenticateOptions;
import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
@@ -112,8 +113,13 @@
final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
return new FaceDetectClient(mContext, () -> aidl, mToken,
- 99 /* requestId */, mClientMonitorCallbackConverter, USER_ID,
- "own-it", 5 /* sensorId */, mBiometricLogger, mBiometricContext,
+ 99 /* requestId */, mClientMonitorCallbackConverter,
+ new FaceAuthenticateOptions.Builder()
+ .setUserId(USER_ID)
+ .setSensorId(5)
+ .setOpPackageName("own-it")
+ .build(),
+ mBiometricLogger, mBiometricContext,
false /* isStrongBiometric */, null /* sensorPrivacyManager */);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java
index 3b66eab..54d6478 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java
@@ -144,7 +144,7 @@
@Test
public void testAuthentication_enrollmentCallbackNeverNotified() {
- AuthenticationClient<?> client = mock(AuthenticationClient.class);
+ AuthenticationClient<?, ?> client = mock(AuthenticationClient.class);
mCallback.onClientFinished(client, true /* success */);
verify(mBiometricStateListener, never()).onEnrollmentsChanged(anyInt(), anyInt(),
anyBoolean());
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
index a4048a2..25a700a 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
@@ -16,24 +16,35 @@
package com.android.server.biometrics.sensors.fingerprint;
+import static android.Manifest.permission.USE_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
+import static android.app.AppOpsManager.OP_USE_BIOMETRIC;
+import static android.app.AppOpsManager.OP_USE_FINGERPRINT;
+import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
+import static android.hardware.fingerprint.FingerprintManager.SENSOR_ID_ANY;
+import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_REAR;
+import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL;
+
+import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
+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.AppOpsManager;
import android.content.pm.PackageManager;
import android.hardware.biometrics.IBiometricService;
-import android.hardware.biometrics.common.CommonProps;
-import android.hardware.biometrics.common.SensorStrength;
-import android.hardware.biometrics.fingerprint.FingerprintSensorType;
-import android.hardware.biometrics.fingerprint.IFingerprint;
-import android.hardware.biometrics.fingerprint.SensorLocation;
-import android.hardware.biometrics.fingerprint.SensorProps;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
+import android.hardware.fingerprint.IFingerprintServiceReceiver;
+import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
import android.testing.TestableContext;
@@ -44,10 +55,13 @@
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintProvider;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -64,6 +78,8 @@
private static final int ID_VIRTUAL = 6;
private static final String NAME_DEFAULT = "default";
private static final String NAME_VIRTUAL = "virtual";
+ private static final List<FingerprintSensorPropertiesInternal> HIDL_AUTHENTICATORS =
+ List.of();
@Rule
public final MockitoRule mMockito = MockitoJUnit.rule();
@@ -74,47 +90,80 @@
public final FakeSettingsProviderRule mSettingsRule = FakeSettingsProvider.rule();
@Mock
+ private AppOpsManager mAppOpsManager;
+ @Mock
private BiometricContext mBiometricContext;
@Mock
private IBiometricService mIBiometricService;
@Mock
- private IFingerprint mIFingerprintDefault;
+ private FingerprintProvider mFingerprintDefault;
@Mock
- private IFingerprint mIFingerprintVirtual;
+ private FingerprintProvider mFingerprintVirtual;
+ @Mock
+ private IFingerprintServiceReceiver mServiceReceiver;
+ @Mock
+ private IBinder mToken;
- private final SensorProps mSensorPropsDefault = createProps(ID_DEFAULT,
- SensorStrength.STRONG, FingerprintSensorType.POWER_BUTTON);
- private final SensorProps mSensorPropsVirtual = createProps(ID_VIRTUAL,
- SensorStrength.STRONG, FingerprintSensorType.UNDER_DISPLAY_OPTICAL);
+ @Captor
+ private ArgumentCaptor<FingerprintAuthenticateOptions> mAuthenticateOptionsCaptor;
+
+ private final FingerprintSensorPropertiesInternal mSensorPropsDefault =
+ new FingerprintSensorPropertiesInternal(ID_DEFAULT, STRENGTH_STRONG,
+ 2 /* maxEnrollmentsPerUser */,
+ List.of(),
+ TYPE_REAR,
+ false /* resetLockoutRequiresHardwareAuthToken */);
+ private final FingerprintSensorPropertiesInternal mSensorPropsVirtual =
+ new FingerprintSensorPropertiesInternal(ID_VIRTUAL, STRENGTH_STRONG,
+ 2 /* maxEnrollmentsPerUser */,
+ List.of(),
+ TYPE_UDFPS_OPTICAL,
+ false /* resetLockoutRequiresHardwareAuthToken */);
private FingerprintService mService;
@Before
public void setup() throws Exception {
- when(mIFingerprintDefault.getSensorProps()).thenReturn(
- new SensorProps[]{mSensorPropsDefault});
- when(mIFingerprintVirtual.getSensorProps()).thenReturn(
- new SensorProps[]{mSensorPropsVirtual});
+ when(mFingerprintDefault.getSensorProperties()).thenReturn(List.of(mSensorPropsDefault));
+ when(mFingerprintVirtual.getSensorProperties()).thenReturn(List.of(mSensorPropsVirtual));
+ when(mFingerprintDefault.containsSensor(anyInt()))
+ .thenAnswer(i -> i.getArguments()[0].equals(ID_DEFAULT));
+ when(mFingerprintVirtual.containsSensor(anyInt()))
+ .thenAnswer(i -> i.getArguments()[0].equals(ID_VIRTUAL));
- mContext.getTestablePermissions().setPermission(
- USE_BIOMETRIC_INTERNAL, PackageManager.PERMISSION_GRANTED);
+ mContext.addMockSystemService(AppOpsManager.class, mAppOpsManager);
+ for (int permission : List.of(OP_USE_BIOMETRIC, OP_USE_FINGERPRINT)) {
+ when(mAppOpsManager.noteOp(eq(permission), anyInt(), any(), any(), any()))
+ .thenReturn(AppOpsManager.MODE_ALLOWED);
+ }
+
+ for (String permission : List.of(USE_BIOMETRIC, USE_BIOMETRIC_INTERNAL)) {
+ mContext.getTestablePermissions().setPermission(
+ permission, PackageManager.PERMISSION_GRANTED);
+ }
}
private void initServiceWith(String... aidlInstances) {
mService = new FingerprintService(mContext, mBiometricContext,
() -> mIBiometricService,
() -> aidlInstances,
- (fqName) -> {
- if (fqName.endsWith(NAME_DEFAULT)) return mIFingerprintDefault;
- if (fqName.endsWith(NAME_VIRTUAL)) return mIFingerprintVirtual;
+ (name) -> {
+ if (NAME_DEFAULT.equals(name)) return mFingerprintDefault;
+ if (NAME_VIRTUAL.equals(name)) return mFingerprintVirtual;
return null;
});
}
+ private void initServiceWithAndWait(String... aidlInstances) throws Exception {
+ initServiceWith(aidlInstances);
+ mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS);
+ waitForRegistration();
+ }
+
@Test
public void registerAuthenticators_defaultOnly() throws Exception {
initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
- mService.mServiceWrapper.registerAuthenticators(List.of());
+ mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS);
waitForRegistration();
verify(mIBiometricService).registerAuthenticator(eq(ID_DEFAULT), anyInt(), anyInt(), any());
@@ -126,7 +175,7 @@
Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED, 1);
- mService.mServiceWrapper.registerAuthenticators(List.of());
+ mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS);
waitForRegistration();
verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
@@ -136,7 +185,7 @@
public void registerAuthenticators_virtualAlwaysWhenNoOther() throws Exception {
initServiceWith(NAME_VIRTUAL);
- mService.mServiceWrapper.registerAuthenticators(List.of());
+ mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS);
waitForRegistration();
verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
@@ -155,13 +204,47 @@
latch.await(5, TimeUnit.SECONDS);
}
- private static SensorProps createProps(int id, byte strength, byte type) {
- final SensorProps props = new SensorProps();
- props.commonProps = new CommonProps();
- props.commonProps.sensorId = id;
- props.commonProps.sensorStrength = strength;
- props.sensorType = type;
- props.sensorLocations = new SensorLocation[]{new SensorLocation()};
- return props;
+ @Test
+ public void authenticateWithDefaultSensorId() throws Exception {
+ initServiceWithAndWait(NAME_DEFAULT, NAME_VIRTUAL);
+
+ final long operationId = 2;
+ mService.mServiceWrapper.authenticate(mToken, operationId, mServiceReceiver,
+ new FingerprintAuthenticateOptions.Builder()
+ .setSensorId(SENSOR_ID_ANY)
+ .build());
+
+ final FingerprintAuthenticateOptions options =
+ verifyAuthenticateWithNewRequestId(mFingerprintDefault, operationId);
+ assertThat(options.getSensorId()).isEqualTo(ID_DEFAULT);
+ verifyNoAuthenticate(mFingerprintVirtual);
+ }
+
+
+ private FingerprintAuthenticateOptions verifyAuthenticateWithNewRequestId(
+ FingerprintProvider provider, long operationId) {
+ return verifyAuthenticateWithNewRequestId(
+ provider, operationId, true /* shouldSchedule */);
+ }
+
+ private void verifyNoAuthenticate(FingerprintProvider provider) {
+ verifyAuthenticateWithNewRequestId(
+ provider, 0 /* operationId */, false /* shouldSchedule */);
+ }
+
+ private FingerprintAuthenticateOptions verifyAuthenticateWithNewRequestId(
+ FingerprintProvider provider, long operationId, boolean shouldSchedule) {
+ verify(provider, shouldSchedule ? times(1) : never())
+ .scheduleAuthenticate(eq(mToken), eq(operationId), anyInt(), any(),
+ mAuthenticateOptionsCaptor.capture(), anyBoolean(), anyInt(),
+ anyBoolean());
+ verify(provider, never()).scheduleAuthenticate(eq(mToken), anyLong(),
+ anyInt(), any(), mAuthenticateOptionsCaptor.capture(), anyLong(),
+ anyBoolean(), anyInt(), anyBoolean());
+
+ if (shouldSchedule) {
+ return mAuthenticateOptionsCaptor.getValue();
+ }
+ return null;
}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index 99f7905..f0f975c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -41,6 +41,7 @@
import android.hardware.biometrics.fingerprint.ISession;
import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
import android.hardware.fingerprint.IUdfpsOverlayController;
@@ -414,11 +415,16 @@
when(mHal.getInterfaceVersion()).thenReturn(version);
final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+ final FingerprintAuthenticateOptions options = new FingerprintAuthenticateOptions.Builder()
+ .setOpPackageName("test-owner")
+ .setUserId(5)
+ .setSensorId(9)
+ .build();
return new FingerprintAuthenticationClient(mContext, () -> aidl, mToken,
- REQUEST_ID, mClientMonitorCallbackConverter, 5 /* targetUserId */, OP_ID,
- false /* restricted */, "test-owner", 4 /* cookie */,
+ REQUEST_ID, mClientMonitorCallbackConverter, OP_ID,
+ false /* restricted */, options, 4 /* cookie */,
false /* requireConfirmation */,
- 9 /* sensorId */, mBiometricLogger, mBiometricContext,
+ mBiometricLogger, mBiometricContext,
true /* isStrongBiometric */,
null /* taskStackListener */, null /* lockoutCache */,
mUdfpsOverlayController, mSideFpsController, null, allowBackgroundAuthentication,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
index 2dbd8f6..e741e44 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
@@ -25,6 +25,7 @@
import static org.mockito.Mockito.when;
import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.IBinder;
import android.os.RemoteException;
@@ -117,8 +118,13 @@
final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
return new FingerprintDetectClient(mContext, () -> aidl, mToken,
- 6 /* requestId */, mClientMonitorCallbackConverter, 2 /* userId */,
- "a-test", 1 /* sensorId */, mBiometricLogger, mBiometricContext,
+ 6 /* requestId */, mClientMonitorCallbackConverter,
+ new FingerprintAuthenticateOptions.Builder()
+ .setUserId(2)
+ .setSensorId(1)
+ .setOpPackageName("a-test")
+ .build(),
+ mBiometricLogger, mBiometricContext,
mUdfpsOverlayController, null, true /* isStrongBiometric */);
}
}
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 0cd50f0..cc6f7c2 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
@@ -35,6 +35,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.argThat;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -67,7 +68,11 @@
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.hardware.Sensor;
+import android.hardware.display.DisplayManagerGlobal;
import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.IDisplayManager;
+import android.hardware.display.IVirtualDisplayCallback;
+import android.hardware.display.VirtualDisplayConfig;
import android.hardware.input.IInputManager;
import android.hardware.input.VirtualDpadConfig;
import android.hardware.input.VirtualKeyEvent;
@@ -144,6 +149,7 @@
private static final String DEVICE_NAME_3 = "device name 3";
private static final int DISPLAY_ID_1 = 2;
private static final int DISPLAY_ID_2 = 3;
+ private static final int NON_EXISTENT_DISPLAY_ID = 42;
private static final int DEVICE_OWNER_UID_1 = 50;
private static final int DEVICE_OWNER_UID_2 = 51;
private static final int UID_1 = 0;
@@ -162,6 +168,8 @@
private static final int FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES = 0x00000;
private static final int VIRTUAL_DEVICE_ID_1 = 42;
private static final int VIRTUAL_DEVICE_ID_2 = 43;
+ private static final VirtualDisplayConfig VIRTUAL_DISPLAY_CONFIG =
+ new VirtualDisplayConfig.Builder("virtual_display", 640, 480, 400).build();
private static final VirtualDpadConfig DPAD_CONFIG =
new VirtualDpadConfig.Builder()
.setVendorId(VENDOR_ID)
@@ -221,6 +229,8 @@
@Mock
private DisplayManagerInternal mDisplayManagerInternalMock;
@Mock
+ private IDisplayManager mIDisplayManager;
+ @Mock
private VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback;
@Mock
private DevicePolicyManager mDevicePolicyManagerMock;
@@ -237,6 +247,8 @@
@Mock
private IVirtualDeviceSoundEffectListener mSoundEffectListener;
@Mock
+ private IVirtualDisplayCallback mVirtualDisplayCallback;
+ @Mock
private Consumer<ArraySet<Integer>> mRunningAppsChangedCallback;
@Mock
private VirtualDeviceManagerInternal.VirtualDisplayListener mDisplayListener;
@@ -271,9 +283,13 @@
private Intent createRestrictedActivityBlockedIntent(List displayCategories,
String targetDisplayCategory) {
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(displayCategories), DISPLAY_ID_1);
- GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+ when(mDisplayManagerInternalMock.createVirtualDisplay(any(), any(), any(), any(),
+ eq(NONBLOCKED_APP_PACKAGE_NAME))).thenReturn(DISPLAY_ID_1);
+ VirtualDisplayConfig config = new VirtualDisplayConfig.Builder("display", 640, 480,
+ 420).setDisplayCategories(displayCategories).build();
+ mDeviceImpl.createVirtualDisplay(config, mVirtualDisplayCallback,
+ NONBLOCKED_APP_PACKAGE_NAME);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
DISPLAY_ID_1);
doNothing().when(mContext).startActivityAsUser(any(), any(), any());
@@ -327,6 +343,7 @@
mContext = Mockito.spy(new ContextWrapper(
InstrumentationRegistry.getInstrumentation().getTargetContext()));
doReturn(mContext).when(mContext).createContextAsUser(eq(Process.myUserHandle()), anyInt());
+ doNothing().when(mContext).sendBroadcastAsUser(any(), any());
when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(
mDevicePolicyManagerMock);
@@ -369,15 +386,13 @@
@Test
public void getDeviceIdForDisplayId_nonExistentDisplayId_returnsDefault() {
- mDeviceImpl.mVirtualDisplayIds.remove(DISPLAY_ID_1);
-
- assertThat(mVdm.getDeviceIdForDisplayId(DISPLAY_ID_1))
+ assertThat(mVdm.getDeviceIdForDisplayId(NON_EXISTENT_DISPLAY_ID))
.isEqualTo(DEVICE_ID_DEFAULT);
}
@Test
public void getDeviceIdForDisplayId_withValidVirtualDisplayId_returnsDeviceId() {
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
assertThat(mVdm.getDeviceIdForDisplayId(DISPLAY_ID_1))
.isEqualTo(mDeviceImpl.getDeviceId());
@@ -503,10 +518,9 @@
@Test
public void getDeviceIdsForUid_differentUidOnDevice_returnsNull() {
- GenericWindowPolicyController gwpc =
- mDeviceImpl.createWindowPolicyController(new ArrayList<>());
- mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID_1);
- gwpc.onRunningAppsChanged(Sets.newArraySet(UID_2));
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ mDeviceImpl.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_1).onRunningAppsChanged(
+ Sets.newArraySet(UID_2));
Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
assertThat(deviceIds).isEmpty();
@@ -514,10 +528,9 @@
@Test
public void getDeviceIdsForUid_oneUidOnDevice_returnsCorrectId() {
- GenericWindowPolicyController gwpc =
- mDeviceImpl.createWindowPolicyController(new ArrayList<>());
- mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID_1);
- gwpc.onRunningAppsChanged(Sets.newArraySet(UID_1));
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ mDeviceImpl.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_1).onRunningAppsChanged(
+ Sets.newArraySet(UID_1));
Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
assertThat(deviceIds).containsExactly(mDeviceImpl.getDeviceId());
@@ -525,10 +538,10 @@
@Test
public void getDeviceIdsForUid_twoUidsOnDevice_returnsCorrectId() {
- GenericWindowPolicyController gwpc =
- mDeviceImpl.createWindowPolicyController(new ArrayList<>());
- mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID_1);
- gwpc.onRunningAppsChanged(Sets.newArraySet(UID_1, UID_2));
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+
+ mDeviceImpl.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_1).onRunningAppsChanged(
+ Sets.newArraySet(UID_1, UID_2));
Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
assertThat(deviceIds).containsExactly(mDeviceImpl.getDeviceId());
@@ -538,11 +551,10 @@
public void getDeviceIdsForUid_twoDevicesUidOnOne_returnsCorrectId() {
VirtualDeviceImpl secondDevice = createVirtualDevice(VIRTUAL_DEVICE_ID_2,
DEVICE_OWNER_UID_2);
+ addVirtualDisplay(secondDevice, DISPLAY_ID_2);
- GenericWindowPolicyController gwpc =
- secondDevice.createWindowPolicyController(new ArrayList<>());
- secondDevice.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID_2);
- gwpc.onRunningAppsChanged(Sets.newArraySet(UID_1));
+ secondDevice.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_2).onRunningAppsChanged(
+ Sets.newArraySet(UID_1));
Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
assertThat(deviceIds).containsExactly(secondDevice.getDeviceId());
@@ -550,16 +562,16 @@
@Test
public void getDeviceIdsForUid_twoDevicesUidOnBoth_returnsCorrectId() {
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
VirtualDeviceImpl secondDevice = createVirtualDevice(VIRTUAL_DEVICE_ID_2,
DEVICE_OWNER_UID_2);
- GenericWindowPolicyController gwpc1 =
- mDeviceImpl.createWindowPolicyController(new ArrayList<>());
- GenericWindowPolicyController gwpc2 =
- secondDevice.createWindowPolicyController(new ArrayList<>());
- mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc1, DISPLAY_ID_1);
- secondDevice.onVirtualDisplayCreatedLocked(gwpc2, DISPLAY_ID_2);
- gwpc1.onRunningAppsChanged(Sets.newArraySet(UID_1));
- gwpc2.onRunningAppsChanged(Sets.newArraySet(UID_1, UID_2));
+ addVirtualDisplay(secondDevice, DISPLAY_ID_2);
+
+
+ mDeviceImpl.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_1).onRunningAppsChanged(
+ Sets.newArraySet(UID_1));
+ secondDevice.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_2).onRunningAppsChanged(
+ Sets.newArraySet(UID_1, UID_2));
Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
assertThat(deviceIds).containsExactly(
@@ -568,8 +580,7 @@
@Test
public void getPreferredLocaleListForApp_keyboardAttached_returnLocaleHints() {
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
-
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER);
mVdms.notifyRunningAppsChanged(mDeviceImpl.getDeviceId(), Sets.newArraySet(UID_1));
@@ -609,8 +620,8 @@
.setLanguageTag("fr-FR")
.build();
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
- secondDevice.mVirtualDisplayIds.add(DISPLAY_ID_2);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ addVirtualDisplay(secondDevice, DISPLAY_ID_2);
mDeviceImpl.createVirtualKeyboard(firstKeyboardConfig, BINDER);
secondDevice.createVirtualKeyboard(secondKeyboardConfig, secondBinder);
@@ -640,10 +651,9 @@
@Test
public void onVirtualDisplayRemovedLocked_doesNotThrowException() {
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
// This call should not throw any exceptions.
- mDeviceImpl.onVirtualDisplayRemovedLocked(DISPLAY_ID_1);
+ mDeviceImpl.onVirtualDisplayRemoved(DISPLAY_ID_1);
}
@Test
@@ -659,8 +669,8 @@
@Test
public void onVirtualDisplayRemovedLocked_listenersNotified() {
mLocalService.registerVirtualDisplayListener(mDisplayListener);
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
mLocalService.onVirtualDisplayRemoved(mDeviceImpl, DISPLAY_ID_1);
TestableLooper.get(this).processAllMessages();
@@ -723,8 +733,7 @@
verify(mIPowerManagerMock, never()).acquireWakeLock(any(Binder.class), anyInt(),
nullable(String.class), nullable(String.class), nullable(WorkSource.class),
nullable(String.class), anyInt(), eq(null));
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
verify(mIPowerManagerMock).acquireWakeLock(any(Binder.class), anyInt(),
nullable(String.class), nullable(String.class), nullable(WorkSource.class),
nullable(String.class), eq(DISPLAY_ID_1), eq(null));
@@ -733,12 +742,9 @@
@Test
public void onVirtualDisplayCreatedLocked_duplicateCalls_onlyOneWakeLockIsAcquired()
throws RemoteException {
- GenericWindowPolicyController gwpc = mDeviceImpl.createWindowPolicyController(
- new ArrayList<>());
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
assertThrows(IllegalStateException.class,
- () -> mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID_1));
+ () -> addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1));
TestableLooper.get(this).processAllMessages();
verify(mIPowerManagerMock).acquireWakeLock(any(Binder.class), anyInt(),
nullable(String.class), nullable(String.class), nullable(WorkSource.class),
@@ -749,13 +755,12 @@
public void onVirtualDisplayRemovedLocked_unknownDisplayId_throwsException() {
final int unknownDisplayId = 999;
assertThrows(IllegalStateException.class,
- () -> mDeviceImpl.onVirtualDisplayRemovedLocked(unknownDisplayId));
+ () -> mDeviceImpl.onVirtualDisplayRemoved(unknownDisplayId));
}
@Test
public void onVirtualDisplayRemovedLocked_wakeLockIsReleased() throws RemoteException {
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
ArgumentCaptor<IBinder> wakeLockCaptor = ArgumentCaptor.forClass(IBinder.class);
TestableLooper.get(this).processAllMessages();
verify(mIPowerManagerMock).acquireWakeLock(wakeLockCaptor.capture(),
@@ -764,14 +769,13 @@
nullable(String.class), eq(DISPLAY_ID_1), eq(null));
IBinder wakeLock = wakeLockCaptor.getValue();
- mDeviceImpl.onVirtualDisplayRemovedLocked(DISPLAY_ID_1);
+ mDeviceImpl.onVirtualDisplayRemoved(DISPLAY_ID_1);
verify(mIPowerManagerMock).releaseWakeLock(eq(wakeLock), anyInt());
}
@Test
public void addVirtualDisplay_displayNotReleased_wakeLockIsReleased() throws RemoteException {
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
ArgumentCaptor<IBinder> wakeLockCaptor = ArgumentCaptor.forClass(IBinder.class);
TestableLooper.get(this).processAllMessages();
verify(mIPowerManagerMock).acquireWakeLock(wakeLockCaptor.capture(),
@@ -825,7 +829,7 @@
@Test
public void createVirtualTouchscreen_positiveDisplayDimension_successful() {
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
VirtualTouchscreenConfig positiveConfig =
new VirtualTouchscreenConfig.Builder(
/* touchscrenWidth= */ 600, /* touchscreenHeight= */ 800)
@@ -863,7 +867,7 @@
@Test
public void createVirtualNavigationTouchpad_positiveDisplayDimension_successful() {
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
VirtualNavigationTouchpadConfig positiveConfig =
new VirtualNavigationTouchpadConfig.Builder(
/* touchpadHeight= */ 50, /* touchpadWidth= */ 50)
@@ -888,7 +892,7 @@
@Test
public void createVirtualDpad_noPermission_failsSecurityException() {
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
assertThrows(SecurityException.class,
() -> mDeviceImpl.createVirtualDpad(DPAD_CONFIG, BINDER));
@@ -897,7 +901,7 @@
@Test
public void createVirtualKeyboard_noPermission_failsSecurityException() {
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
assertThrows(SecurityException.class,
() -> mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER));
@@ -906,7 +910,7 @@
@Test
public void createVirtualMouse_noPermission_failsSecurityException() {
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
assertThrows(SecurityException.class,
() -> mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER));
@@ -915,7 +919,7 @@
@Test
public void createVirtualTouchscreen_noPermission_failsSecurityException() {
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
assertThrows(SecurityException.class,
() -> mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER));
@@ -924,7 +928,7 @@
@Test
public void createVirtualNavigationTouchpad_noPermission_failsSecurityException() {
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
assertThrows(SecurityException.class,
() -> mDeviceImpl.createVirtualNavigationTouchpad(NAVIGATION_TOUCHPAD_CONFIG,
@@ -934,7 +938,7 @@
@Test
public void onAudioSessionStarting_noPermission_failsSecurityException() {
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
assertThrows(SecurityException.class,
() -> mDeviceImpl.onAudioSessionStarting(
@@ -951,7 +955,7 @@
@Test
public void createVirtualDpad_hasDisplay_obtainFileDescriptor() {
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
mDeviceImpl.createVirtualDpad(DPAD_CONFIG, BINDER);
assertWithMessage("Virtual dpad should register fd when the display matches").that(
mInputController.getInputDeviceDescriptors()).isNotEmpty();
@@ -961,7 +965,7 @@
@Test
public void createVirtualKeyboard_hasDisplay_obtainFileDescriptor() {
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER);
assertWithMessage("Virtual keyboard should register fd when the display matches").that(
mInputController.getInputDeviceDescriptors()).isNotEmpty();
@@ -971,7 +975,7 @@
@Test
public void createVirtualKeyboard_keyboardCreated_localeUpdated() {
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER);
assertWithMessage("Virtual keyboard should register fd when the display matches")
.that(mInputController.getInputDeviceDescriptors())
@@ -992,7 +996,7 @@
.setAssociatedDisplayId(DISPLAY_ID_1)
.build();
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
mDeviceImpl.createVirtualKeyboard(configWithoutExplicitLayoutInfo, BINDER);
assertWithMessage("Virtual keyboard should register fd when the display matches")
.that(mInputController.getInputDeviceDescriptors())
@@ -1005,7 +1009,7 @@
@Test
public void virtualDeviceWithoutKeyboard_noLocaleUpdate() {
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
// no preceding call to createVirtualKeyboard()
assertThat(mDeviceImpl.getDeviceLocaleList()).isNull();
@@ -1013,7 +1017,7 @@
@Test
public void createVirtualMouse_hasDisplay_obtainFileDescriptor() {
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER);
assertWithMessage("Virtual mouse should register fd when the display matches").that(
mInputController.getInputDeviceDescriptors()).isNotEmpty();
@@ -1023,7 +1027,7 @@
@Test
public void createVirtualTouchscreen_hasDisplay_obtainFileDescriptor() {
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER);
assertWithMessage("Virtual touchscreen should register fd when the display matches").that(
mInputController.getInputDeviceDescriptors()).isNotEmpty();
@@ -1033,7 +1037,7 @@
@Test
public void createVirtualNavigationTouchpad_hasDisplay_obtainFileDescriptor() {
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
mDeviceImpl.createVirtualNavigationTouchpad(NAVIGATION_TOUCHPAD_CONFIG, BINDER);
assertWithMessage("Virtual navigation touchpad should register fd when the display matches")
.that(
@@ -1055,8 +1059,7 @@
@Test
public void onAudioSessionStarting_hasVirtualAudioController() {
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
mDeviceImpl.onAudioSessionStarting(DISPLAY_ID_1, mRoutingCallback, mConfigChangedCallback);
@@ -1065,8 +1068,7 @@
@Test
public void onAudioSessionEnded_noVirtualAudioController() {
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
mDeviceImpl.onAudioSessionStarting(DISPLAY_ID_1, mRoutingCallback, mConfigChangedCallback);
mDeviceImpl.onAudioSessionEnded();
@@ -1076,8 +1078,7 @@
@Test
public void close_cleanVirtualAudioController() {
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
mDeviceImpl.onAudioSessionStarting(DISPLAY_ID_1, mRoutingCallback, mConfigChangedCallback);
mDeviceImpl.close();
@@ -1321,9 +1322,9 @@
@Test
public void setShowPointerIcon_setsValueForAllDisplays() {
- mDeviceImpl.mVirtualDisplayIds.add(1);
- mDeviceImpl.mVirtualDisplayIds.add(2);
- mDeviceImpl.mVirtualDisplayIds.add(3);
+ addVirtualDisplay(mDeviceImpl, 1);
+ addVirtualDisplay(mDeviceImpl, 2);
+ addVirtualDisplay(mDeviceImpl, 3);
VirtualMouseConfig config1 = new VirtualMouseConfig.Builder()
.setAssociatedDisplayId(1)
.setInputDeviceName(DEVICE_NAME_1)
@@ -1346,7 +1347,9 @@
mDeviceImpl.createVirtualMouse(config1, BINDER);
mDeviceImpl.createVirtualMouse(config2, BINDER);
mDeviceImpl.createVirtualMouse(config3, BINDER);
+ clearInvocations(mInputManagerInternalMock);
mDeviceImpl.setShowPointerIcon(false);
+
verify(mInputManagerInternalMock, times(3)).setPointerIconVisible(eq(false), anyInt());
verify(mInputManagerInternalMock, never()).setPointerIconVisible(eq(true), anyInt());
mDeviceImpl.setShowPointerIcon(true);
@@ -1355,9 +1358,8 @@
@Test
public void openNonBlockedAppOnVirtualDisplay_doesNotStartBlockedAlertActivity() {
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
- GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
DISPLAY_ID_1);
doNothing().when(mContext).startActivityAsUser(any(), any(), any());
@@ -1376,9 +1378,8 @@
@Test
public void openPermissionControllerOnVirtualDisplay_startBlockedAlertActivity() {
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
- GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
DISPLAY_ID_1);
doNothing().when(mContext).startActivityAsUser(any(), any(), any());
@@ -1397,9 +1398,8 @@
@Test
public void openSettingsOnVirtualDisplay_startBlockedAlertActivity() {
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
- GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
DISPLAY_ID_1);
doNothing().when(mContext).startActivityAsUser(any(), any(), any());
@@ -1418,9 +1418,8 @@
@Test
public void openVendingOnVirtualDisplay_startBlockedAlertActivity() {
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
- GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
DISPLAY_ID_1);
doNothing().when(mContext).startActivityAsUser(any(), any(), any());
@@ -1439,9 +1438,8 @@
@Test
public void openGoogleDialerOnVirtualDisplay_startBlockedAlertActivity() {
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
- GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
DISPLAY_ID_1);
doNothing().when(mContext).startActivityAsUser(any(), any(), any());
@@ -1460,9 +1458,8 @@
@Test
public void openGoogleMapsOnVirtualDisplay_startBlockedAlertActivity() {
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
- GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
DISPLAY_ID_1);
doNothing().when(mContext).startActivityAsUser(any(), any(), any());
@@ -1482,9 +1479,8 @@
@Test
public void registerRunningAppsChangedListener_onRunningAppsChanged_listenersNotified() {
ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(UID_1, UID_2));
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
- GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
DISPLAY_ID_1);
gwpc.onRunningAppsChanged(uids);
@@ -1497,11 +1493,10 @@
@Test
public void noRunningAppsChangedListener_onRunningAppsChanged_doesNotThrowException() {
ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(UID_1, UID_2));
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
- GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
DISPLAY_ID_1);
- mDeviceImpl.onVirtualDisplayRemovedLocked(DISPLAY_ID_1);
+ gwpc.unregisterRunningAppsChangedListener(mDeviceImpl);
// This call should not throw any exceptions.
gwpc.onRunningAppsChanged(uids);
@@ -1512,9 +1507,8 @@
@Test
public void canActivityBeLaunched_activityCanLaunch() {
Intent intent = new Intent(ACTION_VIEW, Uri.parse(TEST_SITE));
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
- GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
DISPLAY_ID_1);
ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
NONBLOCKED_APP_PACKAGE_NAME,
@@ -1537,9 +1531,8 @@
doReturn(interceptor).when(interceptor).asBinder();
doReturn(interceptor).when(interceptor).queryLocalInterface(anyString());
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
- GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
DISPLAY_ID_1);
ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
NONBLOCKED_APP_PACKAGE_NAME,
@@ -1581,9 +1574,8 @@
doReturn(interceptor).when(interceptor).asBinder();
doReturn(interceptor).when(interceptor).queryLocalInterface(anyString());
- mDeviceImpl.onVirtualDisplayCreatedLocked(
- mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
- GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
DISPLAY_ID_1);
ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
NONBLOCKED_APP_PACKAGE_NAME,
@@ -1626,8 +1618,7 @@
}
@Test
- public void
- restrictedActivityOnNonMatchingRestrictedVirtualDisplay_startBlockedAlertActivity() {
+ public void restrictedActivityNonMatchingRestrictedVirtualDisplay_startBlockedAlertActivity() {
Intent blockedAppIntent = createRestrictedActivityBlockedIntent(List.of("abc"), "def");
verify(mContext).startActivityAsUser(argThat(intent ->
intent.filterEquals(blockedAppIntent)), any(), any());
@@ -1654,15 +1645,15 @@
@Test
public void getDisplayIdsForDevice_oneDisplay_resultContainsCorrectDisplayId() {
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
ArraySet<Integer> displayIds = mLocalService.getDisplayIdsForDevice(VIRTUAL_DEVICE_ID_1);
assertThat(displayIds).containsExactly(DISPLAY_ID_1);
}
@Test
public void getDisplayIdsForDevice_twoDisplays_resultContainsCorrectDisplayIds() {
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
- mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_2);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_2);
ArraySet<Integer> displayIds = mLocalService.getDisplayIdsForDevice(VIRTUAL_DEVICE_ID_1);
assertThat(displayIds).containsExactly(DISPLAY_ID_1, DISPLAY_ID_2);
}
@@ -1677,15 +1668,22 @@
private VirtualDeviceImpl createVirtualDevice(int virtualDeviceId, int ownerUid,
VirtualDeviceParams params) {
VirtualDeviceImpl virtualDeviceImpl = new VirtualDeviceImpl(mContext,
- mAssociationInfo, new Binder(), ownerUid, virtualDeviceId,
- mInputController, mSensorController, mCameraAccessController,
- /* onDeviceCloseListener= */ deviceId -> mVdms.removeVirtualDevice(deviceId),
+ mAssociationInfo, mVdms, new Binder(), ownerUid, virtualDeviceId,
+ mInputController, mSensorController, mCameraAccessController
+ /* onDeviceCloseListener= */ /*deviceId -> mVdms.removeVirtualDevice(deviceId)*/,
mPendingTrampolineCallback, mActivityListener, mSoundEffectListener,
- mRunningAppsChangedCallback, params);
+ mRunningAppsChangedCallback, params, new DisplayManagerGlobal(mIDisplayManager));
mVdms.addVirtualDevice(virtualDeviceImpl);
return virtualDeviceImpl;
}
+ private void addVirtualDisplay(VirtualDeviceImpl virtualDevice, int displayId) {
+ when(mDisplayManagerInternalMock.createVirtualDisplay(any(), eq(mVirtualDisplayCallback),
+ eq(virtualDevice), any(), any())).thenReturn(displayId);
+ virtualDevice.createVirtualDisplay(VIRTUAL_DISPLAY_CONFIG, mVirtualDisplayCallback,
+ NONBLOCKED_APP_PACKAGE_NAME);
+ }
+
/** Helper class to drop permissions temporarily and restore them at the end of a test. */
static final class DropShellPermissionsTemporarily implements AutoCloseable {
DropShellPermissionsTemporarily() {
diff --git a/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt
index 34540c3..b2bfd2b 100644
--- a/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt
@@ -167,8 +167,8 @@
Mockito.`when`(context.packageManager).thenReturn(packageManager)
val info = createMockReceiver()
- Mockito.`when`(packageManager.queryBroadcastReceivers(Mockito.any(), Mockito.anyInt()))
- .thenReturn(listOf(info))
+ Mockito.`when`(packageManager.queryBroadcastReceiversAsUser(Mockito.any(), Mockito.anyInt(),
+ Mockito.anyInt())).thenReturn(listOf(info))
Mockito.`when`(packageManager.getReceiverInfo(Mockito.any(), Mockito.anyInt()))
.thenReturn(info.activityInfo)
diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java
index fb1a8f8..c0a994b 100644
--- a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java
@@ -40,6 +40,9 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.io.StringWriter;
import java.util.Arrays;
import java.util.List;
@@ -49,7 +52,8 @@
private static final int CONTEXT_HUB_ID = 3;
private static final String CONTEXT_HUB_STRING = "Context Hub Info Test";
- private Context mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ private final Context mContext =
+ InstrumentationRegistry.getInstrumentation().getTargetContext();
@Mock private IContextHubWrapper mMockContextHubWrapper;
@Mock private ContextHubInfo mMockContextHubInfo;
@Rule public final MockitoRule mockito = MockitoJUnit.rule();
@@ -62,20 +66,29 @@
when(mMockContextHubInfo.toString()).thenReturn(CONTEXT_HUB_STRING);
when(mMockContextHubWrapper.getHubs()).thenReturn(hubInfo);
- when(mMockContextHubWrapper.supportsLocationSettingNotifications())
- .thenReturn(true);
+ when(mMockContextHubWrapper.supportsLocationSettingNotifications()).thenReturn(true);
when(mMockContextHubWrapper.supportsWifiSettingNotifications()).thenReturn(true);
- when(mMockContextHubWrapper.supportsAirplaneModeSettingNotifications())
- .thenReturn(true);
- when(mMockContextHubWrapper.supportsMicrophoneSettingNotifications())
- .thenReturn(true);
+ when(mMockContextHubWrapper.supportsAirplaneModeSettingNotifications()).thenReturn(true);
+ when(mMockContextHubWrapper.supportsMicrophoneSettingNotifications()).thenReturn(true);
when(mMockContextHubWrapper.supportsBtSettingNotifications()).thenReturn(true);
}
-// TODO (b/254290317): These existing tests are to setup the testing infra for the ContextHub
-// service and verify the constructor correctly registers a context hub.
-// We need to augment these tests to cover the full behavior of the
-// ContextHub service
+ @Test
+ public void testDump_emptyPreloadedNanoappList() {
+ when(mMockContextHubWrapper.getPreloadedNanoappIds()).thenReturn(null);
+ StringWriter stringWriter = new StringWriter();
+
+ ContextHubService service = new ContextHubService(mContext, mMockContextHubWrapper);
+ service.dump(
+ new FileDescriptor(), new PrintWriter(stringWriter), /* args= */ new String[0]);
+
+ assertThat(stringWriter.toString()).isNotEmpty();
+ }
+
+ // TODO (b/254290317): These existing tests are to setup the testing infra for the ContextHub
+ // service and verify the constructor correctly registers a context hub.
+ // We need to augment these tests to cover the full behavior of the
+ // ContextHub service
@Test
public void testConstructorRegistersContextHub() throws RemoteException {
diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
index 9fc46c5..4a2e5d7 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
@@ -86,6 +86,7 @@
import android.telecom.TelecomManager;
import android.telephony.TelephonyManager;
import android.text.format.DateUtils;
+import android.util.Pair;
import android.util.Range;
import com.android.internal.app.ChooserActivity;
@@ -186,6 +187,7 @@
private ShortcutInfo mShortcutInfo;
private TestInjector mInjector;
private TestLooper mLooper;
+ private TestPerPackageThrottler mShortcutThrottler;
@Before
public void setUp() throws PackageManager.NameNotFoundException {
@@ -275,7 +277,9 @@
mInjector = new TestInjector();
mLooper = new TestLooper();
- mDataManager = new DataManager(mContext, mInjector, mLooper.getLooper());
+ mShortcutThrottler = new TestPerPackageThrottler();
+ mDataManager = new DataManager(mContext, mInjector, mLooper.getLooper(),
+ mShortcutThrottler);
mDataManager.initialize();
when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(),
@@ -283,10 +287,7 @@
mShortcutInfo = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
buildPerson());
- when(mShortcutServiceInternal.getShortcuts(
- anyInt(), anyString(), anyLong(), anyString(), anyList(), any(), any(),
- anyInt(), anyInt(), anyInt(), anyInt()))
- .thenReturn(Collections.singletonList(mShortcutInfo));
+ mockGetShortcuts(Collections.singletonList(mShortcutInfo));
verify(mShortcutServiceInternal).addShortcutChangeCallback(
mShortcutChangeCallbackCaptor.capture());
mShortcutChangeCallback = mShortcutChangeCallbackCaptor.getValue();
@@ -972,6 +973,7 @@
buildPerson());
ShortcutInfo shortcut3 = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, "sc3",
buildPerson());
+ mockGetShortcuts(List.of(shortcut1, shortcut2, shortcut3));
mShortcutChangeCallback.onShortcutsAddedOrUpdated(TEST_PKG_NAME,
Arrays.asList(shortcut1, shortcut2, shortcut3), UserHandle.of(USER_ID_PRIMARY));
mShortcutChangeCallback.onShortcutsRemoved(TEST_PKG_NAME,
@@ -1223,7 +1225,6 @@
eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
}
}
-
@Test
public void testUncacheOldestCachedShortcut_missingNotificationEvents() {
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
@@ -1233,6 +1234,7 @@
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, shortcutId,
buildPerson());
shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
+ mockGetShortcuts(Collections.singletonList(shortcut));
mShortcutChangeCallback.onShortcutsAddedOrUpdated(
TEST_PKG_NAME,
Collections.singletonList(shortcut),
@@ -1252,7 +1254,6 @@
eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
}
}
-
@Test
public void testUncacheOldestCachedShortcut_legacyConversation() {
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
@@ -1274,6 +1275,7 @@
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, shortcutId,
buildPerson());
shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
+ mockGetShortcuts(Collections.singletonList(shortcut));
mShortcutChangeCallback.onShortcutsAddedOrUpdated(
TEST_PKG_NAME,
Collections.singletonList(shortcut),
@@ -1311,7 +1313,8 @@
mDataManager.reportShareTargetEvent(appTargetEvent, intentFilter);
byte[] payload = mDataManager.getBackupPayload(USER_ID_PRIMARY);
- DataManager dataManager = new DataManager(mContext, mInjector, mLooper.getLooper());
+ DataManager dataManager = new DataManager(
+ mContext, mInjector, mLooper.getLooper(), mShortcutThrottler);
dataManager.onUserUnlocked(USER_ID_PRIMARY);
dataManager.restore(USER_ID_PRIMARY, payload);
ConversationInfo conversationInfo = dataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY)
@@ -1723,6 +1726,13 @@
return (queryFlags & flag) != 0;
}
+ private void mockGetShortcuts(List<ShortcutInfo> shortcutInfoList) {
+ when(mShortcutServiceInternal.getShortcuts(
+ anyInt(), anyString(), anyLong(), anyString(), any(), any(), any(),
+ anyInt(), anyInt(), anyInt(), anyInt()))
+ .thenReturn(shortcutInfoList);
+ }
+
// "Sends" a notification to a non-customized notification channel - the notification channel
// is something generic like "messages" and the notification has a shortcut id
private void sendGenericNotification() {
@@ -1984,4 +1994,11 @@
return mUsageStatsQueryHelper;
}
}
+
+ private static class TestPerPackageThrottler implements PerPackageThrottler {
+ @Override
+ public void scheduleDebounced(Pair<String, Integer> pkgUserId, Runnable runnable) {
+ runnable.run();
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/people/data/PerPackageThrottlerImplTest.java b/services/tests/servicestests/src/com/android/server/people/data/PerPackageThrottlerImplTest.java
new file mode 100644
index 0000000..672cbb9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/people/data/PerPackageThrottlerImplTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.server.people.data;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.util.Pair;
+
+import com.android.server.testutils.OffsettableClock;
+import com.android.server.testutils.TestHandler;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+@RunWith(JUnit4.class)
+public class PerPackageThrottlerImplTest {
+ private static final int DEBOUNCE_INTERVAL = 500;
+ private static final String PKG_ONE = "pkg_one";
+ private static final String PKG_TWO = "pkg_two";
+ private static final int USER_ID = 10;
+
+ private final OffsettableClock mClock = new OffsettableClock.Stopped();
+ private final TestHandler mTestHandler = new TestHandler(null, mClock);
+ private PerPackageThrottlerImpl mThrottler;
+
+ @Before
+ public void setUp() {
+ mThrottler = new PerPackageThrottlerImpl(mTestHandler, DEBOUNCE_INTERVAL);
+ }
+
+ @Test
+ public void scheduleDebounced() {
+ AtomicBoolean pkgOneRan = new AtomicBoolean();
+ AtomicBoolean pkgTwoRan = new AtomicBoolean();
+
+ mThrottler.scheduleDebounced(new Pair<>(PKG_ONE, USER_ID), () -> pkgOneRan.set(true));
+ mThrottler.scheduleDebounced(new Pair<>(PKG_ONE, USER_ID), () -> pkgOneRan.set(true));
+ mThrottler.scheduleDebounced(new Pair<>(PKG_TWO, USER_ID), () -> pkgTwoRan.set(true));
+ mThrottler.scheduleDebounced(new Pair<>(PKG_TWO, USER_ID), () -> pkgTwoRan.set(true));
+
+ assertFalse(pkgOneRan.get());
+ assertFalse(pkgTwoRan.get());
+ mClock.fastForward(DEBOUNCE_INTERVAL);
+ mTestHandler.timeAdvance();
+ assertTrue(pkgOneRan.get());
+ assertTrue(pkgTwoRan.get());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/NetworkTimeUpdateServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/NetworkTimeUpdateServiceTest.java
index 2f431bd..d91ee92 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/NetworkTimeUpdateServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/NetworkTimeUpdateServiceTest.java
@@ -111,12 +111,11 @@
verify(mMockNtpTrustedTime).forceRefresh(mDummyNetwork);
// Check everything happened that was supposed to.
+ NetworkTimeSuggestion expectedSuggestion = createExpectedSuggestion(timeResult);
+ verify(mockCallback).submitSuggestion(expectedSuggestion);
long expectedDelayMillis = normalPollingIntervalMillis;
verify(mockCallback).scheduleNextRefresh(
timeResult.getElapsedRealtimeMillis() + expectedDelayMillis);
-
- NetworkTimeSuggestion expectedSuggestion = createExpectedSuggestion(timeResult);
- verify(mockCallback).submitSuggestion(expectedSuggestion);
}
@Test
@@ -148,6 +147,7 @@
verify(mMockNtpTrustedTime).forceRefresh(mDummyNetwork);
// Check everything happened that was supposed to.
+ verify(mockCallback, never()).submitSuggestion(any());
long expectedDelayMillis;
if (i < tryAgainTimesMax) {
expectedDelayMillis = shortPollingIntervalMillis;
@@ -156,7 +156,6 @@
}
verify(mockCallback).scheduleNextRefresh(
mFakeElapsedRealtimeClock.getElapsedRealtimeMillis() + expectedDelayMillis);
- verify(mockCallback, never()).submitSuggestion(any());
reset(mMockNtpTrustedTime);
}
@@ -196,10 +195,10 @@
// initially.
verify(mMockNtpTrustedTime).forceRefresh(mDummyNetwork);
+ verify(mockCallback, times(1)).submitSuggestion(expectedSuggestion);
long expectedDelayMillis = normalPollingIntervalMillis;
verify(mockCallback).scheduleNextRefresh(
timeResult.getElapsedRealtimeMillis() + expectedDelayMillis);
- verify(mockCallback, times(1)).submitSuggestion(expectedSuggestion);
reset(mMockNtpTrustedTime);
}
@@ -222,6 +221,9 @@
// Expect a refresh attempt each time as the cached network time is too old.
verify(mMockNtpTrustedTime).forceRefresh(mDummyNetwork);
+ // No valid time, no suggestion.
+ verify(mockCallback, never()).submitSuggestion(any());
+
// Check the scheduling.
long expectedDelayMillis;
if (i < tryAgainTimesMax) {
@@ -232,9 +234,6 @@
verify(mockCallback).scheduleNextRefresh(
mFakeElapsedRealtimeClock.getElapsedRealtimeMillis() + expectedDelayMillis);
- // No valid time, no suggestion.
- verify(mockCallback, never()).submitSuggestion(any());
-
reset(mMockNtpTrustedTime);
// Simulate the passage of time for realism.
@@ -276,10 +275,10 @@
// initially.
verify(mMockNtpTrustedTime).forceRefresh(mDummyNetwork);
+ verify(mockCallback, times(1)).submitSuggestion(expectedSuggestion);
long expectedDelayMillis = normalPollingIntervalMillis;
verify(mockCallback).scheduleNextRefresh(
timeResult.getElapsedRealtimeMillis() + expectedDelayMillis);
- verify(mockCallback, times(1)).submitSuggestion(expectedSuggestion);
reset(mMockNtpTrustedTime);
}
@@ -302,15 +301,14 @@
// Expect a refresh attempt each time as the cached network time is too old.
verify(mMockNtpTrustedTime).forceRefresh(mDummyNetwork);
- // Check the scheduling. tryAgainTimesMax == 0, so the algorithm should start with
+ // No valid time, no suggestion.
+ verify(mockCallback, never()).submitSuggestion(any());
+ // Check the scheduling. tryAgainTimesMax == 0, so the algorithm should start with
long expectedDelayMillis = normalPollingIntervalMillis;
verify(mockCallback).scheduleNextRefresh(
mFakeElapsedRealtimeClock.getElapsedRealtimeMillis() + expectedDelayMillis);
- // No valid time, no suggestion.
- verify(mockCallback, never()).submitSuggestion(any());
-
reset(mMockNtpTrustedTime);
// Simulate the passage of time for realism.
@@ -352,10 +350,10 @@
// initially.
verify(mMockNtpTrustedTime).forceRefresh(mDummyNetwork);
+ verify(mockCallback, times(1)).submitSuggestion(expectedSuggestion);
long expectedDelayMillis = normalPollingIntervalMillis;
verify(mockCallback).scheduleNextRefresh(
timeResult.getElapsedRealtimeMillis() + expectedDelayMillis);
- verify(mockCallback, times(1)).submitSuggestion(expectedSuggestion);
reset(mMockNtpTrustedTime);
}
@@ -378,15 +376,15 @@
// Expect a refresh attempt each time as the cached network time is too old.
verify(mMockNtpTrustedTime).forceRefresh(mDummyNetwork);
+ // No valid time, no suggestion.
+ verify(mockCallback, never()).submitSuggestion(any());
+
// Check the scheduling. tryAgainTimesMax == -1, so it should always be
// shortPollingIntervalMillis.
long expectedDelayMillis = shortPollingIntervalMillis;
verify(mockCallback).scheduleNextRefresh(
mFakeElapsedRealtimeClock.getElapsedRealtimeMillis() + expectedDelayMillis);
- // No valid time, no suggestion.
- verify(mockCallback, never()).submitSuggestion(any());
-
reset(mMockNtpTrustedTime);
// Simulate the passage of time for realism.
@@ -426,11 +424,11 @@
// initially.
verify(mMockNtpTrustedTime).forceRefresh(mDummyNetwork);
+ NetworkTimeSuggestion expectedSuggestion = createExpectedSuggestion(timeResult1);
+ verify(mockCallback, times(1)).submitSuggestion(expectedSuggestion);
long expectedDelayMillis = normalPollingIntervalMillis;
verify(mockCallback).scheduleNextRefresh(
timeResult1.getElapsedRealtimeMillis() + expectedDelayMillis);
- NetworkTimeSuggestion expectedSuggestion = createExpectedSuggestion(timeResult1);
- verify(mockCallback, times(1)).submitSuggestion(expectedSuggestion);
reset(mMockNtpTrustedTime);
}
@@ -453,12 +451,13 @@
// Expect the refresh attempt to have been made: the timeResult is too old.
verify(mMockNtpTrustedTime).forceRefresh(mDummyNetwork);
+ // No valid time, no suggestion.
+ verify(mockCallback, never()).submitSuggestion(any());
+
long expectedDelayMillis = shortPollingIntervalMillis;
verify(mockCallback).scheduleNextRefresh(
mFakeElapsedRealtimeClock.getElapsedRealtimeMillis() + expectedDelayMillis);
- // No valid time, no suggestion.
- verify(mockCallback, never()).submitSuggestion(any());
reset(mMockNtpTrustedTime);
}
@@ -485,11 +484,11 @@
// Expect the refresh attempt to have been made: the timeResult is too old.
verify(mMockNtpTrustedTime).forceRefresh(mDummyNetwork);
+ NetworkTimeSuggestion expectedSuggestion = createExpectedSuggestion(timeResult2);
+ verify(mockCallback, times(1)).submitSuggestion(expectedSuggestion);
long expectedDelayMillis = normalPollingIntervalMillis;
verify(mockCallback).scheduleNextRefresh(
timeResult2.getElapsedRealtimeMillis() + expectedDelayMillis);
- NetworkTimeSuggestion expectedSuggestion = createExpectedSuggestion(timeResult2);
- verify(mockCallback, times(1)).submitSuggestion(expectedSuggestion);
reset(mMockNtpTrustedTime);
}
}
@@ -525,16 +524,16 @@
// Expect no refresh attempt to have been made.
verify(mMockNtpTrustedTime, never()).forceRefresh(any());
+ // Suggestions must be made every time if the cached time value is not too old in case it
+ // was refreshed by a different component.
+ NetworkTimeSuggestion expectedSuggestion = createExpectedSuggestion(timeResult);
+ verify(mockCallback, times(1)).submitSuggestion(expectedSuggestion);
+
// The next wake-up should be rescheduled for when the cached time value will become too
// old.
long expectedDelayMillis = normalPollingIntervalMillis;
verify(mockCallback).scheduleNextRefresh(
timeResult.getElapsedRealtimeMillis() + expectedDelayMillis);
-
- // Suggestions must be made every time if the cached time value is not too old in case it
- // was refreshed by a different component.
- NetworkTimeSuggestion expectedSuggestion = createExpectedSuggestion(timeResult);
- verify(mockCallback, times(1)).submitSuggestion(expectedSuggestion);
}
/**
@@ -568,13 +567,13 @@
// Expect a refresh attempt to have been made.
verify(mMockNtpTrustedTime, times(1)).forceRefresh(mDummyNetwork);
+ // Suggestions should not be made if the cached time value is too old.
+ verify(mockCallback, never()).submitSuggestion(any());
+
// The next wake-up should be rescheduled using the short polling interval.
long expectedDelayMillis = shortPollingIntervalMillis;
verify(mockCallback).scheduleNextRefresh(
mFakeElapsedRealtimeClock.getElapsedRealtimeMillis() + expectedDelayMillis);
-
- // Suggestions should not be made if the cached time value is too old.
- verify(mockCallback, never()).submitSuggestion(any());
}
/**
@@ -614,6 +613,9 @@
verify(mMockNtpTrustedTime, times(1)).forceRefresh(mDummyNetwork);
lastRefreshAttemptElapsedMillis = mFakeElapsedRealtimeClock.getElapsedRealtimeMillis();
+ // Suggestions should not be made if the cached time value is too old.
+ verify(mockCallback, never()).submitSuggestion(any());
+
// The next wake-up should be rescheduled using the normalPollingIntervalMillis.
// Because the time signal age > normalPollingIntervalMillis, the last refresh attempt
// time will be used.
@@ -622,9 +624,6 @@
lastRefreshAttemptElapsedMillis + expectedDelayMillis;
verify(mockCallback).scheduleNextRefresh(expectedNextRefreshElapsedMillis);
- // Suggestions should not be made if the cached time value is too old.
- verify(mockCallback, never()).submitSuggestion(any());
-
reset(mMockNtpTrustedTime);
}
@@ -646,6 +645,9 @@
// Expect no refresh attempt to have been made: time elapsed isn't enough.
verify(mMockNtpTrustedTime, never()).forceRefresh(any());
+ // Suggestions should not be made if the cached time value is too old.
+ verify(mockCallback, never()).submitSuggestion(any());
+
// The next wake-up should be rescheduled using the normal polling interval and the last
// refresh attempt time.
long expectedDelayMillis = normalPollingIntervalMillis;
@@ -653,11 +655,110 @@
lastRefreshAttemptElapsedMillis + expectedDelayMillis;
verify(mockCallback).scheduleNextRefresh(expectedNextRefreshElapsedMillis);
+ reset(mMockNtpTrustedTime);
+ }
+ }
+
+ /**
+ * Confirms that if a refreshAndRescheduleIfRequired() call is made and there was a recently
+ * failed refresh, then another won't be scheduled too soon.
+ */
+ @Test
+ public void engineImpl_refreshAndRescheduleIfRequired_minimumRefreshTimeEnforced_b269425914() {
+ mFakeElapsedRealtimeClock.setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS);
+
+ int normalPollingIntervalMillis = 7777777;
+ int shortPollingIntervalMillis = 3333;
+ int tryAgainTimesMax = 3;
+ NetworkTimeUpdateService.Engine engine = new NetworkTimeUpdateService.EngineImpl(
+ mFakeElapsedRealtimeClock,
+ normalPollingIntervalMillis, shortPollingIntervalMillis, tryAgainTimesMax,
+ mMockNtpTrustedTime);
+
+ NtpTrustedTime.TimeResult timeResult1;
+
+ // Start out with a successful refresh.
+ long lastRefreshAttemptElapsedMillis;
+ {
+ mFakeElapsedRealtimeClock.incrementMillis(normalPollingIntervalMillis);
+ timeResult1 = createNtpTimeResult(mFakeElapsedRealtimeClock.getElapsedRealtimeMillis());
+ when(mMockNtpTrustedTime.getCachedTimeResult()).thenReturn(null, timeResult1);
+ when(mMockNtpTrustedTime.forceRefresh(mDummyNetwork)).thenReturn(true);
+
+ RefreshCallbacks mockCallback = mock(RefreshCallbacks.class);
+ // Trigger the engine's logic.
+ engine.refreshAndRescheduleIfRequired(mDummyNetwork, "Test", mockCallback);
+
+ // Expect a refresh attempt to have been made.
+ verify(mMockNtpTrustedTime, times(1)).forceRefresh(mDummyNetwork);
+ lastRefreshAttemptElapsedMillis = mFakeElapsedRealtimeClock.getElapsedRealtimeMillis();
+
+ NetworkTimeSuggestion expectedSuggestion = createExpectedSuggestion(timeResult1);
+ verify(mockCallback, times(1)).submitSuggestion(expectedSuggestion);
+
+ // The next wake-up should be rescheduled using the normalPollingIntervalMillis.
+ // Because the time signal age > normalPollingIntervalMillis, the last refresh attempt
+ // time will be used.
+ long expectedDelayMillis = normalPollingIntervalMillis;
+ long expectedNextRefreshElapsedMillis =
+ lastRefreshAttemptElapsedMillis + expectedDelayMillis;
+ verify(mockCallback).scheduleNextRefresh(expectedNextRefreshElapsedMillis);
+
+ reset(mMockNtpTrustedTime);
+ }
+
+ // Now fail: This should result in the next refresh using a short polling interval.
+ {
+ mFakeElapsedRealtimeClock.incrementMillis(normalPollingIntervalMillis);
+ when(mMockNtpTrustedTime.getCachedTimeResult()).thenReturn(timeResult1);
+ when(mMockNtpTrustedTime.forceRefresh(mDummyNetwork)).thenReturn(false);
+
+ RefreshCallbacks mockCallback = mock(RefreshCallbacks.class);
+ // Trigger the engine's logic.
+ engine.refreshAndRescheduleIfRequired(mDummyNetwork, "Test", mockCallback);
+
+ verify(mMockNtpTrustedTime, times(1)).forceRefresh(mDummyNetwork);
+ lastRefreshAttemptElapsedMillis = mFakeElapsedRealtimeClock.getElapsedRealtimeMillis();
+
// Suggestions should not be made if the cached time value is too old.
verify(mockCallback, never()).submitSuggestion(any());
+ // The next wake-up should be rescheduled using the last refresh attempt time (because
+ // the latest time result is too old) and the short polling interval.
+ long expectedDelayMillis = shortPollingIntervalMillis;
+ long expectedNextRefreshElapsedMillis =
+ lastRefreshAttemptElapsedMillis + expectedDelayMillis;
+ verify(mockCallback).scheduleNextRefresh(expectedNextRefreshElapsedMillis);
+
reset(mMockNtpTrustedTime);
}
+
+ // Simulate some other thread successfully refreshing the value held by NtpTrustedTime and
+ // confirm it is handled correctly.
+ {
+ mFakeElapsedRealtimeClock.incrementMillis(shortPollingIntervalMillis / 2);
+ NtpTrustedTime.TimeResult timeResult2 = createNtpTimeResult(
+ mFakeElapsedRealtimeClock.getElapsedRealtimeMillis());
+ when(mMockNtpTrustedTime.getCachedTimeResult()).thenReturn(timeResult2);
+
+ mFakeElapsedRealtimeClock.incrementMillis(shortPollingIntervalMillis / 2);
+
+ RefreshCallbacks mockCallback = mock(RefreshCallbacks.class);
+ // Trigger the engine's logic.
+ engine.refreshAndRescheduleIfRequired(mDummyNetwork, "Test", mockCallback);
+
+ verify(mMockNtpTrustedTime, never()).forceRefresh(any());
+
+ NetworkTimeSuggestion expectedSuggestion = createExpectedSuggestion(timeResult2);
+ verify(mockCallback, times(1)).submitSuggestion(expectedSuggestion);
+
+ // The next wake-up should be rescheduled using the normal polling interval and the
+ // latest time result.
+ long expectedDelayMillis = normalPollingIntervalMillis;
+ long expectedNextRefreshElapsedMillis =
+ timeResult2.getElapsedRealtimeMillis() + expectedDelayMillis;
+ verify(mockCallback).scheduleNextRefresh(expectedNextRefreshElapsedMillis);
+ }
}
private static NetworkTimeSuggestion createExpectedSuggestion(
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
index 5a0867f..daa6823 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
@@ -386,16 +386,16 @@
.when(mMockContext).enforceCallingPermission(anyString(), any());
assertThrows(SecurityException.class,
- () -> mTimeDetectorService.clearNetworkTime());
+ () -> mTimeDetectorService.clearLatestNetworkTime());
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.SET_TIME), anyString());
}
@Test
- public void testClearNetworkTime() throws Exception {
+ public void testClearLatestNetworkSuggestion() throws Exception {
doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
- mTimeDetectorService.clearNetworkTime();
+ mTimeDetectorService.clearLatestNetworkTime();
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.SET_TIME), anyString());
@@ -403,53 +403,48 @@
}
@Test
- public void testLatestNetworkTime() {
- NtpTrustedTime.TimeResult latestNetworkTime = new NtpTrustedTime.TimeResult(
- 1234L, 54321L, 999, InetSocketAddress.createUnresolved("test.timeserver", 123));
- when(mMockNtpTrustedTime.getCachedTimeResult())
- .thenReturn(latestNetworkTime);
- UnixEpochTime expected = new UnixEpochTime(
- latestNetworkTime.getElapsedRealtimeMillis(), latestNetworkTime.getTimeMillis());
- assertEquals(expected, mTimeDetectorService.latestNetworkTime());
- }
-
- @Test
- public void testLatestNetworkTime_noTimeAvailable() {
- when(mMockNtpTrustedTime.getCachedTimeResult()).thenReturn(null);
- assertThrows(ParcelableException.class, () -> mTimeDetectorService.latestNetworkTime());
- }
-
- @Test
public void testGetLatestNetworkSuggestion() {
- if (TimeDetectorNetworkTimeHelper.isInUse()) {
- NetworkTimeSuggestion latestNetworkTime = createNetworkTimeSuggestion();
- mFakeTimeDetectorStrategySpy.setLatestNetworkTime(latestNetworkTime);
+ NetworkTimeSuggestion latestNetworkSuggestion = createNetworkTimeSuggestion();
+ mFakeTimeDetectorStrategySpy.setLatestNetworkTime(latestNetworkSuggestion);
- assertEquals(latestNetworkTime, mTimeDetectorService.getLatestNetworkSuggestion());
+ assertEquals(latestNetworkSuggestion, mTimeDetectorService.getLatestNetworkSuggestion());
+ }
+
+ @Test
+ public void testGetLatestNetworkSuggestion_noTimeAvailable() {
+ mFakeTimeDetectorStrategySpy.setLatestNetworkTime(null);
+
+ assertNull(mTimeDetectorService.getLatestNetworkSuggestion());
+ }
+
+ @Test
+ public void testLatestNetworkTime() {
+ if (TimeDetectorNetworkTimeHelper.isInUse()) {
+ NetworkTimeSuggestion latestNetworkSuggestion = createNetworkTimeSuggestion();
+ mFakeTimeDetectorStrategySpy.setLatestNetworkTime(latestNetworkSuggestion);
+
+ assertEquals(latestNetworkSuggestion.getUnixEpochTime(),
+ mTimeDetectorService.latestNetworkTime());
} else {
NtpTrustedTime.TimeResult latestNetworkTime = new NtpTrustedTime.TimeResult(
1234L, 54321L, 999, InetSocketAddress.createUnresolved("test.timeserver", 123));
when(mMockNtpTrustedTime.getCachedTimeResult())
.thenReturn(latestNetworkTime);
- UnixEpochTime expectedUnixEpochTime = new UnixEpochTime(
+ UnixEpochTime expected = new UnixEpochTime(
latestNetworkTime.getElapsedRealtimeMillis(),
latestNetworkTime.getTimeMillis());
- NetworkTimeSuggestion expected = new NetworkTimeSuggestion(
- expectedUnixEpochTime, latestNetworkTime.getUncertaintyMillis());
- assertEquals(expected, mTimeDetectorService.getLatestNetworkSuggestion());
+ assertEquals(expected, mTimeDetectorService.latestNetworkTime());
}
}
@Test
- public void testGetLatestNetworkSuggestion_noTimeAvailable() {
+ public void testLatestNetworkTime_noTimeAvailable() {
if (TimeDetectorNetworkTimeHelper.isInUse()) {
mFakeTimeDetectorStrategySpy.setLatestNetworkTime(null);
-
- assertNull(mTimeDetectorService.getLatestNetworkSuggestion());
} else {
when(mMockNtpTrustedTime.getCachedTimeResult()).thenReturn(null);
- assertNull(mTimeDetectorService.getLatestNetworkSuggestion());
}
+ assertThrows(ParcelableException.class, () -> mTimeDetectorService.latestNetworkTime());
}
@Test
diff --git a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
index b921838..4c0361d 100644
--- a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
+++ b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
@@ -263,7 +263,7 @@
+ instrumentation.getContext().getUserId() + " " + RoleManager.ROLE_HOME + " "
+ packageName + " 0");
waitUntil("Failed to get shortcut access",
- () -> hasShortcutAccess(instrumentation, packageName), 20);
+ () -> hasShortcutAccess(instrumentation, packageName), 60);
}
public static void setDefaultLauncher(Instrumentation instrumentation, Context packageContext) {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
index 2b6db14..893f538 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
@@ -32,9 +32,9 @@
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenModeConfig.EventInfo;
import android.service.notification.ZenPolicy;
-import android.test.suitebuilder.annotation.SmallTest;
import android.util.Xml;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.modules.utils.TypedXmlPullParser;
@@ -43,11 +43,13 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParserException;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
+import java.io.IOException;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -189,8 +191,6 @@
@Test
public void testRuleXml() throws Exception {
- String tag = "tag";
-
ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
rule.configurationActivity = new ComponentName("a", "a");
rule.component = new ComponentName("b", "b");
@@ -205,20 +205,11 @@
rule.snoozing = true;
rule.pkg = "b";
- TypedXmlSerializer out = Xml.newFastSerializer();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
- out.setOutput(new BufferedOutputStream(baos), "utf-8");
- out.startDocument(null, true);
- out.startTag(null, tag);
- ZenModeConfig.writeRuleXml(rule, out);
- out.endTag(null, tag);
- out.endDocument();
+ writeRuleXml(rule, baos);
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ ZenModeConfig.ZenRule fromXml = readRuleXml(bais);
- TypedXmlPullParser parser = Xml.newFastPullParser();
- parser.setInput(new BufferedInputStream(
- new ByteArrayInputStream(baos.toByteArray())), null);
- parser.nextTag();
- ZenModeConfig.ZenRule fromXml = ZenModeConfig.readRuleXml(parser);
assertEquals("b", fromXml.pkg);
// always resets on reboot
assertFalse(fromXml.snoozing);
@@ -237,75 +228,41 @@
@Test
public void testRuleXml_pkg_component() throws Exception {
- String tag = "tag";
-
ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
rule.configurationActivity = new ComponentName("a", "a");
rule.component = new ComponentName("b", "b");
- TypedXmlSerializer out = Xml.newFastSerializer();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
- out.setOutput(new BufferedOutputStream(baos), "utf-8");
- out.startDocument(null, true);
- out.startTag(null, tag);
- ZenModeConfig.writeRuleXml(rule, out);
- out.endTag(null, tag);
- out.endDocument();
+ writeRuleXml(rule, baos);
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ ZenModeConfig.ZenRule fromXml = readRuleXml(bais);
- TypedXmlPullParser parser = Xml.newFastPullParser();
- parser.setInput(new BufferedInputStream(
- new ByteArrayInputStream(baos.toByteArray())), null);
- parser.nextTag();
- ZenModeConfig.ZenRule fromXml = ZenModeConfig.readRuleXml(parser);
assertEquals("b", fromXml.pkg);
}
@Test
public void testRuleXml_pkg_configActivity() throws Exception {
- String tag = "tag";
-
ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
rule.configurationActivity = new ComponentName("a", "a");
- TypedXmlSerializer out = Xml.newFastSerializer();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
- out.setOutput(new BufferedOutputStream(baos), "utf-8");
- out.startDocument(null, true);
- out.startTag(null, tag);
- ZenModeConfig.writeRuleXml(rule, out);
- out.endTag(null, tag);
- out.endDocument();
+ writeRuleXml(rule, baos);
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ ZenModeConfig.ZenRule fromXml = readRuleXml(bais);
- TypedXmlPullParser parser = Xml.newFastPullParser();
- parser.setInput(new BufferedInputStream(
- new ByteArrayInputStream(baos.toByteArray())), null);
- parser.nextTag();
- ZenModeConfig.ZenRule fromXml = ZenModeConfig.readRuleXml(parser);
assertNull(fromXml.pkg);
}
@Test
public void testRuleXml_getPkg_nullPkg() throws Exception {
- String tag = "tag";
-
ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
rule.enabled = true;
rule.configurationActivity = new ComponentName("a", "a");
- TypedXmlSerializer out = Xml.newFastSerializer();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
- out.setOutput(new BufferedOutputStream(baos), "utf-8");
- out.startDocument(null, true);
- out.startTag(null, tag);
- ZenModeConfig.writeRuleXml(rule, out);
- out.endTag(null, tag);
- out.endDocument();
-
- TypedXmlPullParser parser = Xml.newFastPullParser();
- parser.setInput(new BufferedInputStream(
- new ByteArrayInputStream(baos.toByteArray())), null);
- parser.nextTag();
- ZenModeConfig.ZenRule fromXml = ZenModeConfig.readRuleXml(parser);
+ writeRuleXml(rule, baos);
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ ZenModeConfig.ZenRule fromXml = readRuleXml(bais);
assertEquals("a", fromXml.getPkg());
fromXml.condition = new Condition(Uri.EMPTY, "", Condition.STATE_TRUE);
@@ -313,25 +270,26 @@
}
@Test
- public void testZenPolicyXml_allUnset() throws Exception {
- String tag = "tag";
+ public void testRuleXml_emptyConditionId() throws Exception {
+ ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
+ rule.conditionId = Uri.EMPTY;
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ writeRuleXml(rule, baos);
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ ZenModeConfig.ZenRule fromXml = readRuleXml(bais);
+
+ assertEquals(rule.condition, fromXml.condition);
+ }
+
+ @Test
+ public void testZenPolicyXml_allUnset() throws Exception {
ZenPolicy policy = new ZenPolicy.Builder().build();
- TypedXmlSerializer out = Xml.newFastSerializer();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
- out.setOutput(new BufferedOutputStream(baos), "utf-8");
- out.startDocument(null, true);
- out.startTag(null, tag);
- ZenModeConfig.writeZenPolicyXml(policy, out);
- out.endTag(null, tag);
- out.endDocument();
-
- TypedXmlPullParser parser = Xml.newFastPullParser();
- parser.setInput(new BufferedInputStream(
- new ByteArrayInputStream(baos.toByteArray())), null);
- parser.nextTag();
- ZenPolicy fromXml = ZenModeConfig.readZenPolicyXml(parser);
+ writePolicyXml(policy, baos);
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ ZenPolicy fromXml = readPolicyXml(bais);
// nothing was set, so we should have nothing from the parser
assertNull(fromXml);
@@ -339,8 +297,6 @@
@Test
public void testZenPolicyXml() throws Exception {
- String tag = "tag";
-
ZenPolicy policy = new ZenPolicy.Builder()
.allowCalls(ZenPolicy.PEOPLE_TYPE_CONTACTS)
.allowMessages(ZenPolicy.PEOPLE_TYPE_NONE)
@@ -355,20 +311,10 @@
.showVisualEffect(ZenPolicy.VISUAL_EFFECT_AMBIENT, true)
.build();
- TypedXmlSerializer out = Xml.newFastSerializer();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
- out.setOutput(new BufferedOutputStream(baos), "utf-8");
- out.startDocument(null, true);
- out.startTag(null, tag);
- ZenModeConfig.writeZenPolicyXml(policy, out);
- out.endTag(null, tag);
- out.endDocument();
-
- TypedXmlPullParser parser = Xml.newFastPullParser();
- parser.setInput(new BufferedInputStream(
- new ByteArrayInputStream(baos.toByteArray())), null);
- parser.nextTag();
- ZenPolicy fromXml = ZenModeConfig.readZenPolicyXml(parser);
+ writePolicyXml(policy, baos);
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ ZenPolicy fromXml = readPolicyXml(bais);
assertNotNull(fromXml);
assertEquals(policy.getPriorityCategoryCalls(), fromXml.getPriorityCategoryCalls());
@@ -457,4 +403,45 @@
config.suppressedVisualEffects = 0;
return config;
}
+
+ private void writeRuleXml(ZenModeConfig.ZenRule rule, ByteArrayOutputStream os)
+ throws IOException {
+ String tag = "tag";
+
+ TypedXmlSerializer out = Xml.newFastSerializer();
+ out.setOutput(new BufferedOutputStream(os), "utf-8");
+ out.startDocument(null, true);
+ out.startTag(null, tag);
+ ZenModeConfig.writeRuleXml(rule, out);
+ out.endTag(null, tag);
+ out.endDocument();
+ }
+
+ private ZenModeConfig.ZenRule readRuleXml(ByteArrayInputStream is)
+ throws XmlPullParserException, IOException {
+ TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new BufferedInputStream(is), null);
+ parser.nextTag();
+ return ZenModeConfig.readRuleXml(parser);
+ }
+
+ private void writePolicyXml(ZenPolicy policy, ByteArrayOutputStream os) throws IOException {
+ String tag = "tag";
+
+ TypedXmlSerializer out = Xml.newFastSerializer();
+ out.setOutput(new BufferedOutputStream(os), "utf-8");
+ out.startDocument(null, true);
+ out.startTag(null, tag);
+ ZenModeConfig.writeZenPolicyXml(policy, out);
+ out.endTag(null, tag);
+ out.endDocument();
+ }
+
+ private ZenPolicy readPolicyXml(ByteArrayInputStream is)
+ throws XmlPullParserException, IOException {
+ TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new BufferedInputStream(is), null);
+ parser.nextTag();
+ return ZenModeConfig.readZenPolicyXml(parser);
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index e5fe32a..6661e6a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2324,6 +2324,32 @@
}
@Test
+ public void testActivityServiceConnectionsHolder() {
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final ActivityServiceConnectionsHolder<Object> holder =
+ mAtm.mInternal.getServiceConnectionsHolder(activity.token);
+ assertNotNull(holder);
+ final Object connection = new Object();
+ holder.addConnection(connection);
+ assertTrue(holder.isActivityVisible());
+ final int[] count = new int[1];
+ final Consumer<Object> c = conn -> count[0]++;
+ holder.forEachConnection(c);
+ assertEquals(1, count[0]);
+
+ holder.removeConnection(connection);
+ holder.forEachConnection(c);
+ assertEquals(1, count[0]);
+
+ activity.setVisibleRequested(false);
+ activity.setState(STOPPED, "test");
+ assertFalse(holder.isActivityVisible());
+
+ activity.removeImmediately();
+ assertNull(mAtm.mInternal.getServiceConnectionsHolder(activity.token));
+ }
+
+ @Test
public void testTransferLaunchCookieWhenFinishing() {
final ActivityRecord activity1 = createActivityWithTask();
final Binder launchCookie = new Binder();
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 28e493e..e3cb5fb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -145,7 +145,8 @@
public class ActivityStarterTests extends WindowTestsBase {
private static final String ENABLE_DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_SENDER =
- "enable_default_rescind_bal_privileges_from_pending_intent_sender";
+ "DefaultRescindBalPrivilegesFromPendingIntentSender__"
+ + "enable_default_rescind_bal_privileges_from_pending_intent_sender";
private static final int PRECONDITION_NO_CALLER_APP = 1;
private static final int PRECONDITION_NO_INTENT_COMPONENT = 1 << 1;
private static final int PRECONDITION_NO_ACTIVITY_INFO = 1 << 2;
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index ff5ede7..56461f0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -17,8 +17,11 @@
package com.android.server.wm;
import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.window.BackNavigationInfo.typeToString;
@@ -42,8 +45,10 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.os.Bundle;
+import android.os.RemoteCallback;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.util.ArraySet;
import android.view.WindowManager;
import android.window.BackAnimationAdapter;
import android.window.BackMotionEvent;
@@ -320,6 +325,64 @@
assertThat(backNavigationInfo).isNull();
}
+ @Test
+ public void testTransitionHappensCancelNavigation() {
+ // Create a floating task and a fullscreen task, then navigating on fullscreen task.
+ // The navigation should not been cancelled when transition happens on floating task, and
+ // only be cancelled when transition happens on the navigating task.
+ final Task floatingTask = createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW,
+ ACTIVITY_TYPE_STANDARD);
+ final ActivityRecord baseFloatingActivity = createActivityRecord(floatingTask);
+
+ final Task fullscreenTask = createTopTaskWithActivity();
+ withSystemCallback(fullscreenTask);
+ final ActivityRecord baseFullscreenActivity = fullscreenTask.getTopMostActivity();
+
+ final CountDownLatch navigationObserver = new CountDownLatch(1);
+ startBackNavigation(navigationObserver);
+
+ final ArraySet<ActivityRecord> opening = new ArraySet<>();
+ final ArraySet<ActivityRecord> closing = new ArraySet<>();
+ final ActivityRecord secondFloatingActivity = createActivityRecord(floatingTask);
+ opening.add(secondFloatingActivity);
+ closing.add(baseFloatingActivity);
+ mBackNavigationController.removeIfContainsBackAnimationTargets(opening, closing);
+ assertEquals("Transition happen on an irrelevant task, callback should not been called",
+ 1, navigationObserver.getCount());
+
+ // Create a new activity above navigation target, the transition should cancel navigation.
+ final ActivityRecord topFullscreenActivity = createActivityRecord(fullscreenTask);
+ opening.clear();
+ closing.clear();
+ opening.add(topFullscreenActivity);
+ closing.add(baseFullscreenActivity);
+ mBackNavigationController.removeIfContainsBackAnimationTargets(opening, closing);
+ assertEquals("Transition happen on navigation task, callback should have been called",
+ 0, navigationObserver.getCount());
+ }
+
+ @Test
+ public void testWindowFocusChangeCancelNavigation() {
+ Task task = createTopTaskWithActivity();
+ withSystemCallback(task);
+ WindowState focusWindow = task.getTopVisibleAppMainWindow();
+ final CountDownLatch navigationObserver = new CountDownLatch(1);
+ startBackNavigation(navigationObserver);
+
+ mBackNavigationController.onFocusChanged(null);
+ assertEquals("change focus to null, callback should not have been called",
+ 1, navigationObserver.getCount());
+ mBackNavigationController.onFocusChanged(focusWindow);
+ assertEquals("change focus back, callback should not have been called",
+ 1, navigationObserver.getCount());
+
+ WindowState newWindow = createWindow(null, TYPE_APPLICATION_OVERLAY, "overlayWindow");
+ addToWindowMap(newWindow, true);
+ mBackNavigationController.onFocusChanged(newWindow);
+ assertEquals("Focus change, callback should have been called",
+ 0, navigationObserver.getCount());
+ }
+
private IOnBackInvokedCallback withSystemCallback(Task task) {
IOnBackInvokedCallback callback = createOnBackInvokedCallback();
task.getTopMostActivity().getTopChild().setOnBackInvokedCallbackInfo(
@@ -336,7 +399,14 @@
@Nullable
private BackNavigationInfo startBackNavigation() {
- return mBackNavigationController.startBackNavigation(null, mBackAnimationAdapter);
+ return mBackNavigationController.startBackNavigation(
+ createNavigationObserver(null), mBackAnimationAdapter);
+ }
+
+ @Nullable
+ private BackNavigationInfo startBackNavigation(CountDownLatch navigationObserverLatch) {
+ return mBackNavigationController.startBackNavigation(
+ createNavigationObserver(navigationObserverLatch), mBackAnimationAdapter);
}
@NonNull
@@ -371,6 +441,14 @@
};
}
+ private RemoteCallback createNavigationObserver(CountDownLatch latch) {
+ return new RemoteCallback(result -> {
+ if (latch != null) {
+ latch.countDown();
+ }
+ });
+ }
+
private Task initHomeActivity() {
final Task task = mDisplayContent.getDefaultTaskDisplayArea().getRootHomeTask();
task.forAllLeafTasks((t) -> {
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 6f633d7..06b6ed8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -68,6 +68,7 @@
import android.view.InsetsState;
import android.view.RoundedCorner;
import android.view.RoundedCorners;
+import android.view.WindowInsets;
import android.view.WindowManager;
import androidx.test.filters.SmallTest;
@@ -353,7 +354,7 @@
@Test
public void testGetCropBoundsIfNeeded_noCrop() {
final InsetsSource taskbar = new InsetsSource(/*id=*/ 0,
- InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+ WindowInsets.Type.navigationBars());
final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(taskbar);
// Do not apply crop if taskbar is collapsed
@@ -374,7 +375,8 @@
@Test
public void testGetCropBoundsIfNeeded_appliesCrop() {
final InsetsSource taskbar = new InsetsSource(/*id=*/ 0,
- InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+ WindowInsets.Type.navigationBars());
+ taskbar.setInsetsRoundedCornerFrame(true);
final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(taskbar);
// Apply crop if taskbar is expanded
@@ -396,7 +398,8 @@
@Test
public void testGetCropBoundsIfNeeded_appliesCropWithSizeCompatScaling() {
final InsetsSource taskbar = new InsetsSource(/*id=*/ 0,
- InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+ WindowInsets.Type.navigationBars());
+ taskbar.setInsetsRoundedCornerFrame(true);
final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(taskbar);
final float scaling = 2.0f;
@@ -440,7 +443,7 @@
configurationRadius * 2 /*2 is to test selection of the min radius*/,
/*centerX=*/ 1, /*centerY=*/ 1)
);
- doReturn(roundedCorners).when(insets).getRoundedCorners();
+ insets.setRoundedCorners(roundedCorners);
mLetterboxConfiguration.setLetterboxActivityCornersRadius(-1);
assertEquals(expectedRadius, mController.getRoundedCornersRadius(mainWindow));
@@ -479,7 +482,7 @@
private WindowState mockForGetCropBoundsAndRoundedCorners(@Nullable InsetsSource taskbar) {
final WindowState mainWindow = mock(WindowState.class);
- final InsetsState insets = mock(InsetsState.class);
+ final InsetsState insets = new InsetsState();
final Resources resources = mWm.mContext.getResources();
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
@@ -489,7 +492,7 @@
if (taskbar != null) {
taskbar.setVisible(true);
- doReturn(taskbar).when(insets).peekSource(taskbar.getType());
+ insets.addSource(taskbar);
}
doReturn(mLetterboxedPortraitTaskBounds).when(mActivity).getBounds();
doReturn(true).when(mActivity).isVisible();
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 e9aca56..d242a5f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -28,7 +28,6 @@
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS;
-import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.InsetsState.ITYPE_TOP_MANDATORY_GESTURES;
import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT;
@@ -2993,8 +2992,9 @@
organizer.mPrimary.setBounds(0, screenHeight / 2, screenWidth, screenHeight);
organizer.putTaskToPrimary(mTask, true);
- final InsetsSource navSource =
- new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR, navigationBars());
+ final InsetsSource navSource = new InsetsSource(
+ InsetsSource.createId(null, 0, navigationBars()), navigationBars());
+ navSource.setInsetsRoundedCornerFrame(true);
navSource.setFrame(new Rect(0, screenHeight - taskbarHeight, screenWidth, screenHeight));
mActivity.mWmService.mLetterboxConfiguration.setLetterboxActivityCornersRadius(15);
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
index ec9bd2f..06fc416 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
@@ -83,6 +83,7 @@
import com.android.internal.app.IHotwordRecognitionStatusCallback;
import com.android.internal.infra.AndroidFuture;
import com.android.server.LocalServices;
+import com.android.server.voiceinteraction.VoiceInteractionManagerServiceImpl.DetectorRemoteExceptionListener;
import java.io.Closeable;
import java.io.IOException;
@@ -203,12 +204,16 @@
boolean mPerformingExternalSourceHotwordDetection;
@NonNull final IBinder mToken;
+ @NonNull DetectorRemoteExceptionListener mRemoteExceptionListener;
+
DetectorSession(
@NonNull HotwordDetectionConnection.ServiceConnection remoteDetectionService,
@NonNull Object lock, @NonNull Context context, @NonNull IBinder token,
@NonNull IHotwordRecognitionStatusCallback callback, int voiceInteractionServiceUid,
Identity voiceInteractorIdentity,
- @NonNull ScheduledExecutorService scheduledExecutorService, boolean logging) {
+ @NonNull ScheduledExecutorService scheduledExecutorService, boolean logging,
+ @NonNull DetectorRemoteExceptionListener listener) {
+ mRemoteExceptionListener = listener;
mRemoteDetectionService = remoteDetectionService;
mLock = lock;
mContext = context;
@@ -237,6 +242,14 @@
}
}
+ void notifyOnDetectorRemoteException() {
+ Slog.d(TAG, "notifyOnDetectorRemoteException: mRemoteExceptionListener="
+ + mRemoteExceptionListener);
+ if (mRemoteExceptionListener != null) {
+ mRemoteExceptionListener.onDetectorRemoteException(mToken, getDetectorType());
+ }
+ }
+
@SuppressWarnings("GuardedBy")
private void updateStateAfterProcessStartLocked(PersistableBundle options,
SharedMemory sharedMemory) {
@@ -280,6 +293,7 @@
METRICS_CALLBACK_ON_STATUS_REPORTED_EXCEPTION,
mVoiceInteractionServiceUid);
}
+ notifyOnDetectorRemoteException();
}
}
};
@@ -319,6 +333,7 @@
METRICS_CALLBACK_ON_STATUS_REPORTED_EXCEPTION,
mVoiceInteractionServiceUid);
}
+ notifyOnDetectorRemoteException();
}
} else if (err != null) {
Slog.w(TAG, "Failed to update state: " + err);
@@ -443,6 +458,7 @@
HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION,
mVoiceInteractionServiceUid);
}
+ notifyOnDetectorRemoteException();
}
} finally {
synchronized (mLock) {
@@ -479,8 +495,12 @@
EXTERNAL_HOTWORD_CLEANUP_MILLIS,
TimeUnit.MILLISECONDS);
- callback.onRejected(result);
-
+ try {
+ callback.onRejected(result);
+ } catch (RemoteException e) {
+ notifyOnDetectorRemoteException();
+ throw e;
+ }
if (result != null) {
Slog.i(TAG, "Egressed 'hotword rejected result' "
+ "from hotword trusted process");
@@ -510,14 +530,21 @@
try {
enforcePermissionsForDataDelivery();
} catch (SecurityException e) {
+ Slog.w(TAG, "Ignoring #onDetected due to a "
+ + "SecurityException", e);
HotwordMetricsLogger.writeDetectorEvent(
getDetectorType(),
EXTERNAL_SOURCE_DETECT_SECURITY_EXCEPTION,
mVoiceInteractionServiceUid);
- callback.onError(new HotwordDetectionServiceFailure(
- CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION,
- "Security exception occurs in #onDetected"
- + " method."));
+ try {
+ callback.onError(new HotwordDetectionServiceFailure(
+ CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION,
+ "Security exception occurs in #onDetected"
+ + " method."));
+ } catch (RemoteException e1) {
+ notifyOnDetectorRemoteException();
+ throw e1;
+ }
return;
}
HotwordDetectedResult newResult;
@@ -525,14 +552,26 @@
newResult = mHotwordAudioStreamCopier
.startCopyingAudioStreams(triggerResult);
} catch (IOException e) {
+ Slog.w(TAG, "Ignoring #onDetected due to a "
+ + "IOException", e);
// TODO: Write event
- callback.onError(new HotwordDetectionServiceFailure(
- CALLBACK_ONDETECTED_STREAM_COPY_ERROR,
- "Copy audio stream failure."));
+ try {
+ callback.onError(new HotwordDetectionServiceFailure(
+ CALLBACK_ONDETECTED_STREAM_COPY_ERROR,
+ "Copy audio stream failure."));
+ } catch (RemoteException e1) {
+ notifyOnDetectorRemoteException();
+ throw e1;
+ }
return;
}
- callback.onDetected(newResult, /* audioFormat= */ null,
- /* audioStream= */ null);
+ try {
+ callback.onDetected(newResult, /* audioFormat= */ null,
+ /* audioStream= */ null);
+ } catch (RemoteException e) {
+ notifyOnDetectorRemoteException();
+ throw e;
+ }
Slog.i(TAG, "Egressed "
+ HotwordDetectedResult.getUsageSize(newResult)
+ " bits from hotword trusted process");
@@ -567,6 +606,7 @@
mDestroyed = true;
mDebugHotwordLogging = false;
mRemoteDetectionService = null;
+ mRemoteExceptionListener = null;
if (mAttentionManagerInternal != null) {
mAttentionManagerInternal.onStopProximityUpdates(mProximityCallbackInternal);
}
@@ -595,6 +635,7 @@
HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION,
mVoiceInteractionServiceUid);
}
+ notifyOnDetectorRemoteException();
}
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java
index 63e0f46..f9b5111 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java
@@ -16,6 +16,7 @@
package com.android.server.voiceinteraction;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_DETECTED_EXCEPTION;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_PROCESS_RESTARTED_EXCEPTION;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_REJECTED_EXCEPTION;
@@ -42,6 +43,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IHotwordRecognitionStatusCallback;
+import com.android.server.voiceinteraction.VoiceInteractionManagerServiceImpl.DetectorRemoteExceptionListener;
import java.io.IOException;
import java.io.PrintWriter;
@@ -80,10 +82,11 @@
@NonNull Object lock, @NonNull Context context, @NonNull IBinder token,
@NonNull IHotwordRecognitionStatusCallback callback, int voiceInteractionServiceUid,
Identity voiceInteractorIdentity,
- @NonNull ScheduledExecutorService scheduledExecutorService, boolean logging) {
+ @NonNull ScheduledExecutorService scheduledExecutorService, boolean logging,
+ @NonNull DetectorRemoteExceptionListener listener) {
super(remoteHotwordDetectionService, lock, context, token, callback,
voiceInteractionServiceUid, voiceInteractorIdentity, scheduledExecutorService,
- logging);
+ logging, listener);
}
@SuppressWarnings("GuardedBy")
@@ -126,14 +129,23 @@
enforcePermissionsForDataDelivery();
enforceExtraKeyphraseIdNotLeaked(result, recognitionEvent);
} catch (SecurityException e) {
- Slog.i(TAG, "Ignoring #onDetected due to a SecurityException", e);
+ Slog.w(TAG, "Ignoring #onDetected due to a SecurityException", e);
HotwordMetricsLogger.writeKeyphraseTriggerEvent(
HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION,
mVoiceInteractionServiceUid);
- externalCallback.onDetectionFailure(new HotwordDetectionServiceFailure(
- CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION,
- "Security exception occurs in #onDetected method."));
+ try {
+ externalCallback.onDetectionFailure(new HotwordDetectionServiceFailure(
+ CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION,
+ "Security exception occurs in #onDetected method."));
+ } catch (RemoteException e1) {
+ notifyOnDetectorRemoteException();
+ HotwordMetricsLogger.writeDetectorEvent(
+ HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
+ HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION,
+ mVoiceInteractionServiceUid);
+ throw e1;
+ }
return;
}
saveProximityValueToBundle(result);
@@ -141,14 +153,29 @@
try {
newResult = mHotwordAudioStreamCopier.startCopyingAudioStreams(result);
} catch (IOException e) {
- externalCallback.onDetectionFailure(new HotwordDetectionServiceFailure(
- CALLBACK_ONDETECTED_STREAM_COPY_ERROR,
- "Copy audio stream failure."));
+ try {
+ Slog.w(TAG, "Ignoring #onDetected due to a IOException", e);
+ externalCallback.onDetectionFailure(new HotwordDetectionServiceFailure(
+ CALLBACK_ONDETECTED_STREAM_COPY_ERROR,
+ "Copy audio stream failure."));
+ } catch (RemoteException e1) {
+ notifyOnDetectorRemoteException();
+ throw e1;
+ }
return;
}
- externalCallback.onKeyphraseDetected(recognitionEvent, newResult);
- Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(newResult)
- + " bits from hotword trusted process");
+ try {
+ externalCallback.onKeyphraseDetected(recognitionEvent, newResult);
+ Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(newResult)
+ + " bits from hotword trusted process");
+ } catch (RemoteException e) {
+ notifyOnDetectorRemoteException();
+ HotwordMetricsLogger.writeDetectorEvent(
+ HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
+ HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_DETECTED_EXCEPTION,
+ mVoiceInteractionServiceUid);
+ throw e;
+ }
if (mDebugHotwordLogging) {
Slog.i(TAG, "Egressed detected result: " + newResult);
}
@@ -180,7 +207,16 @@
return;
}
mValidatingDspTrigger = false;
- externalCallback.onRejected(result);
+ try {
+ externalCallback.onRejected(result);
+ } catch (RemoteException e) {
+ notifyOnDetectorRemoteException();
+ HotwordMetricsLogger.writeDetectorEvent(
+ HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
+ HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_REJECTED_EXCEPTION,
+ mVoiceInteractionServiceUid);
+ throw e;
+ }
mLastHotwordRejectedResult = result;
if (mDebugHotwordLogging && result != null) {
Slog.i(TAG, "Egressed rejected result: " + result);
@@ -215,6 +251,7 @@
HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION,
mVoiceInteractionServiceUid);
+ notifyOnDetectorRemoteException();
}
},
MAX_VALIDATION_TIMEOUT_MILLIS,
@@ -247,6 +284,7 @@
HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_REJECTED_EXCEPTION,
mVoiceInteractionServiceUid);
+ notifyOnDetectorRemoteException();
}
mValidatingDspTrigger = false;
}
@@ -260,6 +298,7 @@
HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_PROCESS_RESTARTED_EXCEPTION,
mVoiceInteractionServiceUid);
+ notifyOnDetectorRemoteException();
}
mPerformingExternalSourceHotwordDetection = false;
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 1ba3975..025e1dc 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -30,7 +30,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledSince;
+import android.compat.annotation.Disabled;
import android.content.ComponentName;
import android.content.ContentCaptureOptions;
import android.content.Context;
@@ -41,7 +41,6 @@
import android.media.AudioManagerInternal;
import android.media.permission.Identity;
import android.os.Binder;
-import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.IRemoteCallback;
@@ -69,6 +68,7 @@
import com.android.internal.infra.ServiceConnector;
import com.android.server.LocalServices;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
+import com.android.server.voiceinteraction.VoiceInteractionManagerServiceImpl.DetectorRemoteExceptionListener;
import java.io.PrintWriter;
import java.time.Instant;
@@ -88,8 +88,7 @@
static final boolean DEBUG = false;
/**
- * For apps targeting Android API Build.VERSION_CODES#UPSIDE_DOWN_CAKE and above,
- * implementors of the HotwordDetectionService must not augment the phrase IDs which are
+ * Implementors of the HotwordDetectionService must not augment the phrase IDs which are
* supplied via HotwordDetectionService
* #onDetect(AlwaysOnHotwordDetector.EventPayload, long, HotwordDetectionService.Callback).
*
@@ -103,7 +102,7 @@
* </p>
*/
@ChangeId
- @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Disabled
public static final long ENFORCE_HOTWORD_PHRASE_ID = 215066299L;
private static final String KEY_RESTART_PERIOD_IN_SECONDS = "restart_period_in_seconds";
@@ -147,6 +146,8 @@
@GuardedBy("mLock")
private boolean mDebugHotwordLogging = false;
+ private DetectorRemoteExceptionListener mRemoteExceptionListener;
+
/**
* For multiple detectors feature, we only support one AlwaysOnHotwordDetector and one
* SoftwareHotwordDetector at the same time. We use SparseArray with detector type as the key
@@ -159,7 +160,8 @@
HotwordDetectionConnection(Object lock, Context context, int voiceInteractionServiceUid,
Identity voiceInteractorIdentity, ComponentName hotwordDetectionServiceName,
ComponentName visualQueryDetectionServiceName, int userId,
- boolean bindInstantServiceAllowed, int detectorType) {
+ boolean bindInstantServiceAllowed, int detectorType,
+ DetectorRemoteExceptionListener listener) {
mLock = lock;
mContext = context;
mVoiceInteractionServiceUid = voiceInteractionServiceUid;
@@ -168,6 +170,7 @@
mVisualQueryDetectionComponentName = visualQueryDetectionServiceName;
mUser = userId;
mDetectorType = detectorType;
+ mRemoteExceptionListener = listener;
mReStartPeriodSeconds = DeviceConfig.getInt(DeviceConfig.NAMESPACE_VOICE_INTERACTION,
KEY_RESTART_PERIOD_IN_SECONDS, 0);
@@ -251,6 +254,7 @@
void cancelLocked() {
Slog.v(TAG, "cancelLocked");
clearDebugHotwordLoggingTimeoutLocked();
+ mRemoteExceptionListener = null;
runForEachDetectorSessionLocked((session) -> {
session.destroyLocked();
});
@@ -772,7 +776,8 @@
}
session = new DspTrustedHotwordDetectorSession(mRemoteHotwordDetectionService,
mLock, mContext, token, callback, mVoiceInteractionServiceUid,
- mVoiceInteractorIdentity, mScheduledExecutorService, mDebugHotwordLogging);
+ mVoiceInteractorIdentity, mScheduledExecutorService, mDebugHotwordLogging,
+ mRemoteExceptionListener);
} else if (detectorType == HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
if (mRemoteVisualQueryDetectionService == null) {
mRemoteVisualQueryDetectionService =
@@ -781,7 +786,7 @@
session = new VisualQueryDetectorSession(
mRemoteVisualQueryDetectionService, mLock, mContext, token, callback,
mVoiceInteractionServiceUid, mVoiceInteractorIdentity,
- mScheduledExecutorService, mDebugHotwordLogging);
+ mScheduledExecutorService, mDebugHotwordLogging, mRemoteExceptionListener);
} else {
if (mRemoteHotwordDetectionService == null) {
mRemoteHotwordDetectionService =
@@ -790,7 +795,7 @@
session = new SoftwareTrustedHotwordDetectorSession(
mRemoteHotwordDetectionService, mLock, mContext, token, callback,
mVoiceInteractionServiceUid, mVoiceInteractorIdentity,
- mScheduledExecutorService, mDebugHotwordLogging);
+ mScheduledExecutorService, mDebugHotwordLogging, mRemoteExceptionListener);
}
mDetectorSessions.put(detectorType, session);
session.initialize(options, sharedMemory);
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java
index 522d832..367fb81 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java
@@ -18,6 +18,8 @@
import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_MICROPHONE;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_DETECTED_EXCEPTION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_PROCESS_RESTARTED_EXCEPTION;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__START_SOFTWARE_DETECTION;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED;
@@ -43,6 +45,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IHotwordRecognitionStatusCallback;
+import com.android.server.voiceinteraction.VoiceInteractionManagerServiceImpl.DetectorRemoteExceptionListener;
import java.io.IOException;
import java.io.PrintWriter;
@@ -68,10 +71,11 @@
@NonNull Object lock, @NonNull Context context, @NonNull IBinder token,
@NonNull IHotwordRecognitionStatusCallback callback, int voiceInteractionServiceUid,
Identity voiceInteractorIdentity,
- @NonNull ScheduledExecutorService scheduledExecutorService, boolean logging) {
+ @NonNull ScheduledExecutorService scheduledExecutorService, boolean logging,
+ @NonNull DetectorRemoteExceptionListener listener) {
super(remoteHotwordDetectionService, lock, context, token, callback,
voiceInteractionServiceUid, voiceInteractorIdentity, scheduledExecutorService,
- logging);
+ logging, listener);
}
@SuppressWarnings("GuardedBy")
@@ -118,13 +122,23 @@
try {
enforcePermissionsForDataDelivery();
} catch (SecurityException e) {
+ Slog.w(TAG, "Ignoring #onDetected due to a SecurityException", e);
HotwordMetricsLogger.writeKeyphraseTriggerEvent(
HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE,
METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION,
mVoiceInteractionServiceUid);
- mSoftwareCallback.onError(new HotwordDetectionServiceFailure(
- CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION,
- "Security exception occurs in #onDetected method."));
+ try {
+ mSoftwareCallback.onError(new HotwordDetectionServiceFailure(
+ CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION,
+ "Security exception occurs in #onDetected method."));
+ } catch (RemoteException e1) {
+ notifyOnDetectorRemoteException();
+ HotwordMetricsLogger.writeDetectorEvent(
+ HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE,
+ HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION,
+ mVoiceInteractionServiceUid);
+ throw e1;
+ }
return;
}
saveProximityValueToBundle(result);
@@ -132,13 +146,32 @@
try {
newResult = mHotwordAudioStreamCopier.startCopyingAudioStreams(result);
} catch (IOException e) {
+ Slog.w(TAG, "Ignoring #onDetected due to a IOException", e);
// TODO: Write event
- mSoftwareCallback.onError(new HotwordDetectionServiceFailure(
- CALLBACK_ONDETECTED_STREAM_COPY_ERROR,
- "Copy audio stream failure."));
+ try {
+ mSoftwareCallback.onError(new HotwordDetectionServiceFailure(
+ CALLBACK_ONDETECTED_STREAM_COPY_ERROR,
+ "Copy audio stream failure."));
+ } catch (RemoteException e1) {
+ notifyOnDetectorRemoteException();
+ HotwordMetricsLogger.writeDetectorEvent(
+ HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE,
+ HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION,
+ mVoiceInteractionServiceUid);
+ throw e1;
+ }
return;
}
- mSoftwareCallback.onDetected(newResult, null, null);
+ try {
+ mSoftwareCallback.onDetected(newResult, null, null);
+ } catch (RemoteException e1) {
+ notifyOnDetectorRemoteException();
+ HotwordMetricsLogger.writeDetectorEvent(
+ HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE,
+ HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_DETECTED_EXCEPTION,
+ mVoiceInteractionServiceUid);
+ throw e1;
+ }
Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(newResult)
+ " bits from hotword trusted process");
if (mDebugHotwordLogging) {
@@ -204,6 +237,7 @@
HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE,
HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_PROCESS_RESTARTED_EXCEPTION,
mVoiceInteractionServiceUid);
+ notifyOnDetectorRemoteException();
}
// Restart listening from microphone if the hotword process has been restarted.
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
index c397812..afe5dab 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
@@ -38,6 +38,7 @@
import com.android.internal.app.IHotwordRecognitionStatusCallback;
import com.android.internal.app.IVisualQueryDetectionAttentionListener;
+import com.android.server.voiceinteraction.VoiceInteractionManagerServiceImpl.DetectorRemoteExceptionListener;
import java.io.PrintWriter;
import java.util.Objects;
@@ -64,13 +65,15 @@
@NonNull Object lock, @NonNull Context context, @NonNull IBinder token,
@NonNull IHotwordRecognitionStatusCallback callback, int voiceInteractionServiceUid,
Identity voiceInteractorIdentity,
- @NonNull ScheduledExecutorService scheduledExecutorService, boolean logging) {
+ @NonNull ScheduledExecutorService scheduledExecutorService, boolean logging,
+ @NonNull DetectorRemoteExceptionListener listener) {
super(remoteService, lock, context, token, callback,
voiceInteractionServiceUid, voiceInteractorIdentity, scheduledExecutorService,
- logging);
+ logging, listener);
mEgressingData = false;
mQueryStreaming = false;
mAttentionListener = null;
+ // TODO: handle notify RemoteException to client
}
@Override
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 96b69f8..929e033 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -743,7 +743,15 @@
mHotwordDetectionConnection = new HotwordDetectionConnection(mServiceStub, mContext,
mInfo.getServiceInfo().applicationInfo.uid, voiceInteractorIdentity,
mHotwordDetectionComponentName, mVisualQueryDetectionComponentName, mUser,
- /* bindInstantServiceAllowed= */ false, detectorType);
+ /* bindInstantServiceAllowed= */ false, detectorType,
+ (token1, detectorType1) -> {
+ try {
+ mService.detectorRemoteExceptionOccurred(token1, detectorType1);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Fail to notify client detector remote "
+ + "exception occurred.");
+ }
+ });
} else if (detectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
// TODO: Logger events should be handled in session instead. Temporary adding the
// checking to prevent confusion so VisualQueryDetection events won't be logged if the
@@ -1080,4 +1088,8 @@
// client always get the callback even if session is unexpectedly closed.
mServiceStub.setSessionWindowVisible(connection.mToken, false);
}
+
+ interface DetectorRemoteExceptionListener {
+ void onDetectorRemoteException(@NonNull IBinder token, int detectorType);
+ }
}
diff --git a/telecomm/java/android/telecom/CallControl.java b/telecomm/java/android/telecom/CallControl.java
index 6b2bea0..97538c1 100644
--- a/telecomm/java/android/telecom/CallControl.java
+++ b/telecomm/java/android/telecom/CallControl.java
@@ -76,7 +76,10 @@
}
/**
- * Request Telecom set the call state to active.
+ * Request Telecom set the call state to active. This method should be called when either an
+ * outgoing call is ready to go active or a held call is ready to go active again. For incoming
+ * calls that are ready to be answered, use
+ * {@link CallControl#answer(int, Executor, OutcomeReceiver)}.
*
* @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
* will be called on.
@@ -106,6 +109,43 @@
}
/**
+ * Request Telecom answer an incoming call. For outgoing calls and calls that have been placed
+ * on hold, use {@link CallControl#setActive(Executor, OutcomeReceiver)}.
+ *
+ * @param videoState to report to Telecom. Telecom will store VideoState in the event another
+ * service/device requests it in order to continue the call on another screen.
+ * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
+ * will be called on.
+ * @param callback that will be completed on the Telecom side that details success or failure
+ * of the requested operation.
+ *
+ * {@link OutcomeReceiver#onResult} will be called if Telecom has successfully
+ * switched the call state to active
+ *
+ * {@link OutcomeReceiver#onError} will be called if Telecom has failed to set
+ * the call state to active. A {@link CallException} will be passed
+ * that details why the operation failed.
+ */
+ public void answer(@android.telecom.CallAttributes.CallType int videoState,
+ @CallbackExecutor @NonNull Executor executor,
+ @NonNull OutcomeReceiver<Void, CallException> callback) {
+ validateVideoState(videoState);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+ if (mServerInterface != null) {
+ try {
+ mServerInterface.answer(videoState, mCallId,
+ new CallControlResultReceiver("answer", executor, callback));
+
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ } else {
+ throw new IllegalStateException(INTERFACE_ERROR_MSG);
+ }
+ }
+
+ /**
* Request Telecom set the call state to inactive. This the same as hold for two call endpoints
* but can be extended to setting a meeting to inactive.
*
@@ -343,4 +383,13 @@
}
}
+ /** @hide */
+ private void validateVideoState(@android.telecom.CallAttributes.CallType int videoState) {
+ if (videoState != CallAttributes.AUDIO_CALL && videoState != CallAttributes.VIDEO_CALL) {
+ throw new IllegalArgumentException(TextUtils.formatSimple(
+ "The VideoState argument passed in, %d , is not a valid VideoState. The "
+ + "VideoState choices are limited to CallAttributes.AUDIO_CALL or"
+ + "CallAttributes.VIDEO_CALL", videoState));
+ }
+ }
}
diff --git a/telecomm/java/android/telecom/CallControlCallback.java b/telecomm/java/android/telecom/CallControlCallback.java
index aadf337..35e2fd4 100644
--- a/telecomm/java/android/telecom/CallControlCallback.java
+++ b/telecomm/java/android/telecom/CallControlCallback.java
@@ -75,24 +75,17 @@
@NonNull Consumer<Boolean> wasCompleted);
/**
- * Telecom is informing the client to reject the incoming call
- *
- * @param wasCompleted The {@link Consumer} to be completed. If the client can reject the
- * incoming call, {@link Consumer#accept(Object)} should be called with
- * {@link Boolean#TRUE}. Otherwise, {@link Consumer#accept(Object)}
- * should be called with {@link Boolean#FALSE}.
- */
- void onReject(@NonNull Consumer<Boolean> wasCompleted);
-
- /**
* Telecom is informing the client to disconnect the call
*
- * @param wasCompleted The {@link Consumer} to be completed. If the client can disconnect the
- * call on their end, {@link Consumer#accept(Object)} should be called with
- * {@link Boolean#TRUE}. Otherwise, {@link Consumer#accept(Object)}
- * should be called with {@link Boolean#FALSE}.
+ * @param disconnectCause represents the cause for disconnecting the call.
+ * @param wasCompleted The {@link Consumer} to be completed. If the client can disconnect
+ * the call on their end, {@link Consumer#accept(Object)} should be
+ * called with {@link Boolean#TRUE}. Otherwise,
+ * {@link Consumer#accept(Object)} should be called with
+ * {@link Boolean#FALSE}.
*/
- void onDisconnect(@NonNull Consumer<Boolean> wasCompleted);
+ void onDisconnect(@NonNull DisconnectCause disconnectCause,
+ @NonNull Consumer<Boolean> wasCompleted);
/**
* Telecom is informing the client to set the call in streaming.
diff --git a/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java b/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java
index e44e2b3..71e9184 100644
--- a/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java
+++ b/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java
@@ -28,6 +28,7 @@
import android.telecom.CallEndpoint;
import android.telecom.CallEventCallback;
import android.telecom.CallException;
+import android.telecom.DisconnectCause;
import android.telecom.PhoneAccountHandle;
import android.text.TextUtils;
import android.util.Log;
@@ -142,7 +143,6 @@
private static final String ON_SET_ACTIVE = "onSetActive";
private static final String ON_SET_INACTIVE = "onSetInactive";
private static final String ON_ANSWER = "onAnswer";
- private static final String ON_REJECT = "onReject";
private static final String ON_DISCONNECT = "onDisconnect";
private static final String ON_STREAMING_STARTED = "onStreamingStarted";
private static final String ON_REQ_ENDPOINT_CHANGE = "onRequestEndpointChange";
@@ -151,9 +151,9 @@
private static final String ON_CALL_STREAMING_FAILED = "onCallStreamingFailed";
private static final String ON_EVENT = "onEvent";
- private void handleHandshakeCallback(String action, String callId, int code,
- ResultReceiver ackResultReceiver) {
- Log.i(TAG, TextUtils.formatSimple("hHC: id=[%s], action=[%s]", callId, action));
+ private void handleCallEventCallback(String action, String callId,
+ ResultReceiver ackResultReceiver, Object... args) {
+ Log.i(TAG, TextUtils.formatSimple("hCEC: id=[%s], action=[%s]", callId, action));
// lookup the callEventCallback associated with the particular call
TransactionalCall call = mCallIdToTransactionalCall.get(callId);
@@ -174,16 +174,13 @@
case ON_SET_INACTIVE:
callback.onSetInactive(outcomeReceiverWrapper);
break;
- case ON_REJECT:
- callback.onReject(outcomeReceiverWrapper);
- untrackCall(callId);
- break;
case ON_DISCONNECT:
- callback.onDisconnect(outcomeReceiverWrapper);
+ callback.onDisconnect((DisconnectCause) args[0],
+ outcomeReceiverWrapper);
untrackCall(callId);
break;
case ON_ANSWER:
- callback.onAnswer(code, outcomeReceiverWrapper);
+ callback.onAnswer((int) args[0], outcomeReceiverWrapper);
break;
case ON_STREAMING_STARTED:
callback.onCallStreamingStarted(outcomeReceiverWrapper);
@@ -231,28 +228,23 @@
@Override
public void onSetActive(String callId, ResultReceiver resultReceiver) {
- handleHandshakeCallback(ON_SET_ACTIVE, callId, 0, resultReceiver);
+ handleCallEventCallback(ON_SET_ACTIVE, callId, resultReceiver);
}
-
@Override
public void onSetInactive(String callId, ResultReceiver resultReceiver) {
- handleHandshakeCallback(ON_SET_INACTIVE, callId, 0, resultReceiver);
+ handleCallEventCallback(ON_SET_INACTIVE, callId, resultReceiver);
}
@Override
public void onAnswer(String callId, int videoState, ResultReceiver resultReceiver) {
- handleHandshakeCallback(ON_ANSWER, callId, videoState, resultReceiver);
+ handleCallEventCallback(ON_ANSWER, callId, resultReceiver, videoState);
}
@Override
- public void onReject(String callId, ResultReceiver resultReceiver) {
- handleHandshakeCallback(ON_REJECT, callId, 0, resultReceiver);
- }
-
- @Override
- public void onDisconnect(String callId, ResultReceiver resultReceiver) {
- handleHandshakeCallback(ON_DISCONNECT, callId, 0, resultReceiver);
+ public void onDisconnect(String callId, DisconnectCause cause,
+ ResultReceiver resultReceiver) {
+ handleCallEventCallback(ON_DISCONNECT, callId, resultReceiver, cause);
}
@Override
@@ -308,7 +300,7 @@
@Override
public void onCallStreamingStarted(String callId, ResultReceiver resultReceiver) {
- handleHandshakeCallback(ON_STREAMING_STARTED, callId, 0, resultReceiver);
+ handleCallEventCallback(ON_STREAMING_STARTED, callId, resultReceiver);
}
@Override
diff --git a/telecomm/java/com/android/internal/telecom/ICallControl.aidl b/telecomm/java/com/android/internal/telecom/ICallControl.aidl
index 3e651e9..5e2c923 100644
--- a/telecomm/java/com/android/internal/telecom/ICallControl.aidl
+++ b/telecomm/java/com/android/internal/telecom/ICallControl.aidl
@@ -27,6 +27,7 @@
*/
oneway interface ICallControl {
void setActive(String callId, in ResultReceiver callback);
+ void answer(int videoState, String callId, in ResultReceiver callback);
void setInactive(String callId, in ResultReceiver callback);
void disconnect(String callId, in DisconnectCause disconnectCause, in ResultReceiver callback);
void startCallStreaming(String callId, in ResultReceiver callback);
diff --git a/telecomm/java/com/android/internal/telecom/ICallEventCallback.aidl b/telecomm/java/com/android/internal/telecom/ICallEventCallback.aidl
index dd61d17..213cafb 100644
--- a/telecomm/java/com/android/internal/telecom/ICallEventCallback.aidl
+++ b/telecomm/java/com/android/internal/telecom/ICallEventCallback.aidl
@@ -23,6 +23,7 @@
import android.os.ResultReceiver;
import android.telecom.CallAudioState;
import android.telecom.CallException;
+import android.telecom.DisconnectCause;
import java.util.List;
/**
@@ -36,8 +37,7 @@
void onSetActive(String callId, in ResultReceiver callback);
void onSetInactive(String callId, in ResultReceiver callback);
void onAnswer(String callId, int videoState, in ResultReceiver callback);
- void onReject(String callId, in ResultReceiver callback);
- void onDisconnect(String callId, in ResultReceiver callback);
+ void onDisconnect(String callId, in DisconnectCause cause, in ResultReceiver callback);
// -- Streaming related. Client registered call streaming capabilities should override
void onCallStreamingStarted(String callId, in ResultReceiver callback);
void onCallStreamingFailed(String callId, int reason);
diff --git a/telephony/java/android/service/euicc/EuiccProfileInfo.java b/telephony/java/android/service/euicc/EuiccProfileInfo.java
index 7eccd1a..728dfa6 100644
--- a/telephony/java/android/service/euicc/EuiccProfileInfo.java
+++ b/telephony/java/android/service/euicc/EuiccProfileInfo.java
@@ -24,6 +24,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.service.carrier.CarrierIdentifier;
+import android.telephony.SubscriptionInfo;
import android.telephony.UiccAccessRule;
import android.text.TextUtils;
@@ -451,6 +452,8 @@
+ mPolicyRules
+ ", accessRules="
+ Arrays.toString(mAccessRules)
+ + ", iccid="
+ + SubscriptionInfo.givePrintableIccid(mIccid)
+ ")";
}
}
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index b418a02..3e2c7c4 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -4401,7 +4401,6 @@
*
* @throws IllegalArgumentException if subscription is invalid.
* @throws SecurityException if the caller doesn't have permissions required.
- * @throws IllegalStateException if subscription service is not available.
*
* @hide
*/
@@ -4418,8 +4417,7 @@
if (iSub != null) {
return iSub.getSubscriptionUserHandle(subscriptionId);
} else {
- throw new IllegalStateException("[getSubscriptionUserHandle]: "
- + "subscription service unavailable");
+ Log.e(LOG_TAG, "[getSubscriptionUserHandle]: subscription service unavailable");
}
} catch (RemoteException ex) {
ex.rethrowAsRuntimeException();
diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java
index 174675f..a8fb36b 100644
--- a/telephony/java/android/telephony/ims/feature/ImsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java
@@ -342,8 +342,8 @@
/**
* @return The SIM slot index associated with this ImsFeature.
*
- * @see SubscriptionManager#getSubscriptionIds(int) for more information on getting the
- * subscription IDs associated with this slot.
+ * @see SubscriptionManager#getSubscriptionId(int) for more information on getting the
+ * subscription ID associated with this slot.
* @hide
*/
@SystemApi
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 3009bec..f8cf81c 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -1255,6 +1255,15 @@
* SOS_SMS or LOCATION_SHARING.
* @param datagram encoded gateway datagram which is encrypted by the caller.
* Datagram will be passed down to modem without any encoding or encryption.
+ * @param needFullScreenPointingUI If set to true, this indicates pointingUI app to open in full
+ * screen mode if satellite communication needs pointingUI.
+ * If this is set to false, pointingUI may be presented to the
+ * user in collapsed view. Application may decide to mark this
+ * flag as true when the user is sending data for the first time
+ * or whenever there is a considerable idle time between
+ * satellite activity. This decision should be done based upon
+ * user activity and the application's ability to determine the
+ * best possible UX experience for the user.
* @param executor The executor on which the result listener will be called.
* @param callback The callback object to which the result will be returned.
* If datagram is sent successfully, then
@@ -1267,7 +1276,8 @@
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
public void sendSatelliteDatagram(long datagramId, @DatagramType int datagramType,
- @NonNull SatelliteDatagram datagram, @NonNull @CallbackExecutor Executor executor,
+ @NonNull SatelliteDatagram datagram, boolean needFullScreenPointingUI,
+ @NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<Long, SatelliteException> callback) {
Objects.requireNonNull(datagram);
Objects.requireNonNull(executor);
@@ -1299,7 +1309,7 @@
}
};
telephony.sendSatelliteDatagram(mSubId, datagramId, datagramType, datagram,
- receiver);
+ needFullScreenPointingUI, receiver);
} else {
throw new IllegalStateException("telephony service is null.");
}
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteService.java b/telephony/java/android/telephony/satellite/stub/SatelliteService.java
index 3861c5b..5b96e34 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteService.java
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteService.java
@@ -20,7 +20,8 @@
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
-import android.util.Log;
+
+import com.android.telephony.Rlog;
/**
* Main SatelliteService implementation, which binds via the Telephony SatelliteServiceController.
@@ -44,7 +45,7 @@
* @hide
*/
public class SatelliteService extends Service {
- public static final String TAG = "SatelliteService";
+ private static final String TAG = "SatelliteService";
@SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
public static final String SERVICE_INTERFACE = "android.telephony.satellite.SatelliteService";
@@ -55,7 +56,7 @@
@Override
public IBinder onBind(Intent intent) {
if (SERVICE_INTERFACE.equals(intent.getAction())) {
- Log.e(TAG, "SatelliteService bound");
+ Rlog.d(TAG, "SatelliteService bound");
return new SatelliteImplBase(Runnable::run).getBinder();
}
return null;
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index f4801c2..ef9dc3e 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2938,13 +2938,16 @@
* @param datagramId An id that uniquely identifies datagram requested to be sent.
* @param datagramType Type of datagram.
* @param datagram Datagram to send over satellite.
+ * @param needFullScreenPointingUI this is used to indicate pointingUI app to open in
+ * full screen mode.
* @param receiver Result receiver to get the datagramId if datagram is sent successfully else
* error code of the request.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
void sendSatelliteDatagram(int subId, long datagramId, int datagramType,
- in SatelliteDatagram datagram, in ResultReceiver receiver);
+ in SatelliteDatagram datagram, in boolean needFullScreenPointingUI,
+ in ResultReceiver receiver);
/**
* Request to get whether satellite communication is allowed for the current location.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
index 08786d3..fb6fb22 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
@@ -19,12 +19,15 @@
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
+import android.platform.test.rule.SettingOverrideRule
+import android.provider.Settings
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerBuilder
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
+import org.junit.ClassRule
import org.junit.FixMethodOrder
import org.junit.Ignore
import org.junit.Test
@@ -130,5 +133,16 @@
fun getParams(): Collection<FlickerTest> {
return FlickerTestFactory.nonRotationTests()
}
+
+ /**
+ * Ensures that posted notifications will be visible on the lockscreen and not
+ * suppressed due to being marked as seen.
+ */
+ @ClassRule
+ @JvmField
+ val disableUnseenNotifFilterRule = SettingOverrideRule(
+ Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+ /* value= */ "0",
+ )
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
index a5d85cc..32276d6 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
@@ -18,6 +18,8 @@
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
+import android.platform.test.rule.SettingOverrideRule
+import android.provider.Settings
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerBuilder
import com.android.server.wm.flicker.FlickerTest
@@ -25,6 +27,7 @@
import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
+import org.junit.ClassRule
import org.junit.FixMethodOrder
import org.junit.Ignore
import org.junit.Test
@@ -145,5 +148,16 @@
fun getParams(): Collection<FlickerTest> {
return FlickerTestFactory.nonRotationTests()
}
+
+ /**
+ * Ensures that posted notifications will be visible on the lockscreen and not
+ * suppressed due to being marked as seen.
+ */
+ @ClassRule
+ @JvmField
+ val disableUnseenNotifFilterRule = SettingOverrideRule(
+ Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+ /* value= */ "0",
+ )
}
}