Merge "feat(#AlwaysOnMagnifier): Add haptic and buffer zone when panning scale to persisted scale" 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..9f3ceb3 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 {
@@ -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);
@@ -42065,8 +42078,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 +50395,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 +51992,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 +52049,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 +52205,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 +52376,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 +52420,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 +52433,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 +55632,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 +55655,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 0f79499..47d9ab6 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -843,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);
@@ -10205,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/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/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/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/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/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/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/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/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/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/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/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/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index 96602619..db17a53 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -505,6 +505,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();
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/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..5805d0e 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -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/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/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/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/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/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/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/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/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/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 0d25bec..e3236b3 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -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/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/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/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/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/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/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/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 d9c7234..f32ea71 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 =
- releasedFlag(216, "customizable_lock_screen_quick_affordances")
+ unreleasedFlag(216, "customizable_lock_screen_quick_affordances", teamfood = true)
/** 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 = releasedFlag(222, "revamped_wallpaper_ui")
+ val REVAMPED_WALLPAPER_UI = unreleasedFlag(222, "revamped_wallpaper_ui", teamfood = true)
/** A different path for unocclusion transitions back to keyguard */
// TODO(b/262859270): Tracking Bug
@@ -214,9 +214,10 @@
// TODO(b/266242192): Tracking Bug
@JvmField
val LOCK_SCREEN_LONG_PRESS_ENABLED =
- releasedFlag(
+ unreleasedFlag(
228,
- "lock_screen_long_press_enabled"
+ "lock_screen_long_press_enabled",
+ teamfood = true,
)
// 300 - power menu
@@ -246,6 +247,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 =
@@ -294,6 +298,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/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/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/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/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/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/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/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
index f57b35a..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(),
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/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/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/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/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/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/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/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 b68d993..0c36626 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1348,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);
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/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/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index f5875ab..8ef4e4a 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -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;
@@ -2689,6 +2691,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 +3314,7 @@
"InputMethodManagerService#startStylusHandwriting");
int uid = Binder.getCallingUid();
synchronized (ImfLock.class) {
+ mHwController.clearPendingHandwritingDelegation();
if (!canInteractWithImeLocked(uid, client, "startStylusHandwriting",
null /* statsToken */)) {
return;
@@ -3331,6 +3342,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 +3363,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) {
@@ -4852,6 +4932,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();
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/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..ed3248ec 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.
*/
@@ -286,7 +286,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 +313,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 +387,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 +454,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 +475,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 +520,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 +624,7 @@
+ formatElapsedRealtimeMillis(currentElapsedRealtimeMillis)
+ ", latestTimeResult=" + latestTimeResult
+ ", mTryAgainCounter=" + mTryAgainCounter
- + ", refreshDelayMillis=" + refreshDelayMillis
+ + ", refreshAttemptDelayMillis=" + refreshAttemptDelayMillis
+ ", nextRefreshElapsedRealtimeMillis="
+ formatElapsedRealtimeMillis(nextRefreshElapsedRealtimeMillis));
}
@@ -592,6 +654,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 +668,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/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/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 de7c867..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.isReadyToComputeImeParent(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 2d3d382..ce3379e 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -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 49218ad..71dd917 100644
--- a/services/core/java/com/android/server/wm/ImeTargetVisibilityPolicy.java
+++ b/services/core/java/com/android/server/wm/ImeTargetVisibilityPolicy.java
@@ -19,6 +19,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
+import android.annotation.Nullable;
import android.os.IBinder;
import android.view.WindowManager;
@@ -50,13 +51,18 @@
* Called when {@link DisplayContent#computeImeParent()} to check if it's valid to keep
* computing the ime parent.
*
+ * @param imeLayeringTarget The window which IME target to layer on top of it.
+ * @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 isReadyToComputeImeParent(WindowState imeLayeringTarget,
- InputTarget imeInputTarget) {
+ 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:
@@ -69,7 +75,6 @@
boolean imeLayeringTargetMayUseIme =
WindowManager.LayoutParams.mayUseInputMethod(imeLayeringTarget.mAttrs.flags)
|| imeLayeringTarget.mAttrs.type == TYPE_APPLICATION_STARTING;
-
// Do not change parent if the window hasn't requested IME.
var inputAndLayeringTargetsDisagree = (imeInputTarget == null
|| imeLayeringTarget.mActivityRecord != imeInputTarget.getActivityRecord());
@@ -77,4 +82,46 @@
return !inputTargetStale;
}
+
+
+ /**
+ * 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 WindowState inputTargetWindow = imeInputTarget.getWindowState();
+ if (inputTargetWindow == null || !imeLayeringTarget.isAttached()
+ || !inputTargetWindow.isAttached()) {
+ return false;
+ }
+
+ 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/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/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/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..8b913db 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -58,6 +58,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 +67,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 +276,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 +310,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 +360,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 +387,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 +404,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 +447,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 +496,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 +513,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,
@@ -776,6 +736,10 @@
throws IllegalArgumentException, NonCredentialProviderCallerException {
Log.i(TAG, "registerCredentialDescription");
+ if (!isCredentialDescriptionApiEnabled()) {
+ throw new UnsupportedOperationException();
+ }
+
enforceCallingPackage(callingPackage, Binder.getCallingUid());
List<CredentialProviderInfo> services =
@@ -828,6 +792,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/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/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/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/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/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index dab842c..df3af7d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -24,6 +24,7 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
@@ -613,4 +614,34 @@
assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, tf1.getOrientation(SCREEN_ORIENTATION_UNSET));
assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, task.getOrientation(SCREEN_ORIENTATION_UNSET));
}
+
+ @Test
+ public void testUpdateImeParentForActivityEmbedding() {
+ // Setup two activities in ActivityEmbedding.
+ final Task task = createTask(mDisplayContent);
+ final TaskFragment tf0 = new TaskFragmentBuilder(mAtm)
+ .setParentTask(task)
+ .createActivityCount(1)
+ .setOrganizer(mOrganizer)
+ .setFragmentToken(new Binder())
+ .build();
+ final TaskFragment tf1 = new TaskFragmentBuilder(mAtm)
+ .setParentTask(task)
+ .createActivityCount(1)
+ .setOrganizer(mOrganizer)
+ .setFragmentToken(new Binder())
+ .build();
+ final ActivityRecord activity0 = tf0.getTopMostActivity();
+ final ActivityRecord activity1 = tf1.getTopMostActivity();
+ final WindowState win0 = createWindow(null, TYPE_BASE_APPLICATION, activity0, "win0");
+ final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity1, "win1");
+ doReturn(false).when(mDisplayContent).shouldImeAttachedToApp();
+
+ mDisplayContent.setImeInputTarget(win0);
+ mDisplayContent.setImeLayeringTarget(win1);
+
+ // The ImeParent should be the display.
+ assertEquals(mDisplayContent.getImeContainer().getParent().getSurfaceControl(),
+ mDisplayContent.computeImeParent());
+ }
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
index 021823e..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");
@@ -516,10 +536,15 @@
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;
@@ -530,13 +555,23 @@
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");
@@ -571,6 +606,7 @@
mDestroyed = true;
mDebugHotwordLogging = false;
mRemoteDetectionService = null;
+ mRemoteExceptionListener = null;
if (mAttentionManagerInternal != null) {
mAttentionManagerInternal.onStopProximityUpdates(mProximityCallbackInternal);
}
@@ -599,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 e6cb943..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")
@@ -131,9 +134,18 @@
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,15 +153,29 @@
try {
newResult = mHotwordAudioStreamCopier.startCopyingAudioStreams(result);
} catch (IOException e) {
- Slog.w(TAG, "Ignoring #onDetected due to a 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);
}
@@ -181,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);
@@ -216,6 +251,7 @@
HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION,
mVoiceInteractionServiceUid);
+ notifyOnDetectorRemoteException();
}
},
MAX_VALIDATION_TIMEOUT_MILLIS,
@@ -248,6 +284,7 @@
HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_REJECTED_EXCEPTION,
mVoiceInteractionServiceUid);
+ notifyOnDetectorRemoteException();
}
mValidatingDspTrigger = false;
}
@@ -261,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..4359234 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -69,6 +69,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;
@@ -147,6 +148,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 +162,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 +172,7 @@
mVisualQueryDetectionComponentName = visualQueryDetectionServiceName;
mUser = userId;
mDetectorType = detectorType;
+ mRemoteExceptionListener = listener;
mReStartPeriodSeconds = DeviceConfig.getInt(DeviceConfig.NAMESPACE_VOICE_INTERACTION,
KEY_RESTART_PERIOD_IN_SECONDS, 0);
@@ -251,6 +256,7 @@
void cancelLocked() {
Slog.v(TAG, "cancelLocked");
clearDebugHotwordLoggingTimeoutLocked();
+ mRemoteExceptionListener = null;
runForEachDetectorSessionLocked((session) -> {
session.destroyLocked();
});
@@ -772,7 +778,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 +788,7 @@
session = new VisualQueryDetectorSession(
mRemoteVisualQueryDetectionService, mLock, mContext, token, callback,
mVoiceInteractionServiceUid, mVoiceInteractorIdentity,
- mScheduledExecutorService, mDebugHotwordLogging);
+ mScheduledExecutorService, mDebugHotwordLogging, mRemoteExceptionListener);
} else {
if (mRemoteHotwordDetectionService == null) {
mRemoteHotwordDetectionService =
@@ -790,7 +797,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 3f053b0..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")
@@ -123,9 +127,18 @@
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);
@@ -135,12 +148,30 @@
} 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) {
@@ -206,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/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/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/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/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",
+ )
}
}