Merge "Move package parsing implementations to internal" into main
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
index 03891bb..e3ba50d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
@@ -26,6 +26,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -319,8 +320,8 @@
final boolean isStopped = mPackageManagerInternal.isPackageStopped(packageName, uid);
mPackageStoppedState.add(uid, packageName, isStopped);
return isStopped;
- } catch (IllegalArgumentException e) {
- Slog.d(TAG, "Couldn't determine stopped state for unknown package: " + packageName);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "Couldn't determine stopped state for unknown package: " + packageName);
return false;
}
}
diff --git a/core/api/current.txt b/core/api/current.txt
index 83e3fab..7f261d4 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -5285,9 +5285,10 @@
field public static final int START_TIMESTAMP_RESERVED_RANGE_DEVELOPER_START = 21; // 0x15
field public static final int START_TIMESTAMP_RESERVED_RANGE_SYSTEM = 20; // 0x14
field public static final int START_TIMESTAMP_SURFACEFLINGER_COMPOSITION_COMPLETE = 7; // 0x7
- field public static final int START_TYPE_COLD = 0; // 0x0
- field public static final int START_TYPE_HOT = 2; // 0x2
- field public static final int START_TYPE_WARM = 1; // 0x1
+ field public static final int START_TYPE_COLD = 1; // 0x1
+ field public static final int START_TYPE_HOT = 3; // 0x3
+ field public static final int START_TYPE_UNSET = 0; // 0x0
+ field public static final int START_TYPE_WARM = 2; // 0x2
}
public final class AsyncNotedAppOp implements android.os.Parcelable {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 7237af3..225b31c 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -13051,7 +13051,7 @@
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.HotwordDetector createHotwordDetector(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull java.util.concurrent.Executor, @NonNull android.service.voice.HotwordDetector.Callback);
method @NonNull @RequiresPermission("android.permission.MANAGE_VOICE_KEYPHRASES") public final android.media.voice.KeyphraseModelManager createKeyphraseModelManager();
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.VisualQueryDetector createVisualQueryDetector(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull java.util.concurrent.Executor, @NonNull android.service.voice.VisualQueryDetector.Callback);
- method @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public void setIsReceiveSandboxedTrainingDataAllowed(boolean);
+ method @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public void setShouldReceiveSandboxedTrainingData(boolean);
}
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 8b39ed6..6ddb36a 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4077,7 +4077,7 @@
final LoadedApk sdkApk = getPackageInfo(
contextInfo.getSdkApplicationInfo(),
r.packageInfo.getCompatibilityInfo(),
- ActivityContextInfo.CONTEXT_FLAGS);
+ contextInfo.getContextFlags());
final ContextImpl activityContext = ContextImpl.createActivityContext(
this, sdkApk, r.activityInfo, r.token, displayId, r.overrideConfig);
diff --git a/core/java/android/app/ApplicationStartInfo.java b/core/java/android/app/ApplicationStartInfo.java
index c8317c8..656feb0 100644
--- a/core/java/android/app/ApplicationStartInfo.java
+++ b/core/java/android/app/ApplicationStartInfo.java
@@ -104,14 +104,17 @@
/** Process started due to Activity started for any reason not explicitly listed. */
public static final int START_REASON_START_ACTIVITY = 11;
+ /** Start type not yet set. */
+ public static final int START_TYPE_UNSET = 0;
+
/** Process started from scratch. */
- public static final int START_TYPE_COLD = 0;
+ public static final int START_TYPE_COLD = 1;
/** Process retained minimally SavedInstanceState. */
- public static final int START_TYPE_WARM = 1;
+ public static final int START_TYPE_WARM = 2;
/** Process brought back to foreground. */
- public static final int START_TYPE_HOT = 2;
+ public static final int START_TYPE_HOT = 3;
/**
* Default. The system always creates a new instance of the activity in the target task and
@@ -277,6 +280,7 @@
@IntDef(
prefix = {"START_TYPE_"},
value = {
+ START_TYPE_UNSET,
START_TYPE_COLD,
START_TYPE_WARM,
START_TYPE_HOT,
@@ -769,6 +773,7 @@
private static String startTypeToString(@StartType int startType) {
return switch (startType) {
+ case START_TYPE_UNSET -> "UNSET";
case START_TYPE_COLD -> "COLD";
case START_TYPE_WARM -> "WARM";
case START_TYPE_HOT -> "HOT";
diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index d935449..53a21cd 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -16,6 +16,8 @@
package android.app;
+import static com.android.internal.util.Preconditions.checkArgument;
+
import android.annotation.DrawableRes;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
@@ -390,7 +392,7 @@
*/
@FlaggedApi(Flags.FLAG_MODES_API)
public void setType(@Type int type) {
- mType = type;
+ mType = checkValidType(type);
}
/**
@@ -451,6 +453,24 @@
mAllowManualInvocation = allowManualInvocation;
}
+ /** @hide */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public void validate() {
+ if (Flags.modesApi()) {
+ checkValidType(mType);
+ }
+ }
+
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ @Type
+ private static int checkValidType(@Type int type) {
+ checkArgument(type >= TYPE_UNKNOWN && type <= TYPE_MANAGED,
+ "Rule type must be one of TYPE_UNKNOWN, TYPE_OTHER, TYPE_SCHEDULE_TIME, "
+ + "TYPE_SCHEDULE_CALENDAR, TYPE_BEDTIME, TYPE_DRIVING, TYPE_IMMERSIVE, "
+ + "TYPE_THEATER, or TYPE_MANAGED");
+ return type;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -703,10 +723,10 @@
}
/**
- * Sets the type of the rule
+ * Sets the type of the rule.
*/
public @NonNull Builder setType(@Type int type) {
- mType = type;
+ mType = checkValidType(type);
return this;
}
@@ -714,7 +734,7 @@
* Sets a user visible description of when this rule will be active
* (see {@link Condition#STATE_TRUE}).
*
- * A description should be a (localized) string like "Mon-Fri, 9pm-7am" or
+ * <p>A description should be a (localized) string like "Mon-Fri, 9pm-7am" or
* "When connected to [Car Name]".
*/
public @NonNull Builder setTriggerDescription(@Nullable String description) {
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index e9f419e..6f7299a 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -64,6 +64,7 @@
PendingIntent getActivityLaunchIntent(String callingPackage, in ComponentName component,
in UserHandle user);
LauncherUserInfo getLauncherUserInfo(in UserHandle user);
+ List<String> getPreInstalledSystemPackages(in UserHandle user);
void showAppDetailsAsUser(in IApplicationThread caller, String callingPackage,
String callingFeatureId, in ComponentName component, in Rect sourceBounds,
in Bundle opts, in UserHandle user);
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 0cd4358..ccc8f09 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -800,6 +800,29 @@
}
/**
+ * Returns the list of the system packages that are installed at user creation.
+ *
+ * <p>An empty list denotes that all system packages are installed for that user at creation.
+ * This behaviour is inherited from the underlining UserManager API.
+ *
+ * @param userHandle the user for which installed system packages are required.
+ * @return {@link List} of {@link String}, representing the package name of the installed
+ * package. Can be empty but not null.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+ public List<String> getPreInstalledSystemPackages(@NonNull UserHandle userHandle) {
+ if (DEBUG) {
+ Log.i(TAG, "getPreInstalledSystemPackages for user: " + userHandle);
+ }
+ try {
+ return mService.getPreInstalledSystemPackages(userHandle);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns the activity info for a given intent and user handle, if it resolves. Otherwise it
* returns null.
*
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 93fbe8a..7cf10d8 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -1216,7 +1216,7 @@
* <ul>
* <li>Profile {@link android.hardware.camera2.params.DynamicRangeProfiles#HLG10 }</li>
* <li>All mandatory stream combinations for this specific capability as per
- * <a href="CameraDevice#10-bit-output-additional-guaranteed-configurations">documentation</a></li>
+ * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#10-bit-output-additional-guaranteed-configurations">documentation</a></li>
* <li>In case the device is not able to capture some combination of supported
* standard 8-bit and/or 10-bit dynamic range profiles within the same capture request,
* then those constraints must be listed in
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 06397c9..ded96a2 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -1484,7 +1484,7 @@
* <p>To start a CaptureSession with a target FPS range different from the
* capture request template's default value, the application
* is strongly recommended to call
- * {@link SessionConfiguration#setSessionParameters }
+ * {@link android.hardware.camera2.params.SessionConfiguration#setSessionParameters }
* with the target fps range before creating the capture session. The aeTargetFpsRange is
* typically a session parameter. Specifying it at session creation time helps avoid
* session reconfiguration delays in cases like 60fps or high speed recording.</p>
@@ -2161,7 +2161,7 @@
* OFF if the recording output is not stabilized, or if there are no output
* Surface types that can be stabilized.</p>
* <p>The application is strongly recommended to call
- * {@link SessionConfiguration#setSessionParameters }
+ * {@link android.hardware.camera2.params.SessionConfiguration#setSessionParameters }
* with the desired video stabilization mode before creating the capture session.
* Video stabilization mode is a session parameter on many devices. Specifying
* it at session creation time helps avoid reconfiguration delay caused by difference
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index ab4406c3..1d26d69 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -899,7 +899,7 @@
* <p>To start a CaptureSession with a target FPS range different from the
* capture request template's default value, the application
* is strongly recommended to call
- * {@link SessionConfiguration#setSessionParameters }
+ * {@link android.hardware.camera2.params.SessionConfiguration#setSessionParameters }
* with the target fps range before creating the capture session. The aeTargetFpsRange is
* typically a session parameter. Specifying it at session creation time helps avoid
* session reconfiguration delays in cases like 60fps or high speed recording.</p>
@@ -2382,7 +2382,7 @@
* OFF if the recording output is not stabilized, or if there are no output
* Surface types that can be stabilized.</p>
* <p>The application is strongly recommended to call
- * {@link SessionConfiguration#setSessionParameters }
+ * {@link android.hardware.camera2.params.SessionConfiguration#setSessionParameters }
* with the desired video stabilization mode before creating the capture session.
* Video stabilization mode is a session parameter on many devices. Specifying
* it at session creation time helps avoid reconfiguration delay caused by difference
diff --git a/core/java/android/service/notification/Condition.java b/core/java/android/service/notification/Condition.java
index 531626b..6e771f8 100644
--- a/core/java/android/service/notification/Condition.java
+++ b/core/java/android/service/notification/Condition.java
@@ -1,4 +1,4 @@
-/**
+/*
* Copyright (c) 2014, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,6 +16,8 @@
package android.service.notification;
+import static com.android.internal.util.Preconditions.checkArgument;
+
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -117,7 +119,7 @@
/** The source of, or reason for, the state change represented by this Condition. **/
@FlaggedApi(Flags.FLAG_MODES_API)
- public final @Source int source;
+ public final @Source int source; // default = SOURCE_UNKNOWN
/**
* The maximum string length for any string contained in this condition.
@@ -179,7 +181,7 @@
this.line2 = getTrimmedString(line2);
this.icon = icon;
this.state = state;
- this.source = source;
+ this.source = checkValidSource(source);
this.flags = flags;
}
@@ -197,10 +199,26 @@
source.readInt());
}
+ /** @hide */
+ public void validate() {
+ if (Flags.modesApi()) {
+ checkValidSource(source);
+ }
+ }
+
private static boolean isValidState(int state) {
return state >= STATE_FALSE && state <= STATE_ERROR;
}
+ private static int checkValidSource(@Source int source) {
+ if (Flags.modesApi()) {
+ checkArgument(source >= SOURCE_UNKNOWN && source <= SOURCE_CONTEXT,
+ "Condition source must be one of SOURCE_UNKNOWN, SOURCE_USER_ACTION, "
+ + "SOURCE_SCHEDULE, or SOURCE_CONTEXT");
+ }
+ return source;
+ }
+
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(id, 0);
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index a5b087c..fcdc5fe 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -1980,6 +1980,7 @@
@Nullable public ZenDeviceEffects zenDeviceEffects;
public boolean modified; // rule has been modified from initial creation
public String pkg;
+ @AutomaticZenRule.Type
public int type = AutomaticZenRule.TYPE_UNKNOWN;
public String triggerDescription;
public String iconResName;
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index fba0923..75ab48a 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -1042,13 +1042,13 @@
@SystemApi
@FlaggedApi(Flags.FLAG_ALLOW_TRAINING_DATA_EGRESS_FROM_HDS)
@RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION)
- public void setIsReceiveSandboxedTrainingDataAllowed(boolean allowed) {
- Log.i(TAG, "setIsReceiveSandboxedTrainingDataAllowed to " + allowed);
+ public void setShouldReceiveSandboxedTrainingData(boolean allowed) {
+ Log.i(TAG, "setShouldReceiveSandboxedTrainingData to " + allowed);
if (mSystemService == null) {
throw new IllegalStateException("Not available until onReady() is called");
}
try {
- mSystemService.setIsReceiveSandboxedTrainingDataAllowed(allowed);
+ mSystemService.setShouldReceiveSandboxedTrainingData(allowed);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index dbacca5..9bf43a3 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -162,6 +162,8 @@
* @param flags See {@code View#startDragAndDrop}
* @param surface Surface containing drag shadow image
* @param touchSource See {@code InputDevice#getSource()}
+ * @param touchDeviceId device ID of last touch event
+ * @param pointerId pointer ID of last touch event
* @param touchX X coordinate of last touch point
* @param touchY Y coordinate of last touch point
* @param thumbCenterX X coordinate for the position within the shadow image that should be
@@ -171,9 +173,9 @@
* @param data Data transferred by drag and drop
* @return Token of drag operation which will be passed to cancelDragAndDrop.
*/
- @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
IBinder performDrag(IWindow window, int flags, in SurfaceControl surface, int touchSource,
- float touchX, float touchY, float thumbCenterX, float thumbCenterY, in ClipData data);
+ int touchDeviceId, int touchPointerId, float touchX, float touchY, float thumbCenterX,
+ float thumbCenterY, in ClipData data);
/**
* Drops the content of the current drag operation for accessibility
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 75f8eba..bb5ee03 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -28340,6 +28340,8 @@
IBinder token = mAttachInfo.mSession.performDrag(
mAttachInfo.mWindow, flags, null,
mAttachInfo.mViewRootImpl.getLastTouchSource(),
+ mAttachInfo.mViewRootImpl.getLastTouchDeviceId(),
+ mAttachInfo.mViewRootImpl.getLastTouchPointerId(),
0f, 0f, 0f, 0f, data);
if (ViewDebug.DEBUG_DRAG) {
Log.d(VIEW_LOG_TAG, "startDragAndDrop via a11y action returned " + token);
@@ -28414,7 +28416,8 @@
}
token = mAttachInfo.mSession.performDrag(mAttachInfo.mWindow, flags, surfaceControl,
- root.getLastTouchSource(), lastTouchPoint.x, lastTouchPoint.y,
+ root.getLastTouchSource(), root.getLastTouchDeviceId(),
+ root.getLastTouchPointerId(), lastTouchPoint.x, lastTouchPoint.y,
shadowTouchPoint.x, shadowTouchPoint.y, data);
if (ViewDebug.DEBUG_DRAG) {
Log.d(VIEW_LOG_TAG, "performDrag returned " + token);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 8339d33..e83488e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -813,6 +813,8 @@
final PointF mDragPoint = new PointF();
final PointF mLastTouchPoint = new PointF();
int mLastTouchSource;
+ int mLastTouchDeviceId = KeyCharacterMap.VIRTUAL_KEYBOARD;
+ int mLastTouchPointerId;
/** Tracks last {@link MotionEvent#getToolType(int)} with {@link MotionEvent#ACTION_UP}. **/
private int mLastClickToolType;
@@ -7163,6 +7165,8 @@
mLastTouchPoint.x = event.getRawX();
mLastTouchPoint.y = event.getRawY();
mLastTouchSource = event.getSource();
+ mLastTouchDeviceId = event.getDeviceId();
+ mLastTouchPointerId = event.getPointerId(0);
// Register last ACTION_UP. This will be propagated to IME.
if (event.getActionMasked() == MotionEvent.ACTION_UP) {
@@ -8574,6 +8578,14 @@
return mLastTouchSource;
}
+ public int getLastTouchDeviceId() {
+ return mLastTouchDeviceId;
+ }
+
+ public int getLastTouchPointerId() {
+ return mLastTouchPointerId;
+ }
+
/**
* Used by InputMethodManager.
* @hide
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 393d256..d6ac562 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -489,8 +489,9 @@
@Override
public android.os.IBinder performDrag(android.view.IWindow window, int flags,
- android.view.SurfaceControl surface, int touchSource, float touchX, float touchY,
- float thumbCenterX, float thumbCenterY, android.content.ClipData data) {
+ android.view.SurfaceControl surface, int touchSource, int touchDeviceId,
+ int touchPointerId, float touchX, float touchY, float thumbCenterX, float thumbCenterY,
+ android.content.ClipData data) {
return null;
}
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 82ee8fc..e92c6a6 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -397,5 +397,5 @@
* sandboxed detection (from trusted process).
*/
@EnforcePermission("MANAGE_HOTWORD_DETECTION")
- void setIsReceiveSandboxedTrainingDataAllowed(boolean allowed);
+ void setShouldReceiveSandboxedTrainingData(boolean allowed);
}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 28fd2b4..bf8e613 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -897,13 +897,26 @@
}
/**
- * Returns true if {@code userHandle} is a managed profile with separate challenge.
+ * Returns true if {@code userHandle} is a profile with separate challenge.
+ * <p>
+ * Returns false if {@code userHandle} is a profile with unified challenge, a profile whose
+ * credential is not shareable with its parent, or a non-profile user.
*/
public boolean isSeparateProfileChallengeEnabled(int userHandle) {
return isCredentialSharableWithParent(userHandle) && hasSeparateChallenge(userHandle);
}
/**
+ * Returns true if {@code userHandle} is a profile with unified challenge.
+ * <p>
+ * Returns false if {@code userHandle} is a profile with separate challenge, a profile whose
+ * credential is not shareable with its parent, or a non-profile user.
+ */
+ public boolean isProfileWithUnifiedChallenge(int userHandle) {
+ return isCredentialSharableWithParent(userHandle) && !hasSeparateChallenge(userHandle);
+ }
+
+ /**
* Returns true if {@code userHandle} is a managed profile with unified challenge.
*/
public boolean isManagedProfileWithUnifiedChallenge(int userHandle) {
diff --git a/core/java/com/android/internal/widget/LockscreenCredential.java b/core/java/com/android/internal/widget/LockscreenCredential.java
index c88763c..18d5f6d 100644
--- a/core/java/com/android/internal/widget/LockscreenCredential.java
+++ b/core/java/com/android/internal/widget/LockscreenCredential.java
@@ -134,12 +134,12 @@
}
/**
- * Creates a LockscreenCredential object representing a managed password for profile with
- * unified challenge. This credentiall will have type {@code CREDENTIAL_TYPE_PASSWORD} for now.
- * TODO: consider add a new credential type for this. This can then supersede the
- * isLockTiedToParent argument in various places in LSS.
+ * Creates a LockscreenCredential object representing the system-generated, system-managed
+ * password for a profile with unified challenge. This credential has type {@code
+ * CREDENTIAL_TYPE_PASSWORD} for now. TODO: consider add a new credential type for this. This
+ * can then supersede the isLockTiedToParent argument in various places in LSS.
*/
- public static LockscreenCredential createManagedPassword(@NonNull byte[] password) {
+ public static LockscreenCredential createUnifiedProfilePassword(@NonNull byte[] password) {
return new LockscreenCredential(CREDENTIAL_TYPE_PASSWORD,
Arrays.copyOf(password, password.length), /* hasInvalidChars= */ false);
}
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 1f6ac80..4596ca7 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5252,6 +5252,8 @@
<!-- Zen mode - name of default automatic calendar time-based rule that is triggered every night (when sleeping). [CHAR LIMIT=40] -->
<string name="zen_mode_default_every_night_name">Sleeping</string>
+ <!-- Zen mode - Trigger description of the rule, indicating which app owns it. [CHAR_LIMIT=100] -->
+ <string name="zen_mode_implicit_trigger_description">Managed by <xliff:g id="app_name">%1$s</xliff:g></string>
<!-- Zen mode - Condition summary when a rule is activated due to a call to setInterruptionFilter(). [CHAR_LIMIT=NONE] -->
<string name="zen_mode_implicit_activated">On</string>
<!-- Zen mode - Condition summary when a rule is deactivated due to a call to setInterruptionFilter(). [CHAR_LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 5791ddb..fd6158d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2586,6 +2586,7 @@
<java-symbol type="string" name="zen_mode_default_weekends_name" />
<java-symbol type="string" name="zen_mode_default_events_name" />
<java-symbol type="string" name="zen_mode_default_every_night_name" />
+ <java-symbol type="string" name="zen_mode_implicit_trigger_description" />
<java-symbol type="string" name="zen_mode_implicit_activated" />
<java-symbol type="string" name="zen_mode_implicit_deactivated" />
<java-symbol type="string" name="display_rotation_camera_compat_toast_after_rotation" />
diff --git a/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java b/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java
index ba2ea88..d629f6a 100644
--- a/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java
+++ b/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java
@@ -19,15 +19,20 @@
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.fail;
+import static org.junit.Assert.assertThrows;
+
import android.content.ComponentName;
import android.net.Uri;
import android.os.Parcel;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.google.common.base.Strings;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -38,6 +43,9 @@
public class AutomaticZenRuleTest {
private static final String CLASS = "android.app.AutomaticZenRule";
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Test
public void testLongFields_inConstructor() {
String longString = Strings.repeat("A", 65536);
@@ -100,6 +108,7 @@
}
@Test
+ @EnableFlags(Flags.FLAG_MODES_API)
public void testLongInputsFromParcel() {
// Create a rule with long fields, set directly via reflection so that we can confirm that
// a rule with too-long fields that comes in via a parcel has its fields truncated directly.
@@ -152,6 +161,60 @@
fromParcel.getOwner().getPackageName().length());
assertEquals(AutomaticZenRule.MAX_STRING_LENGTH,
fromParcel.getOwner().getClassName().length());
- assertEquals(AutomaticZenRule.MAX_DESC_LENGTH, rule.getTriggerDescription().length());
+ assertEquals(AutomaticZenRule.MAX_DESC_LENGTH, fromParcel.getTriggerDescription().length());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void validate_builderWithValidType_succeeds() throws Exception {
+ AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", Uri.parse("uri"))
+ .setType(AutomaticZenRule.TYPE_BEDTIME)
+ .build();
+ rule.validate(); // No exception.
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void validate_builderWithoutType_succeeds() throws Exception {
+ AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", Uri.parse("uri")).build();
+ rule.validate(); // No exception.
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void validate_constructorWithoutType_succeeds() throws Exception {
+ AutomaticZenRule rule = new AutomaticZenRule("rule", new ComponentName("pkg", "cps"),
+ new ComponentName("pkg", "activity"), Uri.parse("condition"), null,
+ NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+ rule.validate(); // No exception.
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void validate_invalidType_throws() throws Exception {
+ AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", Uri.parse("uri")).build();
+
+ // Set the field via reflection.
+ Field typeField = AutomaticZenRule.class.getDeclaredField("mType");
+ typeField.setAccessible(true);
+ typeField.set(rule, 100);
+
+ assertThrows(IllegalArgumentException.class, rule::validate);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void setType_invalidType_throws() {
+ AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", Uri.parse("uri")).build();
+
+ assertThrows(IllegalArgumentException.class, () -> rule.setType(100));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void setTypeBuilder_invalidType_throws() {
+ AutomaticZenRule.Builder builder = new AutomaticZenRule.Builder("rule", Uri.parse("uri"));
+
+ assertThrows(IllegalArgumentException.class, () -> builder.setType(100));
}
}
diff --git a/core/tests/coretests/src/android/service/notification/ConditionTest.java b/core/tests/coretests/src/android/service/notification/ConditionTest.java
index 612562e..e94273e 100644
--- a/core/tests/coretests/src/android/service/notification/ConditionTest.java
+++ b/core/tests/coretests/src/android/service/notification/ConditionTest.java
@@ -21,9 +21,12 @@
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.fail;
+import static org.junit.Assert.assertThrows;
+
import android.app.Flags;
import android.net.Uri;
import android.os.Parcel;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -36,6 +39,7 @@
import org.junit.runner.RunWith;
import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -194,4 +198,59 @@
Condition fromParcel = new Condition(parcel);
assertThat(fromParcel).isEqualTo(cond);
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void constructor_unspecifiedSource_succeeds() {
+ new Condition(Uri.parse("id"), "Summary", Condition.STATE_TRUE);
+ // No exception.
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void constructor_validSource_succeeds() {
+ new Condition(Uri.parse("id"), "Summary", Condition.STATE_TRUE, Condition.SOURCE_CONTEXT);
+ // No exception.
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void constructor_invalidSource_throws() {
+ assertThrows(IllegalArgumentException.class,
+ () -> new Condition(Uri.parse("uri"), "Summary", Condition.STATE_TRUE, 1000));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void constructor_parcelWithInvalidSource_throws() {
+ Condition original = new Condition(Uri.parse("condition"), "Summary", Condition.STATE_TRUE,
+ Condition.SOURCE_SCHEDULE);
+ Parcel parcel = Parcel.obtain();
+ original.writeToParcel(parcel, 0);
+
+ // Tweak the parcel to contain and invalid source value.
+ parcel.setDataPosition(parcel.dataPosition() - 8); // going back two int fields.
+ parcel.writeInt(100);
+ parcel.setDataPosition(0);
+
+ assertThrows(IllegalArgumentException.class, () -> new Condition(parcel));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void validate_invalidSource_throws() throws Exception {
+ Condition condition = new Condition(Uri.parse("condition"), "Summary", Condition.STATE_TRUE,
+ Condition.SOURCE_SCHEDULE);
+
+ Field typeField = Condition.class.getDeclaredField("source");
+
+ // Reflection on reflection (ugh) to make a final field non-final
+ Field fieldAccessFlagsField = Field.class.getDeclaredField("accessFlags");
+ fieldAccessFlagsField.setAccessible(true);
+ fieldAccessFlagsField.setInt(typeField, typeField.getModifiers() & ~Modifier.FINAL);
+
+ typeField.setInt(condition, 30);
+
+ assertThrows(IllegalArgumentException.class, condition::validate);
+ }
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
index 15d14e8..b315f94 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
@@ -241,7 +241,7 @@
for (int i = 0; i < displays.length; i++) {
DisplayAddress.Physical address =
(DisplayAddress.Physical) displays[i].getAddress();
- if (mRearDisplayAddress == address.getPhysicalDisplayId()) {
+ if (address != null && mRearDisplayAddress == address.getPhysicalDisplayId()) {
rearDisplayMetrics = new DisplayMetrics();
final Display rearDisplay = displays[i];
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index 0f0fbd9c..f801b0d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -83,6 +83,7 @@
private int mStartPos;
private GestureDetector mDoubleTapDetector;
private boolean mInteractive;
+ private boolean mHideHandle;
private boolean mSetTouchRegion = true;
private int mLastDraggingPosition;
private int mHandleRegionWidth;
@@ -211,11 +212,8 @@
}
/** Sets up essential dependencies of the divider bar. */
- public void setup(
- SplitLayout layout,
- SplitWindowManager splitWindowManager,
- SurfaceControlViewHost viewHost,
- InsetsState insetsState) {
+ public void setup(SplitLayout layout, SplitWindowManager splitWindowManager,
+ SurfaceControlViewHost viewHost, InsetsState insetsState) {
mSplitLayout = layout;
mSplitWindowManager = splitWindowManager;
mViewHost = viewHost;
@@ -277,6 +275,7 @@
R.dimen.docked_stack_divider_lift_elevation);
mDoubleTapDetector = new GestureDetector(getContext(), new DoubleTapListener());
mInteractive = true;
+ mHideHandle = false;
setOnTouchListener(this);
mHandle.setAccessibilityDelegate(mHandleDelegate);
setWillNotDraw(false);
@@ -469,10 +468,11 @@
void setInteractive(boolean interactive, boolean hideHandle, String from) {
if (interactive == mInteractive) return;
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
- "Set divider bar %s from %s", interactive ? "interactive" : "non-interactive",
- from);
+ "Set divider bar %s hide handle=%b from %s",
+ interactive ? "interactive" : "non-interactive", hideHandle, from);
mInteractive = interactive;
- if (!mInteractive && hideHandle && mMoving) {
+ mHideHandle = hideHandle;
+ if (!mInteractive && mHideHandle && mMoving) {
final int position = mSplitLayout.getDividePosition();
mSplitLayout.flingDividePosition(
mLastDraggingPosition,
@@ -482,7 +482,15 @@
mMoving = false;
}
releaseTouching();
- mHandle.setVisibility(!mInteractive && hideHandle ? View.INVISIBLE : View.VISIBLE);
+ mHandle.setVisibility(!mInteractive && mHideHandle ? View.INVISIBLE : View.VISIBLE);
+ }
+
+ boolean isInteractive() {
+ return mInteractive;
+ }
+
+ boolean isHandleHidden() {
+ return mHideHandle;
}
private class DoubleTapListener extends GestureDetector.SimpleOnGestureListener {
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 b699533..53caddb 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
@@ -59,6 +59,7 @@
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.animation.Interpolators;
@@ -70,6 +71,7 @@
import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
import com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.io.PrintWriter;
import java.util.function.Consumer;
@@ -420,7 +422,7 @@
public void init() {
if (mInitialized) return;
mInitialized = true;
- mSplitWindowManager.init(this, mInsetsState);
+ mSplitWindowManager.init(this, mInsetsState, false /* isRestoring */);
mDisplayImeController.addPositionProcessor(mImePositionProcessor);
}
@@ -442,14 +444,19 @@
}
/** Releases and re-inflates {@link DividerView} on the root surface. */
- public void update(SurfaceControl.Transaction t) {
+ public void update(SurfaceControl.Transaction t, boolean resetImePosition) {
if (!mInitialized) {
init();
return;
}
mSplitWindowManager.release(t);
- mImePositionProcessor.reset();
- mSplitWindowManager.init(this, mInsetsState);
+ if (resetImePosition) {
+ mImePositionProcessor.reset();
+ }
+ mSplitWindowManager.init(this, mInsetsState, true /* isRestoring */);
+ // Update the surface positions again after recreating the divider in case nothing else
+ // triggers it
+ mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this);
}
@Override
@@ -868,6 +875,9 @@
pw.println(prefix + TAG + ":");
pw.println(innerPrefix + "mAllowLeftRightSplitInPortrait=" + mAllowLeftRightSplitInPortrait);
pw.println(innerPrefix + "mIsLeftRightSplit=" + mIsLeftRightSplit);
+ pw.println(innerPrefix + "mFreezeDividerWindow=" + mFreezeDividerWindow);
+ pw.println(innerPrefix + "mDimNonImeSide=" + mDimNonImeSide);
+ pw.println(innerPrefix + "mDividerPosition=" + mDividerPosition);
pw.println(innerPrefix + "bounds1=" + mBounds1.toShortString());
pw.println(innerPrefix + "dividerBounds=" + mDividerBounds.toShortString());
pw.println(innerPrefix + "bounds2=" + mBounds2.toShortString());
@@ -1151,14 +1161,16 @@
mTargetYOffset = needOffset ? getTargetYOffset() : 0;
if (mTargetYOffset != mLastYOffset) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Split IME animation starting, fromY=%d toY=%d",
+ mLastYOffset, mTargetYOffset);
// Freeze the configuration size with offset to prevent app get a configuration
// changed or relaunch. This is required to make sure client apps will calculate
// insets properly after layout shifted.
if (mTargetYOffset == 0) {
mSplitLayoutHandler.setLayoutOffsetTarget(0, 0, SplitLayout.this);
} else {
- mSplitLayoutHandler.setLayoutOffsetTarget(0, mTargetYOffset - mLastYOffset,
- SplitLayout.this);
+ mSplitLayoutHandler.setLayoutOffsetTarget(0, mTargetYOffset, SplitLayout.this);
}
}
@@ -1183,6 +1195,8 @@
public void onImeEndPositioning(int displayId, boolean cancel,
SurfaceControl.Transaction t) {
if (displayId != mDisplayId || !mHasImeFocus || cancel) return;
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Split IME animation ending, canceled=%b", cancel);
onProgress(1.0f);
mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
index 00361d9..8fb9bda 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
@@ -62,6 +62,10 @@
// Used to "pass" a transaction to WWM.remove so that view removal can be synchronized.
private SurfaceControl.Transaction mSyncTransaction = null;
+ // For saving/restoring state
+ private boolean mLastDividerInteractive = true;
+ private boolean mLastDividerHandleHidden;
+
public interface ParentContainerCallbacks {
void attachToParentSurface(SurfaceControl.Builder b);
void onLeashReady(SurfaceControl leash);
@@ -107,7 +111,7 @@
}
/** Inflates {@link DividerView} on to the root surface. */
- void init(SplitLayout splitLayout, InsetsState insetsState) {
+ void init(SplitLayout splitLayout, InsetsState insetsState, boolean isRestoring) {
if (mDividerView != null || mViewHost != null) {
throw new UnsupportedOperationException(
"Try to inflate divider view again without release first");
@@ -130,6 +134,10 @@
lp.accessibilityTitle = mContext.getResources().getString(R.string.accessibility_divider);
mViewHost.setView(mDividerView, lp);
mDividerView.setup(splitLayout, this, mViewHost, insetsState);
+ if (isRestoring) {
+ mDividerView.setInteractive(mLastDividerInteractive, mLastDividerHandleHidden,
+ "restore_setup");
+ }
}
/**
@@ -138,6 +146,8 @@
*/
void release(@Nullable SurfaceControl.Transaction t) {
if (mDividerView != null) {
+ mLastDividerInteractive = mDividerView.isInteractive();
+ mLastDividerHandleHidden = mDividerView.isHandleHidden();
mDividerView = null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 449bef5..77427d9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -1666,7 +1666,7 @@
}
void finishEnterSplitScreen(SurfaceControl.Transaction finishT) {
- mSplitLayout.update(finishT);
+ mSplitLayout.update(finishT, true /* resetImePosition */);
mMainStage.getSplitDecorManager().inflate(mContext, mMainStage.mRootLeash,
getMainStageBounds());
mSideStage.getSplitDecorManager().inflate(mContext, mSideStage.mRootLeash,
@@ -1860,9 +1860,10 @@
&& mSplitLayout.updateConfiguration(mRootTaskInfo.configuration)
&& mMainStage.isActive()) {
// Clear the divider remote animating flag as the divider will be re-rendered to apply
- // the new rotation config.
+ // the new rotation config. Don't reset the IME state since those updates are not in
+ // sync with task info changes.
mIsDividerRemoteAnimating = false;
- mSplitLayout.update(null /* t */);
+ mSplitLayout.update(null /* t */, false /* resetImePosition */);
onLayoutSizeChanged(mSplitLayout);
}
}
@@ -2325,7 +2326,7 @@
*/
public void updateSurfaces(SurfaceControl.Transaction transaction) {
updateSurfaceBounds(mSplitLayout, transaction, /* applyResizingOffset */ false);
- mSplitLayout.update(transaction);
+ mSplitLayout.update(transaction, true /* resetImePosition */);
}
private void onDisplayChange(int displayId, int fromRotation, int toRotation,
@@ -2598,7 +2599,9 @@
final TransitionInfo.Change change = info.getChanges().get(iC);
if (change.getMode() == TRANSIT_CHANGE
&& (change.getFlags() & FLAG_IS_DISPLAY) != 0) {
- mSplitLayout.update(startTransaction);
+ // Don't reset the IME state since those updates are not in sync with the
+ // display change transition
+ mSplitLayout.update(startTransaction, false /* resetImePosition */);
}
if (mMixedHandler.isEnteringPip(change, transitType)) {
@@ -2699,7 +2702,7 @@
startTransaction, finishTransaction, finishCallback)) {
if (mSplitTransitions.isPendingResize(transition)) {
// Only need to update in resize because divider exist before transition.
- mSplitLayout.update(startTransaction);
+ mSplitLayout.update(startTransaction, true /* resetImePosition */);
startTransaction.apply();
}
return true;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
index 145c8f0..636c632 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
@@ -69,7 +69,7 @@
SplitWindowManager splitWindowManager = new SplitWindowManager("TestSplitWindowManager",
mContext,
configuration, mCallbacks);
- splitWindowManager.init(mSplitLayout, new InsetsState());
+ splitWindowManager.init(mSplitLayout, new InsetsState(), false /* isRestoring */);
mDividerView = spy((DividerView) splitWindowManager.getDividerView());
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java
index 2e5078d..150aa13 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java
@@ -59,7 +59,7 @@
@Test
@UiThreadTest
public void testInitRelease() {
- mSplitWindowManager.init(mSplitLayout, new InsetsState());
+ mSplitWindowManager.init(mSplitLayout, new InsetsState(), false /* isRestoring */);
assertThat(mSplitWindowManager.getSurfaceControl()).isNotNull();
mSplitWindowManager.release(null /* t */);
assertThat(mSplitWindowManager.getSurfaceControl()).isNull();
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
index e07e942..2da8c8c 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
@@ -156,10 +156,9 @@
if (customUserManager.isUserOfType(USER_TYPE_PROFILE_MANAGED)
&& customUserManager.isSameProfileGroup(dialogInfo.user, myUserHandle)) {
messageBuilder.append(isArchive
- ? getString(R.string.archive_application_text_current_user_work_profile,
- userName) : getString(
- R.string.uninstall_application_text_current_user_work_profile,
- userName));
+ ? getString(R.string.archive_application_text_current_user_work_profile)
+ : getString(
+ R.string.uninstall_application_text_current_user_work_profile));
} else if (customUserManager.isUserOfType(USER_TYPE_PROFILE_CLONE)
&& customUserManager.isSameProfileGroup(dialogInfo.user, myUserHandle)) {
mIsClonedApp = true;
@@ -168,11 +167,11 @@
} else if (Flags.allowPrivateProfile()
&& customUserManager.isPrivateProfile()
&& customUserManager.isSameProfileGroup(dialogInfo.user, myUserHandle)) {
- messageBuilder.append(isArchive ? getString(
- R.string.archive_application_text_current_user_private_profile,
- userName) : getString(
- R.string.uninstall_application_text_current_user_private_profile,
- userName));
+ messageBuilder.append(
+ isArchive ? getString(
+ R.string.archive_application_text_current_user_private_profile)
+ : getString(
+ R.string.uninstall_application_text_current_user_private_profile));
} else if (isArchive) {
messageBuilder.append(
getString(R.string.archive_application_text_user, userName));
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt
index d0d2dc0..e099f11 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt
@@ -20,13 +20,17 @@
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
+import android.util.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.flowOn
+private const val TAG = "BroadcastReceiverFlow"
+
/**
* A [BroadcastReceiver] flow for the given [intentFilter].
*/
@@ -39,4 +43,6 @@
registerReceiver(broadcastReceiver, intentFilter, Context.RECEIVER_NOT_EXPORTED)
awaitClose { unregisterReceiver(broadcastReceiver) }
+}.catch { e ->
+ Log.e(TAG, "Error while broadcastReceiverFlow", e)
}.conflate().flowOn(Dispatchers.Default)
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt
index dfaf3c6..eef5225 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt
@@ -31,8 +31,10 @@
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doThrow
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
@RunWith(AndroidJUnit4::class)
class BroadcastReceiverFlowTest {
@@ -74,6 +76,18 @@
assertThat(onReceiveIsCalled).isTrue()
}
+ @Test
+ fun broadcastReceiverFlow_unregisterReceiverThrowException_noCrash() = runBlocking {
+ context.stub {
+ on { unregisterReceiver(any()) } doThrow IllegalArgumentException()
+ }
+ val flow = context.broadcastReceiverFlow(INTENT_FILTER)
+
+ flow.firstWithTimeoutOrNull()
+
+ assertThat(registeredBroadcastReceiver).isNotNull()
+ }
+
private companion object {
val INTENT_FILTER = IntentFilter()
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index 69b61c7..2cb44ec 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -31,7 +31,6 @@
import android.bluetooth.BluetoothLeBroadcastSubgroup;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothProfile.ServiceListener;
-import android.bluetooth.BluetoothStatusCodes;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
@@ -42,7 +41,6 @@
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
-import android.util.Pair;
import androidx.annotation.RequiresApi;
@@ -53,15 +51,17 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadLocalRandom;
/**
- * LocalBluetoothLeBroadcast provides an interface between the Settings app
- * and the functionality of the local {@link BluetoothLeBroadcast}.
- * Use the {@link BluetoothLeBroadcast.Callback} to get the result callback.
+ * LocalBluetoothLeBroadcast provides an interface between the Settings app and the functionality of
+ * the local {@link BluetoothLeBroadcast}. Use the {@link BluetoothLeBroadcast.Callback} to get the
+ * result callback.
*/
public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
private static final String TAG = "LocalBluetoothLeBroadcast";
@@ -74,11 +74,12 @@
// Order of this profile in device profiles list
private static final int ORDINAL = 1;
private static final int UNKNOWN_VALUE_PLACEHOLDER = -1;
- private static final Uri[] SETTINGS_URIS = new Uri[]{
- Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO),
- Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE),
- Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME),
- };
+ private static final Uri[] SETTINGS_URIS =
+ new Uri[] {
+ Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO),
+ Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE),
+ Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME),
+ };
private BluetoothLeBroadcast mServiceBroadcast;
private BluetoothLeBroadcastAssistant mServiceBroadcastAssistant;
@@ -95,62 +96,82 @@
private Executor mExecutor;
private ContentResolver mContentResolver;
private ContentObserver mSettingsObserver;
+ // Cached broadcast callbacks being register before service is connected.
+ private Map<BluetoothLeBroadcast.Callback, Executor> mCachedBroadcastCallbackExecutorMap =
+ new ConcurrentHashMap<>();
- private final ServiceListener mServiceListener = new ServiceListener() {
- @Override
- public void onServiceConnected(int profile, BluetoothProfile proxy) {
- if (DEBUG) {
- Log.d(TAG, "Bluetooth service connected: " + profile);
- }
- if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST) && !mIsBroadcastProfileReady) {
- mServiceBroadcast = (BluetoothLeBroadcast) proxy;
- mIsBroadcastProfileReady = true;
- registerServiceCallBack(mExecutor, mBroadcastCallback);
- List<BluetoothLeBroadcastMetadata> metadata = getAllBroadcastMetadata();
- if (!metadata.isEmpty()) {
- updateBroadcastInfoFromBroadcastMetadata(metadata.get(0));
+ private final ServiceListener mServiceListener =
+ new ServiceListener() {
+ @Override
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ if (DEBUG) {
+ Log.d(TAG, "Bluetooth service connected: " + profile);
+ }
+ if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST)
+ && !mIsBroadcastProfileReady) {
+ mServiceBroadcast = (BluetoothLeBroadcast) proxy;
+ mIsBroadcastProfileReady = true;
+ registerServiceCallBack(mExecutor, mBroadcastCallback);
+ List<BluetoothLeBroadcastMetadata> metadata = getAllBroadcastMetadata();
+ if (!metadata.isEmpty()) {
+ updateBroadcastInfoFromBroadcastMetadata(metadata.get(0));
+ }
+ registerContentObserver();
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "onServiceConnected: register "
+ + "mCachedBroadcastCallbackExecutorMap = "
+ + mCachedBroadcastCallbackExecutorMap);
+ }
+ mCachedBroadcastCallbackExecutorMap.forEach(
+ (callback, executor) ->
+ registerServiceCallBack(executor, callback));
+ } else if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT)
+ && !mIsBroadcastAssistantProfileReady) {
+ mIsBroadcastAssistantProfileReady = true;
+ mServiceBroadcastAssistant = (BluetoothLeBroadcastAssistant) proxy;
+ registerBroadcastAssistantCallback(mExecutor, mBroadcastAssistantCallback);
+ }
}
- registerContentObserver();
- } else if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT)
- && !mIsBroadcastAssistantProfileReady) {
- mIsBroadcastAssistantProfileReady = true;
- mServiceBroadcastAssistant = (BluetoothLeBroadcastAssistant) proxy;
- registerBroadcastAssistantCallback(mExecutor, mBroadcastAssistantCallback);
- }
- }
- @Override
- public void onServiceDisconnected(int profile) {
- if (DEBUG) {
- Log.d(TAG, "Bluetooth service disconnected");
- }
- if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST) && mIsBroadcastProfileReady) {
- mIsBroadcastProfileReady = false;
- unregisterServiceCallBack(mBroadcastCallback);
- }
- if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT)
- && mIsBroadcastAssistantProfileReady) {
- mIsBroadcastAssistantProfileReady = false;
- unregisterBroadcastAssistantCallback(mBroadcastAssistantCallback);
- }
+ @Override
+ public void onServiceDisconnected(int profile) {
+ if (DEBUG) {
+ Log.d(TAG, "Bluetooth service disconnected: " + profile);
+ }
+ if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST)
+ && mIsBroadcastProfileReady) {
+ mIsBroadcastProfileReady = false;
+ unregisterServiceCallBack(mBroadcastCallback);
+ mCachedBroadcastCallbackExecutorMap.clear();
+ }
+ if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT)
+ && mIsBroadcastAssistantProfileReady) {
+ mIsBroadcastAssistantProfileReady = false;
+ unregisterBroadcastAssistantCallback(mBroadcastAssistantCallback);
+ }
- if (!mIsBroadcastAssistantProfileReady && !mIsBroadcastProfileReady) {
- unregisterContentObserver();
- }
- }
- };
+ if (!mIsBroadcastAssistantProfileReady && !mIsBroadcastProfileReady) {
+ unregisterContentObserver();
+ }
+ }
+ };
private final BluetoothLeBroadcast.Callback mBroadcastCallback =
new BluetoothLeBroadcast.Callback() {
@Override
public void onBroadcastStarted(int reason, int broadcastId) {
if (DEBUG) {
- Log.d(TAG,
- "onBroadcastStarted(), reason = " + reason + ", broadcastId = "
+ Log.d(
+ TAG,
+ "onBroadcastStarted(), reason = "
+ + reason
+ + ", broadcastId = "
+ broadcastId);
}
setLatestBroadcastId(broadcastId);
- setAppSourceName(mNewAppSourceName, /*updateContentResolver=*/ true);
+ setAppSourceName(mNewAppSourceName, /* updateContentResolver= */ true);
}
@Override
@@ -161,8 +182,8 @@
}
@Override
- public void onBroadcastMetadataChanged(int broadcastId,
- @NonNull BluetoothLeBroadcastMetadata metadata) {
+ public void onBroadcastMetadataChanged(
+ int broadcastId, @NonNull BluetoothLeBroadcastMetadata metadata) {
if (DEBUG) {
Log.d(TAG, "onBroadcastMetadataChanged(), broadcastId = " + broadcastId);
}
@@ -172,8 +193,11 @@
@Override
public void onBroadcastStopped(int reason, int broadcastId) {
if (DEBUG) {
- Log.d(TAG,
- "onBroadcastStopped(), reason = " + reason + ", broadcastId = "
+ Log.d(
+ TAG,
+ "onBroadcastStopped(), reason = "
+ + reason
+ + ", broadcastId = "
+ broadcastId);
}
@@ -191,37 +215,42 @@
@Override
public void onBroadcastUpdated(int reason, int broadcastId) {
if (DEBUG) {
- Log.d(TAG,
- "onBroadcastUpdated(), reason = " + reason + ", broadcastId = "
+ Log.d(
+ TAG,
+ "onBroadcastUpdated(), reason = "
+ + reason
+ + ", broadcastId = "
+ broadcastId);
}
setLatestBroadcastId(broadcastId);
- setAppSourceName(mNewAppSourceName, /*updateContentResolver=*/ true);
+ setAppSourceName(mNewAppSourceName, /* updateContentResolver= */ true);
}
@Override
public void onBroadcastUpdateFailed(int reason, int broadcastId) {
if (DEBUG) {
- Log.d(TAG,
- "onBroadcastUpdateFailed(), reason = " + reason + ", broadcastId = "
+ Log.d(
+ TAG,
+ "onBroadcastUpdateFailed(), reason = "
+ + reason
+ + ", broadcastId = "
+ broadcastId);
}
}
@Override
- public void onPlaybackStarted(int reason, int broadcastId) {
- }
+ public void onPlaybackStarted(int reason, int broadcastId) {}
@Override
- public void onPlaybackStopped(int reason, int broadcastId) {
- }
+ public void onPlaybackStopped(int reason, int broadcastId) {}
};
private final BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
new BluetoothLeBroadcastAssistant.Callback() {
@Override
- public void onSourceAdded(@NonNull BluetoothDevice sink, int sourceId,
- int reason) {}
+ public void onSourceAdded(
+ @NonNull BluetoothDevice sink, int sourceId, int reason) {}
+
@Override
public void onSearchStarted(int reason) {}
@@ -238,38 +267,65 @@
public void onSourceFound(@NonNull BluetoothLeBroadcastMetadata source) {}
@Override
- public void onSourceAddFailed(@NonNull BluetoothDevice sink,
- @NonNull BluetoothLeBroadcastMetadata source, int reason) {}
-
- @Override
- public void onSourceModified(@NonNull BluetoothDevice sink, int sourceId,
+ public void onSourceAddFailed(
+ @NonNull BluetoothDevice sink,
+ @NonNull BluetoothLeBroadcastMetadata source,
int reason) {}
@Override
- public void onSourceModifyFailed(@NonNull BluetoothDevice sink, int sourceId,
- int reason) {}
+ public void onSourceModified(
+ @NonNull BluetoothDevice sink, int sourceId, int reason) {}
@Override
- public void onSourceRemoved(@NonNull BluetoothDevice sink, int sourceId,
- int reason) {
+ public void onSourceModifyFailed(
+ @NonNull BluetoothDevice sink, int sourceId, int reason) {}
+
+ @Override
+ public void onSourceRemoved(
+ @NonNull BluetoothDevice sink, int sourceId, int reason) {
if (DEBUG) {
- Log.d(TAG, "onSourceRemoved(), sink = " + sink + ", reason = "
- + reason + ", sourceId = " + sourceId);
+ Log.d(
+ TAG,
+ "onSourceRemoved(), sink = "
+ + sink
+ + ", reason = "
+ + reason
+ + ", sourceId = "
+ + sourceId);
}
}
@Override
- public void onSourceRemoveFailed(@NonNull BluetoothDevice sink, int sourceId,
- int reason) {
+ public void onSourceRemoveFailed(
+ @NonNull BluetoothDevice sink, int sourceId, int reason) {
if (DEBUG) {
- Log.d(TAG, "onSourceRemoveFailed(), sink = " + sink + ", reason = "
- + reason + ", sourceId = " + sourceId);
+ Log.d(
+ TAG,
+ "onSourceRemoveFailed(), sink = "
+ + sink
+ + ", reason = "
+ + reason
+ + ", sourceId = "
+ + sourceId);
}
}
@Override
- public void onReceiveStateChanged(@NonNull BluetoothDevice sink, int sourceId,
- @NonNull BluetoothLeBroadcastReceiveState state) {}
+ public void onReceiveStateChanged(
+ @NonNull BluetoothDevice sink,
+ int sourceId,
+ @NonNull BluetoothLeBroadcastReceiveState state) {
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "onReceiveStateChanged(), sink = "
+ + sink
+ + ", sourceId = "
+ + sourceId
+ + ", state = "
+ + state);
+ }
+ }
};
private class BroadcastSettingsObserver extends ContentObserver {
@@ -296,8 +352,8 @@
BluetoothAdapter.getDefaultAdapter()
.getProfileProxy(context, mServiceListener, BluetoothProfile.LE_AUDIO_BROADCAST);
BluetoothAdapter.getDefaultAdapter()
- .getProfileProxy(context, mServiceListener,
- BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
+ .getProfileProxy(
+ context, mServiceListener, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
}
/**
@@ -312,11 +368,11 @@
}
String programInfo = getProgramInfo();
if (DEBUG) {
- Log.d(TAG,
- "startBroadcast: language = " + language + " ,programInfo = " + programInfo);
+ Log.d(TAG, "startBroadcast: language = " + language + " ,programInfo = " + programInfo);
}
buildContentMetadata(language, programInfo);
- mServiceBroadcast.startBroadcast(mBluetoothLeAudioContentMetadata,
+ mServiceBroadcast.startBroadcast(
+ mBluetoothLeAudioContentMetadata,
(mBroadcastCode != null && mBroadcastCode.length > 0) ? mBroadcastCode : null);
}
@@ -325,7 +381,7 @@
}
public void setProgramInfo(String programInfo) {
- setProgramInfo(programInfo, /*updateContentResolver=*/ true);
+ setProgramInfo(programInfo, /* updateContentResolver= */ true);
}
private void setProgramInfo(String programInfo, boolean updateContentResolver) {
@@ -344,8 +400,10 @@
Log.d(TAG, "mContentResolver is null");
return;
}
- Settings.Secure.putString(mContentResolver,
- Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO, programInfo);
+ Settings.Secure.putString(
+ mContentResolver,
+ Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO,
+ programInfo);
}
}
@@ -354,7 +412,7 @@
}
public void setBroadcastCode(byte[] broadcastCode) {
- setBroadcastCode(broadcastCode, /*updateContentResolver=*/ true);
+ setBroadcastCode(broadcastCode, /* updateContentResolver= */ true);
}
private void setBroadcastCode(byte[] broadcastCode, boolean updateContentResolver) {
@@ -372,7 +430,9 @@
Log.d(TAG, "mContentResolver is null");
return;
}
- Settings.Secure.putString(mContentResolver, Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE,
+ Settings.Secure.putString(
+ mContentResolver,
+ Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE,
new String(broadcastCode, StandardCharsets.UTF_8));
}
}
@@ -401,8 +461,10 @@
Log.d(TAG, "mContentResolver is null");
return;
}
- Settings.Secure.putString(mContentResolver,
- Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME, mAppSourceName);
+ Settings.Secure.putString(
+ mContentResolver,
+ Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME,
+ mAppSourceName);
}
}
@@ -427,10 +489,11 @@
if (mBluetoothLeBroadcastMetadata == null) {
final List<BluetoothLeBroadcastMetadata> metadataList =
mServiceBroadcast.getAllBroadcastMetadata();
- mBluetoothLeBroadcastMetadata = metadataList.stream()
- .filter(i -> i.getBroadcastId() == mBroadcastId)
- .findFirst()
- .orElse(null);
+ mBluetoothLeBroadcastMetadata =
+ metadataList.stream()
+ .filter(i -> i.getBroadcastId() == mBroadcastId)
+ .findFirst()
+ .orElse(null);
}
return mBluetoothLeBroadcastMetadata;
}
@@ -440,22 +503,27 @@
Log.d(TAG, "updateBroadcastInfoFromContentProvider: mContentResolver is null");
return;
}
- String programInfo = Settings.Secure.getString(mContentResolver,
- Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO);
+ String programInfo =
+ Settings.Secure.getString(
+ mContentResolver, Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO);
if (programInfo == null) {
programInfo = getDefaultValueOfProgramInfo();
}
- setProgramInfo(programInfo, /*updateContentResolver=*/ false);
+ setProgramInfo(programInfo, /* updateContentResolver= */ false);
- String prefBroadcastCode = Settings.Secure.getString(mContentResolver,
- Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE);
- byte[] broadcastCode = (prefBroadcastCode == null) ? getDefaultValueOfBroadcastCode()
- : prefBroadcastCode.getBytes(StandardCharsets.UTF_8);
- setBroadcastCode(broadcastCode, /*updateContentResolver=*/ false);
+ String prefBroadcastCode =
+ Settings.Secure.getString(
+ mContentResolver, Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE);
+ byte[] broadcastCode =
+ (prefBroadcastCode == null)
+ ? getDefaultValueOfBroadcastCode()
+ : prefBroadcastCode.getBytes(StandardCharsets.UTF_8);
+ setBroadcastCode(broadcastCode, /* updateContentResolver= */ false);
- String appSourceName = Settings.Secure.getString(mContentResolver,
- Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME);
- setAppSourceName(appSourceName, /*updateContentResolver=*/ false);
+ String appSourceName =
+ Settings.Secure.getString(
+ mContentResolver, Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME);
+ setAppSourceName(appSourceName, /* updateContentResolver= */ false);
}
private void updateBroadcastInfoFromBroadcastMetadata(
@@ -474,12 +542,12 @@
}
BluetoothLeAudioContentMetadata contentMetadata = subgroup.get(0).getContentMetadata();
setProgramInfo(contentMetadata.getProgramInfo());
- setAppSourceName(getAppSourceName(), /*updateContentResolver=*/ true);
+ setAppSourceName(getAppSourceName(), /* updateContentResolver= */ true);
}
/**
- * Stop the latest LE Broadcast. If the system stopped the LE Broadcast, then the system
- * calls the corresponding callback {@link BluetoothLeBroadcast.Callback}.
+ * Stop the latest LE Broadcast. If the system stopped the LE Broadcast, then the system calls
+ * the corresponding callback {@link BluetoothLeBroadcast.Callback}.
*/
public void stopLatestBroadcast() {
stopBroadcast(mBroadcastId);
@@ -511,7 +579,8 @@
}
String programInfo = getProgramInfo();
if (DEBUG) {
- Log.d(TAG,
+ Log.d(
+ TAG,
"updateBroadcast: language = " + language + " ,programInfo = " + programInfo);
}
mNewAppSourceName = appSourceName;
@@ -519,50 +588,79 @@
mServiceBroadcast.updateBroadcast(mBroadcastId, mBluetoothLeAudioContentMetadata);
}
- public void registerServiceCallBack(@NonNull @CallbackExecutor Executor executor,
- @NonNull BluetoothLeBroadcast.Callback callback) {
- if (mServiceBroadcast == null) {
- Log.d(TAG, "The BluetoothLeBroadcast is null.");
- return;
- }
-
- mServiceBroadcast.registerCallback(executor, callback);
- }
-
/**
- * Register Broadcast Assistant Callbacks to track it's state and receivers
+ * Register Broadcast Callbacks to track its state and receivers
*
* @param executor Executor object for callback
* @param callback Callback object to be registered
*/
- public void registerBroadcastAssistantCallback(@NonNull @CallbackExecutor Executor executor,
+ public void registerServiceCallBack(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull BluetoothLeBroadcast.Callback callback) {
+ if (mServiceBroadcast == null) {
+ Log.d(TAG, "registerServiceCallBack failed, the BluetoothLeBroadcast is null.");
+ mCachedBroadcastCallbackExecutorMap.putIfAbsent(callback, executor);
+ return;
+ }
+
+ try {
+ mServiceBroadcast.registerCallback(executor, callback);
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "registerServiceCallBack failed. " + e.getMessage());
+ }
+ }
+
+ /**
+ * Register Broadcast Assistant Callbacks to track its state and receivers
+ *
+ * @param executor Executor object for callback
+ * @param callback Callback object to be registered
+ */
+ private void registerBroadcastAssistantCallback(
+ @NonNull @CallbackExecutor Executor executor,
@NonNull BluetoothLeBroadcastAssistant.Callback callback) {
if (mServiceBroadcastAssistant == null) {
- Log.d(TAG, "The BluetoothLeBroadcastAssisntant is null.");
+ Log.d(
+ TAG,
+ "registerBroadcastAssistantCallback failed, "
+ + "the BluetoothLeBroadcastAssistant is null.");
return;
}
mServiceBroadcastAssistant.registerCallback(executor, callback);
}
- public void unregisterServiceCallBack(@NonNull BluetoothLeBroadcast.Callback callback) {
- if (mServiceBroadcast == null) {
- Log.d(TAG, "The BluetoothLeBroadcast is null.");
- return;
- }
-
- mServiceBroadcast.unregisterCallback(callback);
- }
-
/**
- * Unregister previousely registered Broadcast Assistant Callbacks
+ * Unregister previously registered Broadcast Callbacks
*
* @param callback Callback object to be unregistered
*/
- public void unregisterBroadcastAssistantCallback(
+ public void unregisterServiceCallBack(@NonNull BluetoothLeBroadcast.Callback callback) {
+ mCachedBroadcastCallbackExecutorMap.remove(callback);
+ if (mServiceBroadcast == null) {
+ Log.d(TAG, "unregisterServiceCallBack failed, the BluetoothLeBroadcast is null.");
+ return;
+ }
+
+ try {
+ mServiceBroadcast.unregisterCallback(callback);
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "unregisterServiceCallBack failed. " + e.getMessage());
+ }
+ }
+
+ /**
+ * Unregister previously registered Broadcast Assistant Callbacks
+ *
+ * @param callback Callback object to be unregistered
+ */
+ private void unregisterBroadcastAssistantCallback(
@NonNull BluetoothLeBroadcastAssistant.Callback callback) {
if (mServiceBroadcastAssistant == null) {
- Log.d(TAG, "The BluetoothLeBroadcastAssisntant is null.");
+ Log.d(
+ TAG,
+ "unregisterBroadcastAssistantCallback, "
+ + "the BluetoothLeBroadcastAssistant is null.");
return;
}
@@ -570,8 +668,8 @@
}
private void buildContentMetadata(String language, String programInfo) {
- mBluetoothLeAudioContentMetadata = mBuilder.setLanguage(language).setProgramInfo(
- programInfo).build();
+ mBluetoothLeAudioContentMetadata =
+ mBuilder.setLanguage(language).setProgramInfo(programInfo).build();
}
public LocalBluetoothLeBroadcastMetadata getLocalBluetoothLeBroadcastMetaData() {
@@ -600,9 +698,7 @@
return true;
}
- /**
- * Not supported since LE Audio Broadcasts do not establish a connection.
- */
+ /** Not supported since LE Audio Broadcasts do not establish a connection. */
public int getConnectionStatus(BluetoothDevice device) {
if (mServiceBroadcast == null) {
return BluetoothProfile.STATE_DISCONNECTED;
@@ -611,9 +707,7 @@
return mServiceBroadcast.getConnectionState(device);
}
- /**
- * Not supported since LE Audio Broadcasts do not establish a connection.
- */
+ /** Not supported since LE Audio Broadcasts do not establish a connection. */
public List<BluetoothDevice> getConnectedDevices() {
if (mServiceBroadcast == null) {
return new ArrayList<BluetoothDevice>(0);
@@ -622,8 +716,8 @@
return mServiceBroadcast.getConnectedDevices();
}
- public @NonNull
- List<BluetoothLeBroadcastMetadata> getAllBroadcastMetadata() {
+ /** Get all broadcast metadata. */
+ public @NonNull List<BluetoothLeBroadcastMetadata> getAllBroadcastMetadata() {
if (mServiceBroadcast == null) {
Log.d(TAG, "The BluetoothLeBroadcast is null.");
return Collections.emptyList();
@@ -640,16 +734,14 @@
return !mServiceBroadcast.getAllBroadcastMetadata().isEmpty();
}
- /**
- * Service does not provide method to get/set policy.
- */
+ /** Service does not provide method to get/set policy. */
public int getConnectionPolicy(BluetoothDevice device) {
return CONNECTION_POLICY_FORBIDDEN;
}
/**
- * Service does not provide "setEnabled" method. Please use {@link #startBroadcast},
- * {@link #stopBroadcast()} or {@link #updateBroadcast(String, String)}
+ * Service does not provide "setEnabled" method. Please use {@link #startBroadcast}, {@link
+ * #stopBroadcast()} or {@link #updateBroadcast(String, String)}
*/
public boolean setEnabled(BluetoothDevice device, boolean enabled) {
return false;
@@ -683,9 +775,8 @@
}
if (mServiceBroadcast != null) {
try {
- BluetoothAdapter.getDefaultAdapter().closeProfileProxy(
- BluetoothProfile.LE_AUDIO_BROADCAST,
- mServiceBroadcast);
+ BluetoothAdapter.getDefaultAdapter()
+ .closeProfileProxy(BluetoothProfile.LE_AUDIO_BROADCAST, mServiceBroadcast);
mServiceBroadcast = null;
} catch (Throwable t) {
Log.w(TAG, "Error cleaning up LeAudio proxy", t);
@@ -694,13 +785,13 @@
}
private String getDefaultValueOfProgramInfo() {
- //set the default value;
+ // set the default value;
int postfix = ThreadLocalRandom.current().nextInt(DEFAULT_CODE_MIN, DEFAULT_CODE_MAX);
return BluetoothAdapter.getDefaultAdapter().getName() + UNDERLINE + postfix;
}
private byte[] getDefaultValueOfBroadcastCode() {
- //set the default value;
+ // set the default value;
return generateRandomPassword().getBytes(StandardCharsets.UTF_8);
}
@@ -708,14 +799,14 @@
if (DEBUG) {
Log.d(TAG, "resetCacheInfo:");
}
- setAppSourceName("", /*updateContentResolver=*/ true);
+ setAppSourceName("", /* updateContentResolver= */ true);
mBluetoothLeBroadcastMetadata = null;
mBroadcastId = UNKNOWN_VALUE_PLACEHOLDER;
}
private String generateRandomPassword() {
String randomUUID = UUID.randomUUID().toString();
- //first 12 chars from xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
+ // first 12 chars from xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
return randomUUID.substring(0, 8) + randomUUID.substring(9, 13);
}
@@ -752,5 +843,4 @@
}
}
}
-
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
index bb103b8..34008ac 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
@@ -39,13 +39,14 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
-
/**
- * LocalBluetoothLeBroadcastAssistant provides an interface between the Settings app
- * and the functionality of the local {@link BluetoothLeBroadcastAssistant}.
- * Use the {@link BluetoothLeBroadcastAssistant.Callback} to get the result callback.
+ * LocalBluetoothLeBroadcastAssistant provides an interface between the Settings app and the
+ * functionality of the local {@link BluetoothLeBroadcastAssistant}. Use the {@link
+ * BluetoothLeBroadcastAssistant.Callback} to get the result callback.
*/
public class LocalBluetoothLeBroadcastAssistant implements LocalBluetoothProfile {
private static final String TAG = "LocalBluetoothLeBroadcastAssistant";
@@ -62,58 +63,76 @@
private BluetoothLeBroadcastMetadata mBluetoothLeBroadcastMetadata;
private BluetoothLeBroadcastMetadata.Builder mBuilder;
private boolean mIsProfileReady;
+ // Cached assistant callbacks being register before service is connected.
+ private final Map<BluetoothLeBroadcastAssistant.Callback, Executor> mCachedCallbackExecutorMap =
+ new ConcurrentHashMap<>();
- private final ServiceListener mServiceListener = new ServiceListener() {
- @Override
- public void onServiceConnected(int profile, BluetoothProfile proxy) {
- if (DEBUG) {
- Log.d(TAG, "Bluetooth service connected");
- }
- mService = (BluetoothLeBroadcastAssistant) proxy;
- // We just bound to the service, so refresh the UI for any connected LeAudio devices.
- List<BluetoothDevice> deviceList = mService.getConnectedDevices();
- while (!deviceList.isEmpty()) {
- BluetoothDevice nextDevice = deviceList.remove(0);
- CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
- // we may add a new device here, but generally this should not happen
- if (device == null) {
+ private final ServiceListener mServiceListener =
+ new ServiceListener() {
+ @Override
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
if (DEBUG) {
- Log.d(TAG, "LocalBluetoothLeBroadcastAssistant found new device: "
- + nextDevice);
+ Log.d(TAG, "Bluetooth service connected");
}
- device = mDeviceManager.addDevice(nextDevice);
+ mService = (BluetoothLeBroadcastAssistant) proxy;
+ // We just bound to the service, so refresh the UI for any connected LeAudio
+ // devices.
+ List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+ while (!deviceList.isEmpty()) {
+ BluetoothDevice nextDevice = deviceList.remove(0);
+ CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
+ // we may add a new device here, but generally this should not happen
+ if (device == null) {
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "LocalBluetoothLeBroadcastAssistant found new device: "
+ + nextDevice);
+ }
+ device = mDeviceManager.addDevice(nextDevice);
+ }
+ device.onProfileStateChanged(
+ LocalBluetoothLeBroadcastAssistant.this,
+ BluetoothProfile.STATE_CONNECTED);
+ device.refresh();
+ }
+
+ mProfileManager.callServiceConnectedListeners();
+ mIsProfileReady = true;
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "onServiceConnected, register mCachedCallbackExecutorMap = "
+ + mCachedCallbackExecutorMap);
+ }
+ mCachedCallbackExecutorMap.forEach(
+ (callback, executor) -> registerServiceCallBack(executor, callback));
}
- device.onProfileStateChanged(LocalBluetoothLeBroadcastAssistant.this,
- BluetoothProfile.STATE_CONNECTED);
- device.refresh();
- }
- mProfileManager.callServiceConnectedListeners();
- mIsProfileReady = true;
- }
+ @Override
+ public void onServiceDisconnected(int profile) {
+ if (profile != BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT) {
+ Log.d(TAG, "The profile is not LE_AUDIO_BROADCAST_ASSISTANT");
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Bluetooth service disconnected");
+ }
+ mProfileManager.callServiceDisconnectedListeners();
+ mIsProfileReady = false;
+ mCachedCallbackExecutorMap.clear();
+ }
+ };
- @Override
- public void onServiceDisconnected(int profile) {
- if (profile != BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT) {
- Log.d(TAG, "The profile is not LE_AUDIO_BROADCAST_ASSISTANT");
- return;
- }
- if (DEBUG) {
- Log.d(TAG, "Bluetooth service disconnected");
- }
- mProfileManager.callServiceDisconnectedListeners();
- mIsProfileReady = false;
- }
- };
-
- public LocalBluetoothLeBroadcastAssistant(Context context,
+ public LocalBluetoothLeBroadcastAssistant(
+ Context context,
CachedBluetoothDeviceManager deviceManager,
LocalBluetoothProfileManager profileManager) {
mProfileManager = profileManager;
mDeviceManager = deviceManager;
- BluetoothAdapter.getDefaultAdapter().
- getProfileProxy(context, mServiceListener,
- BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
+ BluetoothAdapter.getDefaultAdapter()
+ .getProfileProxy(
+ context, mServiceListener, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
mBuilder = new BluetoothLeBroadcastMetadata.Builder();
}
@@ -123,11 +142,11 @@
* @param sink Broadcast Sink to which the Broadcast Source should be added
* @param metadata Broadcast Source metadata to be added to the Broadcast Sink
* @param isGroupOp {@code true} if Application wants to perform this operation for all
- * coordinated set members throughout this session. Otherwise, caller
- * would have to add, modify, and remove individual set members.
+ * coordinated set members throughout this session. Otherwise, caller would have to add,
+ * modify, and remove individual set members.
*/
- public void addSource(BluetoothDevice sink, BluetoothLeBroadcastMetadata metadata,
- boolean isGroupOp) {
+ public void addSource(
+ BluetoothDevice sink, BluetoothLeBroadcastMetadata metadata, boolean isGroupOp) {
if (mService == null) {
Log.d(TAG, "The BluetoothLeBroadcastAssistant is null");
return;
@@ -140,36 +159,55 @@
* the qr code string.
*
* @param sink Broadcast Sink to which the Broadcast Source should be added
- * @param sourceAddressType hardware MAC Address of the device. See
- * {@link BluetoothDevice.AddressType}.
+ * @param sourceAddressType hardware MAC Address of the device. See {@link
+ * BluetoothDevice.AddressType}.
* @param presentationDelayMicros presentation delay of this Broadcast Source in microseconds.
* @param sourceAdvertisingSid 1-byte long Advertising_SID of the Broadcast Source.
* @param broadcastId 3-byte long Broadcast_ID of the Broadcast Source.
- * @param paSyncInterval Periodic Advertising Sync interval of the broadcast Source,
- * {@link BluetoothLeBroadcastMetadata#PA_SYNC_INTERVAL_UNKNOWN} if
- * unknown.
+ * @param paSyncInterval Periodic Advertising Sync interval of the broadcast Source, {@link
+ * BluetoothLeBroadcastMetadata#PA_SYNC_INTERVAL_UNKNOWN} if unknown.
* @param isEncrypted whether the Broadcast Source is encrypted.
* @param broadcastCode Broadcast Code for this Broadcast Source, null if code is not required.
* @param sourceDevice source advertiser address.
* @param isGroupOp {@code true} if Application wants to perform this operation for all
- * coordinated set members throughout this session. Otherwise, caller
- * would have to add, modify, and remove individual set members.
+ * coordinated set members throughout this session. Otherwise, caller would have to add,
+ * modify, and remove individual set members.
*/
- public void addSource(@NonNull BluetoothDevice sink, int sourceAddressType,
- int presentationDelayMicros, int sourceAdvertisingSid, int broadcastId,
- int paSyncInterval, boolean isEncrypted, byte[] broadcastCode,
- BluetoothDevice sourceDevice, boolean isGroupOp) {
+ public void addSource(
+ @NonNull BluetoothDevice sink,
+ int sourceAddressType,
+ int presentationDelayMicros,
+ int sourceAdvertisingSid,
+ int broadcastId,
+ int paSyncInterval,
+ boolean isEncrypted,
+ byte[] broadcastCode,
+ BluetoothDevice sourceDevice,
+ boolean isGroupOp) {
if (DEBUG) {
Log.d(TAG, "addSource()");
}
- buildMetadata(sourceAddressType, presentationDelayMicros, sourceAdvertisingSid, broadcastId,
- paSyncInterval, isEncrypted, broadcastCode, sourceDevice);
+ buildMetadata(
+ sourceAddressType,
+ presentationDelayMicros,
+ sourceAdvertisingSid,
+ broadcastId,
+ paSyncInterval,
+ isEncrypted,
+ broadcastCode,
+ sourceDevice);
addSource(sink, mBluetoothLeBroadcastMetadata, isGroupOp);
}
- private void buildMetadata(int sourceAddressType, int presentationDelayMicros,
- int sourceAdvertisingSid, int broadcastId, int paSyncInterval, boolean isEncrypted,
- byte[] broadcastCode, BluetoothDevice sourceDevice) {
+ private void buildMetadata(
+ int sourceAddressType,
+ int presentationDelayMicros,
+ int sourceAdvertisingSid,
+ int broadcastId,
+ int paSyncInterval,
+ boolean isEncrypted,
+ byte[] broadcastCode,
+ BluetoothDevice sourceDevice) {
mBluetoothLeBroadcastMetadata =
mBuilder.setSourceDevice(sourceDevice, sourceAddressType)
.setSourceAdvertisingSid(sourceAdvertisingSid)
@@ -223,10 +261,10 @@
/**
* Stops an ongoing search for nearby Broadcast Sources.
*
- * On success, {@link BluetoothLeBroadcastAssistant.Callback#onSearchStopped(int)} will be
- * called with reason code {@link BluetoothStatusCodes#REASON_LOCAL_APP_REQUEST}.
- * On failure, {@link BluetoothLeBroadcastAssistant.Callback#onSearchStopFailed(int)} will be
- * called with reason code
+ * <p>On success, {@link BluetoothLeBroadcastAssistant.Callback#onSearchStopped(int)} will be
+ * called with reason code {@link BluetoothStatusCodes#REASON_LOCAL_APP_REQUEST}. On failure,
+ * {@link BluetoothLeBroadcastAssistant.Callback#onSearchStopFailed(int)} will be called with
+ * reason code
*
* @throws IllegalStateException if callback was not registered
*/
@@ -245,8 +283,8 @@
* Get information about all Broadcast Sources that a Broadcast Sink knows about.
*
* @param sink Broadcast Sink from which to get all Broadcast Sources
- * @return the list of Broadcast Receive State {@link BluetoothLeBroadcastReceiveState}
- * stored in the Broadcast Sink
+ * @return the list of Broadcast Receive State {@link BluetoothLeBroadcastReceiveState} stored
+ * in the Broadcast Sink
* @throws NullPointerException when <var>sink</var> is null
*/
public @NonNull List<BluetoothLeBroadcastReceiveState> getAllSources(
@@ -261,24 +299,50 @@
return mService.getAllSources(sink);
}
- public void registerServiceCallBack(@NonNull @CallbackExecutor Executor executor,
+ /**
+ * Register Broadcast Assistant Callbacks to track its state and receivers
+ *
+ * @param executor Executor object for callback
+ * @param callback Callback object to be registered
+ */
+ public void registerServiceCallBack(
+ @NonNull @CallbackExecutor Executor executor,
@NonNull BluetoothLeBroadcastAssistant.Callback callback) {
if (mService == null) {
- Log.d(TAG, "The BluetoothLeBroadcast is null.");
+ Log.d(
+ TAG,
+ "registerServiceCallBack failed, the BluetoothLeBroadcastAssistant is null.");
+ mCachedCallbackExecutorMap.putIfAbsent(callback, executor);
return;
}
- mService.registerCallback(executor, callback);
+ try {
+ mService.registerCallback(executor, callback);
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "registerServiceCallBack failed. " + e.getMessage());
+ }
}
+ /**
+ * Unregister previously registered Broadcast Assistant Callbacks
+ *
+ * @param callback Callback object to be unregistered
+ */
public void unregisterServiceCallBack(
@NonNull BluetoothLeBroadcastAssistant.Callback callback) {
+ mCachedCallbackExecutorMap.remove(callback);
if (mService == null) {
- Log.d(TAG, "The BluetoothLeBroadcast is null.");
+ Log.d(
+ TAG,
+ "unregisterServiceCallBack failed, the BluetoothLeBroadcastAssistant is null.");
return;
}
- mService.unregisterCallback(callback);
+ try {
+ mService.unregisterCallback(callback);
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "unregisterServiceCallBack failed. " + e.getMessage());
+ }
}
public boolean isProfileReady() {
@@ -310,9 +374,11 @@
return new ArrayList<BluetoothDevice>(0);
}
return mService.getDevicesMatchingConnectionStates(
- new int[]{BluetoothProfile.STATE_CONNECTED,
- BluetoothProfile.STATE_CONNECTING,
- BluetoothProfile.STATE_DISCONNECTING});
+ new int[] {
+ BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTING
+ });
}
public boolean isEnabled(BluetoothDevice device) {
@@ -373,9 +439,8 @@
}
if (mService != null) {
try {
- BluetoothAdapter.getDefaultAdapter().closeProfileProxy(
- BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT,
- mService);
+ BluetoothAdapter.getDefaultAdapter()
+ .closeProfileProxy(BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT, mService);
mService = null;
} catch (Throwable t) {
Log.w(TAG, "Error cleaning up LeAudio proxy", t);
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
index 009f8bb..e538e09 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
@@ -175,11 +175,7 @@
canOverflow: Boolean,
): T {
val state = layoutImpl.state.transitionState
- if (
- state !is TransitionState.Transition ||
- state.fromScene == state.toScene ||
- !layoutImpl.isTransitionReady(state)
- ) {
+ if (state !is TransitionState.Transition || !layoutImpl.isTransitionReady(state)) {
return sharedValue.value
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
index 199832b..de69c37 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
@@ -47,12 +47,6 @@
when (state) {
is TransitionState.Idle -> animate(layoutImpl, target)
is TransitionState.Transition -> {
- if (state.toScene == state.fromScene) {
- // Same as idle.
- animate(layoutImpl, target)
- return
- }
-
// A transition is currently running: first check whether `transition.toScene` or
// `transition.fromScene` is the same as our target scene, in which case the transition
// can be accelerated or reversed to end up in the target state.
@@ -153,13 +147,13 @@
}
private class OneOffTransition(
- override val fromScene: SceneKey,
- override val toScene: SceneKey,
+ fromScene: SceneKey,
+ toScene: SceneKey,
override val currentScene: SceneKey,
override val isInitiatedByUserInput: Boolean,
override val isUserInputOngoing: Boolean,
private val animatable: Animatable<Float, AnimationVector1D>,
-) : TransitionState.Transition {
+) : TransitionState.Transition(fromScene, toScene) {
override val progress: Float
get() = animatable.value
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index 31604a6..431a8ae 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -288,7 +288,6 @@
// Always draw the element if there is no ongoing transition or if the element is not shared.
if (
state !is TransitionState.Transition ||
- state.fromScene == state.toScene ||
!layoutImpl.isTransitionReady(state) ||
state.fromScene !in element.sceneValues ||
state.toScene !in element.sceneValues
@@ -374,7 +373,7 @@
): Boolean {
val state = layoutImpl.state.transitionState
- if (state !is TransitionState.Transition || state.fromScene == state.toScene) {
+ if (state !is TransitionState.Transition) {
return true
}
@@ -611,7 +610,7 @@
val state = layoutImpl.state.transitionState
// There is no ongoing transition.
- if (state !is TransitionState.Transition || state.fromScene == state.toScene) {
+ if (state !is TransitionState.Transition) {
// Even if this element SceneTransitionLayout is not animated, the layout itself might be
// animated (e.g. by another parent SceneTransitionLayout), in which case this element still
// need to participate in the layout phase.
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
index fa385d0..7029da2 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
@@ -131,10 +131,6 @@
val fromScene = (transitionState as TransitionState.Transition).fromScene
val toScene = transitionState.toScene
- if (fromScene == toScene) {
- check(fromScene == scene)
- return true
- }
val fromReady = layoutImpl.isSceneReady(fromScene)
val toReady = layoutImpl.isSceneReady(toScene)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
index 1b79dbd..983cff8 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
@@ -73,17 +73,13 @@
when (val state = transitionState) {
is TransitionState.Idle -> ObservableTransitionState.Idle(state.currentScene)
is TransitionState.Transition -> {
- if (state.fromScene == state.toScene) {
- ObservableTransitionState.Idle(state.currentScene)
- } else {
- ObservableTransitionState.Transition(
- fromScene = state.fromScene,
- toScene = state.toScene,
- progress = snapshotFlow { state.progress },
- isInitiatedByUserInput = state.isInitiatedByUserInput,
- isUserInputOngoing = snapshotFlow { state.isUserInputOngoing },
- )
- }
+ ObservableTransitionState.Transition(
+ fromScene = state.fromScene,
+ toScene = state.toScene,
+ progress = snapshotFlow { state.progress },
+ isInitiatedByUserInput = state.isInitiatedByUserInput,
+ isUserInputOngoing = snapshotFlow { state.isUserInputOngoing },
+ )
}
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
index 03f37d0..91decf4 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
@@ -49,8 +49,12 @@
layoutImpl.state.transitionState = value
}
- internal var swipeTransition: SwipeTransition = SwipeTransition(currentScene, currentScene, 1f)
- private set
+ private var _swipeTransition: SwipeTransition? = null
+ internal var swipeTransition: SwipeTransition
+ get() = _swipeTransition ?: error("SwipeTransition needs to be initialized")
+ set(value) {
+ _swipeTransition = value
+ }
private fun updateTransition(newTransition: SwipeTransition, force: Boolean = false) {
if (isDrivingTransition || force) transitionState = newTransition
@@ -61,7 +65,7 @@
get() = layoutImpl.scene(transitionState.currentScene)
internal val isDrivingTransition
- get() = transitionState == swipeTransition
+ get() = transitionState == _swipeTransition
/**
* The velocity threshold at which the intent of the user is to swipe up or down. It is the same
@@ -82,12 +86,15 @@
private var actionDownOrRight: UserAction? = null
private var actionUpOrLeftNoEdge: UserAction? = null
private var actionDownOrRightNoEdge: UserAction? = null
+ private var upOrLeftScene: SceneKey? = null
+ private var downOrRightScene: SceneKey? = null
internal fun onDragStarted(pointersDown: Int, startedPosition: Offset?, overSlop: Float) {
if (isDrivingTransition) {
// This [transition] was already driving the animation: simply take over it.
// Stop animating and start from where the current offset.
swipeTransition.cancelOffsetAnimation()
+ updateTargetScenes(swipeTransition._fromScene)
return
}
@@ -105,11 +112,8 @@
val fromScene = currentScene
setCurrentActions(fromScene, startedPosition, pointersDown)
- if (fromScene.upOrLeft() == null && fromScene.downOrRight() == null) {
- return
- }
-
- val (targetScene, distance) = fromScene.findTargetSceneAndDistance(overSlop)
+ val (targetScene, distance) =
+ findTargetSceneAndDistance(fromScene, overSlop, updateScenes = true) ?: return
updateTransition(SwipeTransition(fromScene, targetScene, distance), force = true)
}
@@ -179,16 +183,21 @@
val (fromScene, acceleratedOffset) =
computeFromSceneConsideringAcceleratedSwipe(swipeTransition)
+
+ val isNewFromScene = fromScene.key != swipeTransition.fromScene
+ val (targetScene, distance) =
+ findTargetSceneAndDistance(
+ fromScene,
+ swipeTransition.dragOffset,
+ updateScenes = isNewFromScene,
+ )
+ ?: run {
+ onDragStopped(delta, true)
+ return
+ }
swipeTransition.dragOffset += acceleratedOffset
- // Compute the target scene depending on the current offset.
- val (targetScene, distance) =
- fromScene.findTargetSceneAndDistance(swipeTransition.dragOffset)
-
- // TODO(b/290184746): support long scroll A => B => C? especially for non fullscreen scenes
- if (
- fromScene.key != swipeTransition.fromScene || targetScene.key != swipeTransition.toScene
- ) {
+ if (isNewFromScene || targetScene.key != swipeTransition.toScene) {
updateTransition(
SwipeTransition(fromScene, targetScene, distance).apply {
this.dragOffset = swipeTransition.dragOffset
@@ -197,6 +206,11 @@
}
}
+ private fun updateTargetScenes(fromScene: Scene) {
+ upOrLeftScene = fromScene.upOrLeft()
+ downOrRightScene = fromScene.downOrRight()
+ }
+
/**
* Change fromScene in the case where the user quickly swiped multiple times in the same
* direction to accelerate the transition from A => B then B => C.
@@ -214,37 +228,71 @@
val absoluteDistance = swipeTransition.distance.absoluteValue
// If the swipe was not committed, don't do anything.
- if (fromScene == toScene || swipeTransition._currentScene != toScene) {
+ if (swipeTransition._currentScene != toScene) {
return Pair(fromScene, 0f)
}
// If the offset is past the distance then let's change fromScene so that the user can swipe
// to the next screen or go back to the previous one.
val offset = swipeTransition.dragOffset
- return if (offset <= -absoluteDistance && fromScene.upOrLeft() == toScene.key) {
+ return if (offset <= -absoluteDistance && upOrLeftScene == toScene.key) {
Pair(toScene, absoluteDistance)
- } else if (offset >= absoluteDistance && fromScene.downOrRight() == toScene.key) {
+ } else if (offset >= absoluteDistance && downOrRightScene == toScene.key) {
Pair(toScene, -absoluteDistance)
} else {
Pair(fromScene, 0f)
}
}
- // TODO(b/290184746): there are two bugs here:
- // 1. if both upOrLeft and downOrRight become `null` during a transition this will crash
- // 2. if one of them changes during a transition, the transition will jump cut to the new target
- private inline fun Scene.findTargetSceneAndDistance(
- directionOffset: Float
- ): Pair<Scene, Float> {
- val upOrLeft = upOrLeft()
- val downOrRight = downOrRight()
- val absoluteDistance = getAbsoluteDistance()
+ /**
+ * Returns the target scene and distance from [fromScene] in the direction [directionOffset].
+ *
+ * @param fromScene the scene from which we look for the target
+ * @param directionOffset signed float that indicates the direction. Positive is down or right
+ * negative is up or left.
+ * @param updateScenes whether the target scenes should be updated to the current values held in
+ * the Scenes map. Usually we don't want to update them while doing a drag, because this could
+ * change the target scene (jump cutting) to a different scene, when some system state changed
+ * the targets the background. However, an update is needed any time we calculate the targets
+ * for a new fromScene.
+ * @return null when there are no targets in either direction. If one direction is null and you
+ * drag into the null direction this function will return the opposite direction, assuming
+ * that the users intention is to start the drag into the other direction eventually. If
+ * [directionOffset] is 0f and both direction are available, it will default to
+ * [upOrLeftScene].
+ */
+ private inline fun findTargetSceneAndDistance(
+ fromScene: Scene,
+ directionOffset: Float,
+ updateScenes: Boolean,
+ ): Pair<Scene, Float>? {
+ if (updateScenes) updateTargetScenes(fromScene)
+ val absoluteDistance = fromScene.getAbsoluteDistance()
// Compute the target scene depending on the current offset.
- return if ((directionOffset < 0f && upOrLeft != null) || downOrRight == null) {
- Pair(layoutImpl.scene(upOrLeft!!), -absoluteDistance)
- } else {
- Pair(layoutImpl.scene(downOrRight), absoluteDistance)
+ return when {
+ upOrLeftScene == null && downOrRightScene == null -> null
+ (directionOffset < 0f && upOrLeftScene != null) || downOrRightScene == null ->
+ Pair(layoutImpl.scene(upOrLeftScene!!), -absoluteDistance)
+ else -> Pair(layoutImpl.scene(downOrRightScene!!), absoluteDistance)
+ }
+ }
+
+ /**
+ * A strict version of [findTargetSceneAndDistance] that will return null when there is no Scene
+ * in [directionOffset] direction
+ */
+ private inline fun findTargetSceneAndDistanceStrict(
+ fromScene: Scene,
+ directionOffset: Float,
+ ): Pair<Scene, Float>? {
+ val absoluteDistance = fromScene.getAbsoluteDistance()
+ return when {
+ directionOffset > 0f ->
+ upOrLeftScene?.let { Pair(layoutImpl.scene(it), -absoluteDistance) }
+ directionOffset < 0f ->
+ downOrRightScene?.let { Pair(layoutImpl.scene(it), absoluteDistance) }
+ else -> null
}
}
@@ -311,20 +359,21 @@
val startFromIdlePosition = swipeTransition.dragOffset == 0f
if (startFromIdlePosition) {
- // If there is a next scene, we start the overscroll animation.
- val (targetScene, distance) = fromScene.findTargetSceneAndDistance(velocity)
- val isValidTarget = distance != 0f && targetScene.key != fromScene.key
- if (isValidTarget) {
- updateTransition(
- SwipeTransition(fromScene, targetScene, distance).apply {
- _currentScene = swipeTransition._currentScene
+ // If there is a target scene, we start the overscroll animation.
+ val (targetScene, distance) =
+ findTargetSceneAndDistanceStrict(fromScene, velocity)
+ ?: run {
+ // We will not animate
+ transitionState = TransitionState.Idle(fromScene.key)
+ return
}
- )
- animateTo(targetScene = fromScene, targetOffset = 0f)
- } else {
- // We will not animate
- transitionState = TransitionState.Idle(fromScene.key)
- }
+
+ updateTransition(
+ SwipeTransition(fromScene, targetScene, distance).apply {
+ _currentScene = swipeTransition._currentScene
+ }
+ )
+ animateTo(targetScene = fromScene, targetOffset = 0f)
} else {
// We were between two scenes: animate to the initial scene.
animateTo(targetScene = fromScene, targetOffset = 0f)
@@ -410,15 +459,11 @@
* above or to the left of [toScene].
*/
val distance: Float
- ) : TransitionState.Transition {
+ ) : TransitionState.Transition(_fromScene.key, _toScene.key) {
var _currentScene by mutableStateOf(_fromScene)
override val currentScene: SceneKey
get() = _currentScene.key
- override val fromScene: SceneKey = _fromScene.key
-
- override val toScene: SceneKey = _toScene.key
-
override val progress: Float
get() {
val offset = if (isAnimatingOffset) offsetAnimatable.value else dragOffset
@@ -494,9 +539,9 @@
}
internal class SceneNestedScrollHandler(
- private val gestureHandler: SceneGestureHandler,
- private val topOrLeftBehavior: NestedScrollBehavior,
- private val bottomOrRightBehavior: NestedScrollBehavior,
+ private val gestureHandler: SceneGestureHandler,
+ private val topOrLeftBehavior: NestedScrollBehavior,
+ private val bottomOrRightBehavior: NestedScrollBehavior,
) : NestedScrollHandler {
override val connection: PriorityNestedScrollConnection = nestedScrollConnection()
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 02ddccb..00e33e2 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -172,7 +172,7 @@
val width: Int
val height: Int
val state = state.transitionState
- if (state !is TransitionState.Transition || state.fromScene == state.toScene) {
+ if (state !is TransitionState.Transition) {
width = placeable.width
height = placeable.height
} else {
@@ -232,10 +232,7 @@
is TransitionState.Idle -> drawContent()
is TransitionState.Transition -> {
// Don't draw scenes that are not ready yet.
- if (
- readyScenes.containsKey(key) ||
- state.fromScene == state.toScene
- ) {
+ if (readyScenes.containsKey(key)) {
drawContent()
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index f48e914..62372558 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -39,11 +39,6 @@
fun isTransitioning(from: SceneKey? = null, to: SceneKey? = null): Boolean {
val transition = transitionState as? TransitionState.Transition ?: return false
- // TODO(b/310915136): Remove this check.
- if (transition.fromScene == transition.toScene) {
- return false
- }
-
return (from == null || transition.fromScene == from) &&
(to == null || transition.toScene == to)
}
@@ -71,32 +66,30 @@
/** No transition/animation is currently running. */
data class Idle(override val currentScene: SceneKey) : TransitionState
- /**
- * There is a transition animating between two scenes.
- *
- * Important note: [fromScene] and [toScene] might be the same, in which case this [Transition]
- * should be treated the same as [Idle]. This is designed on purpose so that a [Transition] can
- * be started without knowing in advance where it is transitioning to, making the logic of
- * [swipeToScene] easier to reason about.
- */
- interface Transition : TransitionState {
- /** The scene this transition is starting from. */
- val fromScene: SceneKey
+ /** There is a transition animating between two scenes. */
+ abstract class Transition(
+ /** The scene this transition is starting from. Can't be the same as toScene */
+ val fromScene: SceneKey,
- /** The scene this transition is going to. */
+ /** The scene this transition is going to. Can't be the same as fromScene */
val toScene: SceneKey
+ ) : TransitionState {
+
+ init {
+ check(fromScene != toScene)
+ }
/**
* The progress of the transition. This is usually in the `[0; 1]` range, but it can also be
* less than `0` or greater than `1` when using transitions with a spring AnimationSpec or
* when flinging quickly during a swipe gesture.
*/
- val progress: Float
+ abstract val progress: Float
/** Whether the transition was triggered by user input rather than being programmatic. */
- val isInitiatedByUserInput: Boolean
+ abstract val isInitiatedByUserInput: Boolean
/** Whether user input is currently driving the transition. */
- val isUserInputOngoing: Boolean
+ abstract val isUserInputOngoing: Boolean
}
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
index 34afc4c..e6224df 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
@@ -58,16 +58,22 @@
private val layoutState: SceneTransitionLayoutState =
SceneTransitionLayoutState(internalCurrentScene)
+ val mutableUserActionsA: MutableMap<UserAction, SceneKey> =
+ mutableMapOf(Swipe.Up to SceneB, Swipe.Down to SceneC)
+
+ val mutableUserActionsB: MutableMap<UserAction, SceneKey> =
+ mutableMapOf(Swipe.Up to SceneC, Swipe.Down to SceneA)
+
private val scenesBuilder: SceneTransitionLayoutScope.() -> Unit = {
scene(
key = SceneA,
- userActions = mapOf(Swipe.Up to SceneB, Swipe.Down to SceneC),
+ userActions = mutableUserActionsA,
) {
Text("SceneA")
}
scene(
key = SceneB,
- userActions = mapOf(Swipe.Up to SceneC, Swipe.Down to SceneA),
+ userActions = mutableUserActionsB,
) {
Text("SceneB")
}
@@ -412,6 +418,70 @@
}
@Test
+ fun onAccelaratedScrollBothTargetsBecomeNull_settlesToIdle() = runGestureTest {
+ draggable.onDragStarted()
+ draggable.onDelta(up(0.2f))
+
+ draggable.onDelta(up(0.2f))
+ draggable.onDragStopped(velocity = -velocityThreshold)
+ assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
+
+ mutableUserActionsA.remove(Swipe.Up)
+ mutableUserActionsA.remove(Swipe.Down)
+ mutableUserActionsB.remove(Swipe.Up)
+ mutableUserActionsB.remove(Swipe.Down)
+
+ // start accelaratedScroll and scroll over to B -> null
+ draggable.onDragStarted()
+ draggable.onDelta(up(0.5f))
+ draggable.onDelta(up(0.5f))
+
+ // here onDragStopped is already triggered, but subsequent onDelta/onDragStopped calls may
+ // still be called. Make sure that they don't crash or change the scene
+ draggable.onDelta(up(0.5f))
+ draggable.onDragStopped(0f)
+
+ advanceUntilIdle()
+ assertIdle(SceneB)
+
+ // These events can still come in after the animation has settled
+ draggable.onDelta(up(0.5f))
+ draggable.onDragStopped(0f)
+ assertIdle(SceneB)
+ }
+
+ @Test
+ fun onDragTargetsChanged_targetStaysTheSame() = runGestureTest {
+ draggable.onDragStarted(up(0.1f))
+ assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
+
+ mutableUserActionsA[Swipe.Up] = SceneC
+ draggable.onDelta(up(0.1f))
+ // target stays B even though UserActions changed
+ assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.2f)
+ draggable.onDragStopped(down(0.1f))
+ advanceUntilIdle()
+
+ // now target changed to C for new drag
+ draggable.onDragStarted(up(0.1f))
+ assertTransition(fromScene = SceneA, toScene = SceneC, progress = 0.1f)
+ }
+
+ @Test
+ fun onDragTargetsChanged_targetsChangeWhenStartingNewDrag() = runGestureTest {
+ draggable.onDragStarted(up(0.1f))
+ assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
+
+ mutableUserActionsA[Swipe.Up] = SceneC
+ draggable.onDelta(up(0.1f))
+ draggable.onDragStopped(down(0.1f))
+
+ // now target changed to C for new drag that started before previous drag settled to Idle
+ draggable.onDragStarted(up(0.1f))
+ assertTransition(fromScene = SceneA, toScene = SceneC, progress = 0.3f)
+ }
+
+ @Test
fun startGestureDuringAnimatingOffset_shouldImmediatelyStopTheAnimation() = runGestureTest {
draggable.onDragStarted()
assertTransition(currentScene = SceneA)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index 94c51ca..eeda8d46 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -39,18 +39,6 @@
}
@Test
- fun isTransitioningTo_fromSceneEqualToToScene() {
- val state = SceneTransitionLayoutState(TestScenes.SceneA)
- state.transitionState = transition(from = TestScenes.SceneA, to = TestScenes.SceneA)
-
- assertThat(state.isTransitioning()).isFalse()
- assertThat(state.isTransitioning(from = TestScenes.SceneA)).isFalse()
- assertThat(state.isTransitioning(to = TestScenes.SceneB)).isFalse()
- assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB))
- .isFalse()
- }
-
- @Test
fun isTransitioningTo_transition() {
val state = SceneTransitionLayoutState(TestScenes.SceneA)
state.transitionState = transition(from = TestScenes.SceneA, to = TestScenes.SceneB)
@@ -64,10 +52,8 @@
}
private fun transition(from: SceneKey, to: SceneKey): TransitionState.Transition {
- return object : TransitionState.Transition {
+ return object : TransitionState.Transition(from, to) {
override val currentScene: SceneKey = from
- override val fromScene: SceneKey = from
- override val toScene: SceneKey = to
override val progress: Float = 0f
override val isInitiatedByUserInput: Boolean = false
override val isUserInputOngoing: Boolean = false
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 45aca17..d6d5b23 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -827,25 +827,6 @@
}
@Test
- fun isAuthenticatedIsResetToFalseWhenKeyguardIsGoingAway() =
- testScope.runTest {
- initCollectors()
- allPreconditionsToRunFaceAuthAreTrue()
-
- triggerFaceAuth(false)
-
- authenticationCallback.value.onAuthenticationSucceeded(
- mock(FaceManager.AuthenticationResult::class.java)
- )
-
- assertThat(authenticated()).isTrue()
-
- keyguardRepository.setKeyguardGoingAway(true)
-
- assertThat(authenticated()).isFalse()
- }
-
- @Test
fun isAuthenticatedIsResetToFalseWhenDeviceStartsGoingToSleep() =
testScope.runTest {
initCollectors()
@@ -906,6 +887,25 @@
}
@Test
+ fun isAuthenticatedIsResetToFalseWhenKeyguardDoneAnimationsFinished() =
+ testScope.runTest {
+ initCollectors()
+ allPreconditionsToRunFaceAuthAreTrue()
+
+ triggerFaceAuth(false)
+
+ authenticationCallback.value.onAuthenticationSucceeded(
+ mock(FaceManager.AuthenticationResult::class.java)
+ )
+
+ assertThat(authenticated()).isTrue()
+
+ keyguardRepository.keyguardDoneAnimationsFinished()
+
+ assertThat(authenticated()).isFalse()
+ }
+
+ @Test
fun detectDoesNotRunWhenFaceIsNotUsuallyAllowed() =
testScope.runTest {
testGatingCheckForDetect {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManagerTest.kt
new file mode 100644
index 0000000..e850456
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManagerTest.kt
@@ -0,0 +1,40 @@
+package com.android.systemui.keyguard.ui.preview
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardRemotePreviewManagerTest : SysuiTestCase() {
+
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ @Test
+ fun onDestroy_clearsReferencesToRenderer() =
+ testScope.runTest {
+ val renderer = mock<KeyguardPreviewRenderer>()
+ val onDestroy: (PreviewLifecycleObserver) -> Unit = {}
+
+ val observer = PreviewLifecycleObserver(this, testDispatcher, renderer, onDestroy)
+
+ // Precondition check.
+ assertThat(observer.renderer).isNotNull()
+ assertThat(observer.onDestroy).isNotNull()
+
+ observer.onDestroy()
+
+ // The verification checks renderer/requestDestruction lambda because they-re
+ // non-singletons which can't leak KeyguardPreviewRenderer.
+ assertThat(observer.renderer).isNull()
+ assertThat(observer.onDestroy).isNull()
+ }
+}
diff --git a/packages/SystemUI/res/drawable/arrow_pointing_down.xml b/packages/SystemUI/res/drawable/arrow_pointing_down.xml
new file mode 100644
index 0000000..be39683
--- /dev/null
+++ b/packages/SystemUI/res/drawable/arrow_pointing_down.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ 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:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M5.41,7.59L4,9l8,8 8,-8 -1.41,-1.41L12,14.17" />
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/record_issue_dialog.xml b/packages/SystemUI/res/layout/record_issue_dialog.xml
new file mode 100644
index 0000000..53ad9f1
--- /dev/null
+++ b/packages/SystemUI/res/layout/record_issue_dialog.xml
@@ -0,0 +1,81 @@
+<?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.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:orientation="vertical" >
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
+ android:text="@string/qs_record_issue_dropdown_header" />
+
+ <Button
+ android:id="@+id/issue_type_button"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/qs_record_issue_dropdown_prompt"
+ android:lines="1"
+ android:drawableRight="@drawable/arrow_pointing_down"
+ android:layout_marginTop="@dimen/qqs_layout_margin_top"
+ android:focusable="false"
+ android:clickable="true" />
+
+ <!-- Screen Record Switch -->
+ <LinearLayout
+ android:id="@+id/screenrecord_switch_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/qqs_layout_margin_top"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:layout_width="@dimen/screenrecord_option_icon_size"
+ android:layout_height="@dimen/screenrecord_option_icon_size"
+ android:layout_weight="0"
+ android:src="@drawable/ic_screenrecord"
+ app:tint="?androidprv:attr/materialColorOnSurface"
+ android:layout_gravity="center"
+ android:layout_marginEnd="@dimen/screenrecord_option_padding" />
+
+ <TextView
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/screenrecord_option_icon_size"
+ android:layout_weight="1"
+ android:layout_gravity="fill_vertical"
+ android:gravity="center"
+ android:text="@string/quick_settings_screen_record_label"
+ android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
+ android:importantForAccessibility="no"/>
+
+ <Switch
+ android:id="@+id/screenrecord_switch"
+ android:layout_width="wrap_content"
+ android:minHeight="@dimen/screenrecord_option_icon_size"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:layout_gravity="fill_vertical"
+ android:layout_weight="0"
+ android:contentDescription="@string/quick_settings_screen_record_label" />
+ </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 13d2fea..e10925d 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -831,6 +831,20 @@
<!-- QuickSettings: Text to prompt the user to stop an ongoing recording [CHAR LIMIT=20] -->
<string name="qs_record_issue_stop">Stop</string>
+ <!-- QuickSettings: Issue Type Drop down options in Record Issue Start Dialog [CHAR LIMIT=50] -->
+ <string name="qs_record_issue_dropdown_header">What part of your device experience was affected?</string>
+ <!-- QuickSettings: Issue Type Drop down prompt in Record Issue Start Dialog [CHAR LIMIT=30] -->
+ <string name="qs_record_issue_dropdown_prompt">Select issue type</string>
+ <!-- QuickSettings: Screen record switch label in Record Issue Start Dialog [CHAR LIMIT=20] -->
+ <string name="qs_record_issue_dropdown_screenrecord">Screen record</string>
+
+ <!-- QuickSettings: Issue Type Drop down choices list in Record Issue Start Dialog [CHAR LIMIT=30] -->
+ <string-array name="qs_record_issue_types">
+ <item>Performance</item>
+ <item>User Interface</item>
+ <item>Battery</item>
+ </string-array>
+
<!-- QuickSettings: Label for the toggle that controls whether One-handed mode is enabled. [CHAR LIMIT=NONE] -->
<string name="quick_settings_onehanded_label">One-handed mode</string>
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index b7260f2..57f3b70 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -141,6 +141,7 @@
import com.android.systemui.flags.Flags;
import com.android.systemui.flags.SystemPropertiesHelper;
import com.android.systemui.keyguard.dagger.KeyguardModule;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
import com.android.systemui.log.SessionTracker;
@@ -1319,6 +1320,7 @@
private DeviceConfigProxy mDeviceConfig;
private DozeParameters mDozeParameters;
private SelectedUserInteractor mSelectedUserInteractor;
+ private KeyguardInteractor mKeyguardInteractor;
private final KeyguardStateController mKeyguardStateController;
private final KeyguardStateController.Callback mKeyguardStateControllerCallback =
@@ -1400,7 +1402,8 @@
Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel,
SystemPropertiesHelper systemPropertiesHelper,
Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager,
- SelectedUserInteractor selectedUserInteractor) {
+ SelectedUserInteractor selectedUserInteractor,
+ KeyguardInteractor keyguardInteractor) {
mContext = context;
mUserTracker = userTracker;
mFalsingCollector = falsingCollector;
@@ -1441,6 +1444,7 @@
}));
mDozeParameters = dozeParameters;
mSelectedUserInteractor = selectedUserInteractor;
+ mKeyguardInteractor = keyguardInteractor;
mStatusBarStateController = statusBarStateController;
statusBarStateController.addCallback(this);
@@ -2618,6 +2622,7 @@
setPendingLock(false); // user may have authenticated during the screen off animation
handleHide();
+ mKeyguardInteractor.keyguardDoneAnimationsFinished();
mUpdateMonitor.clearFingerprintRecognizedWhenKeyguardDone(currentUser);
Trace.endSection();
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 331d892..3925dd1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -52,6 +52,7 @@
import com.android.systemui.keyguard.data.quickaffordance.KeyguardDataQuickAffordanceModule;
import com.android.systemui.keyguard.data.repository.KeyguardFaceAuthModule;
import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionModule;
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger;
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLoggerImpl;
@@ -154,7 +155,8 @@
Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel,
SystemPropertiesHelper systemPropertiesHelper,
Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager,
- SelectedUserInteractor selectedUserInteractor) {
+ SelectedUserInteractor selectedUserInteractor,
+ KeyguardInteractor keyguardInteractor) {
return new KeyguardViewMediator(
context,
uiEventLogger,
@@ -199,7 +201,8 @@
dreamingToLockscreenTransitionViewModel,
systemPropertiesHelper,
wmLockscreenVisibilityManager,
- selectedUserInteractor);
+ selectedUserInteractor,
+ keyguardInteractor);
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index eceaf6c..4d60dd0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -187,7 +187,8 @@
faceManager?.sensorPropertiesInternal?.firstOrNull()?.supportsFaceDetection ?: false
private val _isAuthRunning = MutableStateFlow(false)
- override val isAuthRunning: StateFlow<Boolean> = _isAuthRunning
+ override val isAuthRunning: StateFlow<Boolean>
+ get() = _isAuthRunning
private val keyguardSessionId: InstanceId?
get() = sessionTracker.getSessionId(StatusBarManager.SESSION_KEYGUARD)
@@ -253,13 +254,6 @@
)
.andAllFlows("canFaceAuthRun", faceAuthLog)
.flowOn(backgroundDispatcher)
- .onEach {
- faceAuthLogger.canFaceAuthRunChanged(it)
- if (!it) {
- // Cancel currently running auth if any of the gating checks are false.
- cancel()
- }
- }
.stateIn(applicationScope, SharingStarted.Eagerly, false)
// Face detection can run only when lockscreen bypass is enabled
@@ -287,12 +281,9 @@
)
.andAllFlows("canFaceDetectRun", faceDetectLog)
.flowOn(backgroundDispatcher)
- .onEach {
- if (!it) {
- cancelDetection()
- }
- }
.stateIn(applicationScope, SharingStarted.Eagerly, false)
+ observeFaceAuthGatingChecks()
+ observeFaceDetectGatingChecks()
observeFaceAuthResettingConditions()
listenForSchedulingWatchdog()
processPendingAuthRequests()
@@ -313,14 +304,14 @@
}
private fun observeFaceAuthResettingConditions() {
- // Clear auth status when keyguard is going away or when the user is switching or device
- // starts going to sleep.
+ // Clear auth status when keyguard done animations finished or when the user is switching
+ // or device starts going to sleep.
merge(
powerInteractor.isAsleep,
if (featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
keyguardTransitionInteractor.isInTransitionToState(KeyguardState.GONE)
} else {
- keyguardRepository.isKeyguardGoingAway
+ keyguardRepository.keyguardDoneAnimationsFinished.map { true }
},
userRepository.selectedUser.map {
it.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS
@@ -347,6 +338,17 @@
pendingAuthenticateRequest.value = null
}
+ private fun observeFaceDetectGatingChecks() {
+ canRunDetection
+ .onEach {
+ if (!it) {
+ cancelDetection()
+ }
+ }
+ .flowOn(mainDispatcher)
+ .launchIn(applicationScope)
+ }
+
private fun isUdfps() =
deviceEntryFingerprintAuthRepository.availableFpSensorType.map {
it == BiometricType.UNDER_DISPLAY_FINGERPRINT
@@ -405,6 +407,20 @@
)
}
+ private fun observeFaceAuthGatingChecks() {
+ canRunFaceAuth
+ .onEach {
+ faceAuthLogger.canFaceAuthRunChanged(it)
+ if (!it) {
+ // Cancel currently running auth if any of the gating checks are false.
+ faceAuthLogger.cancellingFaceAuth()
+ cancel()
+ }
+ }
+ .flowOn(mainDispatcher)
+ .launchIn(applicationScope)
+ }
+
private val faceAuthCallback =
object : FaceManager.AuthenticationCallback() {
override fun onAuthenticationFailed() {
@@ -539,7 +555,7 @@
authenticate(it.uiEvent, it.fallbackToDetection)
}
}
- .flowOn(backgroundDispatcher)
+ .flowOn(mainDispatcher)
.launchIn(applicationScope)
}
@@ -635,7 +651,6 @@
override fun cancel() {
if (authCancellationSignal == null) return
- faceAuthLogger.cancellingFaceAuth()
authCancellationSignal?.cancel()
cancelNotReceivedHandlerJob?.cancel()
cancelNotReceivedHandlerJob =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 0df7f9b..31ef100 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -192,6 +192,17 @@
/** Observable updated when keyguardDone should be called either now or soon. */
val keyguardDone: Flow<KeyguardDone>
+ /**
+ * Emits after the keyguard is done animating away.
+ *
+ * TODO(b/278086361): Remove once KEYGUARD_WM_STATE_REFACTOR flag is removed.
+ */
+ @Deprecated(
+ "Use KeyguardTransitionInteractor flows instead. The closest match for " +
+ "'keyguardDoneAnimationsFinished' is when the GONE transition is finished."
+ )
+ val keyguardDoneAnimationsFinished: Flow<Unit>
+
/** Receive whether clock should be centered on lockscreen. */
val clockShouldBeCentered: Flow<Boolean>
@@ -239,6 +250,17 @@
suspend fun setKeyguardDone(keyguardDoneType: KeyguardDone)
fun setClockShouldBeCentered(shouldBeCentered: Boolean)
+
+ /**
+ * Updates signal that the keyguard done animations are finished
+ *
+ * TODO(b/278086361): Remove once KEYGUARD_WM_STATE_REFACTOR flag is removed.
+ */
+ @Deprecated(
+ "Use KeyguardTransitionInteractor flows instead. The closest match for " +
+ "'keyguardDoneAnimationsFinished' is when the GONE transition is finished."
+ )
+ fun keyguardDoneAnimationsFinished()
}
/** Encapsulates application state for the keyguard. */
@@ -269,6 +291,11 @@
_keyguardDone.emit(keyguardDoneType)
}
+ override val keyguardDoneAnimationsFinished: MutableSharedFlow<Unit> = MutableSharedFlow()
+ override fun keyguardDoneAnimationsFinished() {
+ keyguardDoneAnimationsFinished.tryEmit(Unit)
+ }
+
private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
override val animateBottomAreaDozingTransitions =
_animateBottomAreaDozingTransitions.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index defca18..21651ba2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -318,6 +318,10 @@
repository.ambientIndicationVisible.value = isVisible
}
+ fun keyguardDoneAnimationsFinished() {
+ repository.keyguardDoneAnimationsFinished()
+ }
+
companion object {
private const val TAG = "KeyguardInteractor"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
index fb20000..e3f4739 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
@@ -31,7 +31,6 @@
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
@@ -72,7 +71,6 @@
private val context: Context,
@Application private val applicationScope: CoroutineScope,
@Main private val mainDispatcher: CoroutineDispatcher,
- @Background private val backgroundDispatcher: CoroutineDispatcher,
private val repository: DeviceEntryFaceAuthRepository,
private val primaryBouncerInteractor: Lazy<PrimaryBouncerInteractor>,
private val alternateBouncerInteractor: AlternateBouncerInteractor,
@@ -109,7 +107,6 @@
fallbackToDetect = false
)
}
- .flowOn(backgroundDispatcher)
.launchIn(applicationScope)
alternateBouncerInteractor.isVisible
@@ -121,7 +118,6 @@
fallbackToDetect = false
)
}
- .flowOn(backgroundDispatcher)
.launchIn(applicationScope)
merge(
@@ -150,7 +146,6 @@
fallbackToDetect = true
)
}
- .flowOn(backgroundDispatcher)
.launchIn(applicationScope)
deviceEntryFingerprintAuthRepository.isLockedOut
@@ -163,7 +158,6 @@
}
}
}
- .flowOn(backgroundDispatcher)
.launchIn(applicationScope)
// User switching should stop face auth and then when it is complete we should trigger face
@@ -187,7 +181,6 @@
)
}
}
- .flowOn(backgroundDispatcher)
.launchIn(applicationScope)
}
@@ -302,7 +295,6 @@
trustManager.clearAllBiometricRecognized(BiometricSourceType.FACE, userInfo.id)
}
}
- .flowOn(backgroundDispatcher)
.onEach { (isAuthenticated, _) ->
listeners.forEach { it.onAuthenticatedChanged(isAuthenticated) }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
index acfd3b0..24240df 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
@@ -30,6 +30,7 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants
+import com.android.systemui.util.kotlin.logD
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -58,11 +59,14 @@
observer =
PreviewLifecycleObserver(
- renderer,
applicationScope,
mainDispatcher,
+ renderer,
::destroyObserver,
)
+
+ logD(TAG) { "Created observer $observer" }
+
// Destroy any previous renderer associated with this token.
activePreviews[renderer.id]?.let { destroyObserver(it) }
activePreviews[renderer.id] = observer
@@ -80,6 +84,8 @@
observer,
)
)
+ // NOTE: The process on the other side can retain messenger indefinitely.
+ // (e.g. GC might not trigger and cleanup the reference)
val msg = Message.obtain()
msg.replyTo = messenger
result.putParcelable(KEY_PREVIEW_CALLBACK, msg)
@@ -99,57 +105,84 @@
}
}
- private class PreviewLifecycleObserver(
- private val renderer: KeyguardPreviewRenderer,
- private val scope: CoroutineScope,
- private val mainDispatcher: CoroutineDispatcher,
- private val requestDestruction: (PreviewLifecycleObserver) -> Unit,
- ) : Handler.Callback, IBinder.DeathRecipient {
+ companion object {
+ internal const val TAG = "KeyguardRemotePreviewManager"
+ @VisibleForTesting const val KEY_PREVIEW_SURFACE_PACKAGE = "surface_package"
+ @VisibleForTesting const val KEY_PREVIEW_CALLBACK = "callback"
+ }
+}
- private var isDestroyedOrDestroying = false
+/**
+ * Handles messages from the other process and handles cleanup.
+ *
+ * NOTE: The other process might hold on to reference of this class indefinitely. It's entirely
+ * possible that GC won't trigger and we'll leak this for all times even if [onDestroy] was called.
+ * This helps make sure no non-Singleton objects are retained beyond destruction to prevent leaks.
+ */
+@VisibleForTesting(VisibleForTesting.PRIVATE)
+class PreviewLifecycleObserver(
+ private val scope: CoroutineScope,
+ private val mainDispatcher: CoroutineDispatcher,
+ renderer: KeyguardPreviewRenderer,
+ onDestroy: (PreviewLifecycleObserver) -> Unit,
+) : Handler.Callback, IBinder.DeathRecipient {
- override fun handleMessage(message: Message): Boolean {
- if (isDestroyedOrDestroying) {
- return true
- }
+ private var isDestroyedOrDestroying = false
+ // These two are null after destruction
+ @VisibleForTesting var renderer: KeyguardPreviewRenderer?
+ @VisibleForTesting var onDestroy: ((PreviewLifecycleObserver) -> Unit)?
- when (message.what) {
- KeyguardPreviewConstants.MESSAGE_ID_SLOT_SELECTED -> {
- message.data.getString(KeyguardPreviewConstants.KEY_SLOT_ID)?.let { slotId ->
- renderer.onSlotSelected(slotId = slotId)
- }
- }
- KeyguardPreviewConstants.MESSAGE_ID_HIDE_SMART_SPACE -> {
- renderer.hideSmartspace(
- message.data.getBoolean(KeyguardPreviewConstants.KEY_HIDE_SMART_SPACE)
- )
- }
- else -> requestDestruction(this)
- }
+ init {
+ this.renderer = renderer
+ this.onDestroy = onDestroy
+ }
+ override fun handleMessage(message: Message): Boolean {
+ if (isDestroyedOrDestroying) {
return true
}
- override fun binderDied() {
- requestDestruction(this)
+ when (message.what) {
+ KeyguardPreviewConstants.MESSAGE_ID_SLOT_SELECTED -> {
+ message.data.getString(KeyguardPreviewConstants.KEY_SLOT_ID)?.let { slotId ->
+ checkNotNull(renderer).onSlotSelected(slotId = slotId)
+ }
+ }
+ KeyguardPreviewConstants.MESSAGE_ID_HIDE_SMART_SPACE -> {
+ checkNotNull(renderer)
+ .hideSmartspace(
+ message.data.getBoolean(KeyguardPreviewConstants.KEY_HIDE_SMART_SPACE)
+ )
+ }
+ else -> checkNotNull(onDestroy).invoke(this)
}
- fun onDestroy(): Pair<IBinder?, Int>? {
- if (isDestroyedOrDestroying) {
- return null
- }
+ return true
+ }
- isDestroyedOrDestroying = true
- val hostToken = renderer.hostToken
+ override fun binderDied() {
+ onDestroy?.invoke(this)
+ }
+
+ fun onDestroy(): Pair<IBinder?, Int>? {
+ if (isDestroyedOrDestroying) {
+ return null
+ }
+
+ logD(TAG) { "Destroying $this" }
+
+ isDestroyedOrDestroying = true
+ return renderer?.let { rendererToDestroy ->
+ this.renderer = null
+ this.onDestroy = null
+ val hostToken = rendererToDestroy.hostToken
hostToken?.unlinkToDeath(this, 0)
- scope.launch(mainDispatcher) { renderer.destroy() }
- return renderer.id
+ scope.launch(mainDispatcher) { rendererToDestroy.destroy() }
+ rendererToDestroy.id
}
}
companion object {
private const val TAG = "KeyguardRemotePreviewManager"
- @VisibleForTesting const val KEY_PREVIEW_SURFACE_PACKAGE = "surface_package"
- @VisibleForTesting const val KEY_PREVIEW_CALLBACK = "callback"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
index 14d4b68..c87fd14 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
@@ -119,7 +119,7 @@
::AnimatingColorTransition
)
- val bgColor = context.getColor(com.google.android.material.R.color.material_dynamic_secondary95)
+ val bgColor = context.getColor(com.google.android.material.R.color.material_dynamic_neutral20)
val surfaceColor =
animatingColorTransitionFactory(bgColor, ::surfaceFromScheme) { surfaceColor ->
val colorList = ColorStateList.valueOf(surfaceColor)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
index a4088f8..0434b2d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.tiles
+import android.app.AlertDialog
import android.content.Intent
import android.os.Handler
import android.os.Looper
@@ -24,8 +25,11 @@
import android.view.View
import android.widget.Switch
import androidx.annotation.VisibleForTesting
+import com.android.internal.jank.InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN
import com.android.internal.logging.MetricsLogger
import com.android.systemui.Flags.recordIssueQsTile
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
@@ -36,7 +40,11 @@
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.recordissue.RecordIssueDialogDelegate
import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.KeyguardDismissUtil
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
class RecordIssueTile
@@ -50,7 +58,11 @@
metricsLogger: MetricsLogger,
statusBarStateController: StatusBarStateController,
activityStarter: ActivityStarter,
- qsLogger: QSLogger
+ qsLogger: QSLogger,
+ private val keyguardDismissUtil: KeyguardDismissUtil,
+ private val keyguardStateController: KeyguardStateController,
+ private val dialogLaunchAnimator: DialogLaunchAnimator,
+ private val sysuiDialogFactory: SystemUIDialog.Factory,
) :
QSTileImpl<QSTile.BooleanState>(
host,
@@ -76,11 +88,41 @@
handlesLongClick = false
}
- override fun handleClick(view: View?) {
- isRecording = !isRecording
+ @VisibleForTesting
+ public override fun handleClick(view: View?) {
+ if (isRecording) {
+ isRecording = false
+ } else {
+ mUiHandler.post { showPrompt(view) }
+ }
refreshState()
}
+ private fun showPrompt(view: View?) {
+ val dialog: AlertDialog =
+ RecordIssueDialogDelegate(sysuiDialogFactory) {
+ isRecording = true
+ refreshState()
+ }
+ .createDialog()
+ val dismissAction =
+ ActivityStarter.OnDismissAction {
+ // We animate from the touched view only if we are not on the keyguard, given
+ // that if we are we will dismiss it which will also collapse the shade.
+ if (view != null && !keyguardStateController.isShowing) {
+ dialogLaunchAnimator.showFromView(
+ dialog,
+ view,
+ DialogCuj(CUJ_SHADE_DIALOG_OPEN, TILE_SPEC)
+ )
+ } else {
+ dialog.show()
+ }
+ false
+ }
+ keyguardDismissUtil.executeWhenUnlocked(dismissAction, false, true)
+ }
+
override fun getLongClickIntent(): Intent? = null
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
new file mode 100644
index 0000000..8221c63
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.recordissue
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.res.ColorStateList
+import android.graphics.Color
+import android.os.Bundle
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.WindowManager
+import android.widget.Button
+import android.widget.PopupMenu
+import android.widget.Switch
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+
+class RecordIssueDialogDelegate(
+ private val factory: SystemUIDialog.Factory,
+ private val onStarted: Runnable
+) : SystemUIDialog.Delegate {
+
+ @SuppressLint("UseSwitchCompatOrMaterialCode") private lateinit var screenRecordSwitch: Switch
+ private lateinit var issueTypeButton: Button
+
+ override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
+ dialog.apply {
+ setView(LayoutInflater.from(context).inflate(R.layout.record_issue_dialog, null))
+ setTitle(context.getString(R.string.qs_record_issue_label))
+ setIcon(R.drawable.qs_record_issue_icon_off)
+ setNegativeButton(R.string.cancel) { _, _ -> dismiss() }
+ setPositiveButton(R.string.qs_record_issue_start) { _, _ ->
+ onStarted.run()
+ dismiss()
+ }
+ }
+ }
+
+ override fun createDialog(): SystemUIDialog = factory.create(this)
+
+ override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
+ dialog.apply {
+ window?.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
+ window?.setGravity(Gravity.CENTER)
+
+ screenRecordSwitch = requireViewById(R.id.screenrecord_switch)
+ issueTypeButton = requireViewById(R.id.issue_type_button)
+ issueTypeButton.setOnClickListener { onIssueTypeClicked(context) }
+ }
+ }
+
+ private fun onIssueTypeClicked(context: Context) {
+ val selectedCategory = issueTypeButton.text.toString()
+ val popupMenu = PopupMenu(context, issueTypeButton)
+
+ context.resources.getStringArray(R.array.qs_record_issue_types).forEachIndexed { i, cat ->
+ popupMenu.menu.add(0, 0, i, cat).apply {
+ setIcon(R.drawable.arrow_pointing_down)
+ if (selectedCategory != cat) {
+ iconTintList = ColorStateList.valueOf(Color.TRANSPARENT)
+ }
+ }
+ }
+ popupMenu.apply {
+ setOnMenuItemClickListener {
+ issueTypeButton.text = it.title
+ true
+ }
+ setForceShowIcon(true)
+ show()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index 756c440..0c5472f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -41,6 +41,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.log.core.LogLevel;
import com.android.systemui.res.R;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
@@ -49,6 +50,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Objects;
+import java.util.function.Consumer;
import javax.inject.Inject;
@@ -210,23 +212,33 @@
private void notifyKeyguardChanged() {
Trace.beginSection("KeyguardStateController#notifyKeyguardChanged");
// Copy the list to allow removal during callback.
- new ArrayList<>(mCallbacks).forEach(Callback::onKeyguardShowingChanged);
+ invokeForEachCallback(Callback::onKeyguardShowingChanged);
Trace.endSection();
}
private void notifyKeyguardFaceAuthEnabledChanged() {
+ invokeForEachCallback(Callback::onFaceEnrolledChanged);
+ }
+
+ private void invokeForEachCallback(Consumer<Callback> consumer) {
// Copy the list to allow removal during callback.
- new ArrayList<>(mCallbacks).forEach(callback -> {
+ ArrayList<Callback> copyOfCallbacks = new ArrayList<>(mCallbacks);
+ for (int i = 0; i < copyOfCallbacks.size(); i++) {
+ Callback callback = copyOfCallbacks.get(i);
+ // Temporary fix for b/315731775, callback is null even though only non-null callbacks
+ // are added to the list by addCallback
if (callback != null) {
- callback.onFaceEnrolledChanged();
+ consumer.accept(callback);
+ } else {
+ mLogger.log("KeyguardStateController callback is null", LogLevel.DEBUG);
}
- });
+ }
}
private void notifyUnlockedChanged() {
Trace.beginSection("KeyguardStateController#notifyUnlockedChanged");
// Copy the list to allow removal during callback.
- new ArrayList<>(mCallbacks).forEach(Callback::onUnlockedChanged);
+ invokeForEachCallback(Callback::onUnlockedChanged);
Trace.endSection();
}
@@ -242,10 +254,7 @@
Trace.traceCounter(Trace.TRACE_TAG_APP, "keyguardFadingAway",
keyguardFadingAway ? 1 : 0);
mKeyguardFadingAway = keyguardFadingAway;
- ArrayList<Callback> callbacks = new ArrayList<>(mCallbacks);
- for (int i = 0; i < callbacks.size(); i++) {
- callbacks.get(i).onKeyguardFadingAwayChanged();
- }
+ invokeForEachCallback(Callback::onKeyguardFadingAwayChanged);
}
}
@@ -359,7 +368,7 @@
Trace.traceCounter(Trace.TRACE_TAG_APP, "keyguardGoingAway",
keyguardGoingAway ? 1 : 0);
mKeyguardGoingAway = keyguardGoingAway;
- new ArrayList<>(mCallbacks).forEach(Callback::onKeyguardGoingAwayChanged);
+ invokeForEachCallback(Callback::onKeyguardGoingAwayChanged);
}
}
@@ -368,7 +377,7 @@
if (mPrimaryBouncerShowing != showing) {
mPrimaryBouncerShowing = showing;
- new ArrayList<>(mCallbacks).forEach(Callback::onPrimaryBouncerShowingChanged);
+ invokeForEachCallback(Callback::onPrimaryBouncerShowingChanged);
}
}
@@ -392,13 +401,13 @@
boolean dismissingFromTouch) {
mDismissAmount = dismissAmount;
mDismissingFromTouch = dismissingFromTouch;
- new ArrayList<>(mCallbacks).forEach(Callback::onKeyguardDismissAmountChanged);
+ invokeForEachCallback(Callback::onKeyguardDismissAmountChanged);
}
@Override
public void setLaunchTransitionFadingAway(boolean fadingAway) {
mLaunchTransitionFadingAway = fadingAway;
- new ArrayList<>(mCallbacks).forEach(Callback::onLaunchTransitionFadingAwayChanged);
+ invokeForEachCallback(Callback::onLaunchTransitionFadingAwayChanged);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Log.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Log.kt
new file mode 100644
index 0000000..2f6c450
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Log.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.kotlin
+
+import android.util.Log
+
+/** Logs message at [Log.DEBUG] level. Won't call the lambda if [DEBUG] is not loggable. */
+inline fun logD(tag: String, messageLambda: () -> String) {
+ if (Log.isLoggable(tag, Log.DEBUG)) {
+ Log.d(tag, messageLambda.invoke())
+ }
+}
+
+/** Logs message at [Log.VERBOSE] level. Won't call the lambda if [VERBOSE] is not loggable. */
+inline fun logV(tag: String, messageLambda: () -> String) {
+ if (Log.isLoggable(tag, Log.VERBOSE)) {
+ Log.v(tag, messageLambda.invoke())
+ }
+}
+
+/** Logs message at [Log.INFO] level. Won't call the lambda if [INFO] is not loggable. */
+inline fun logI(tag: String, messageLambda: () -> String) {
+ if (Log.isLoggable(tag, Log.INFO)) {
+ Log.i(tag, messageLambda.invoke())
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index ae5f625..4ef18cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -93,6 +93,7 @@
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.flags.SystemPropertiesHelper;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.navigationbar.NavigationModeController;
@@ -190,6 +191,7 @@
private @Mock ShadeInteractor mShadeInteractor;
private @Mock ShadeWindowLogger mShadeWindowLogger;
private @Mock SelectedUserInteractor mSelectedUserInteractor;
+ private @Mock KeyguardInteractor mKeyguardInteractor;
private @Captor ArgumentCaptor<KeyguardStateController.Callback>
mKeyguardStateControllerCallback;
private @Captor ArgumentCaptor<KeyguardUpdateMonitorCallback>
@@ -1131,7 +1133,8 @@
() -> mDreamingToLockscreenTransitionViewModel,
mSystemPropertiesHelper,
() -> mock(WindowManagerLockscreenVisibilityManager.class),
- mSelectedUserInteractor);
+ mSelectedUserInteractor,
+ mKeyguardInteractor);
mViewMediator.start();
mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
index 4ab8e28..6eb95bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
@@ -137,7 +137,6 @@
mContext,
testScope.backgroundScope,
dispatcher,
- dispatcher,
faceAuthRepository,
{
PrimaryBouncerInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
index d8199c5..e9714dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
@@ -23,6 +23,7 @@
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -30,12 +31,19 @@
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.KeyguardDismissUtil
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.ArgumentMatchers.isA
import org.mockito.Mock
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
/**
@@ -53,15 +61,22 @@
@Mock private lateinit var statusBarStateController: StatusBarStateController
@Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var qsLogger: QSLogger
+ @Mock private lateinit var keyguardDismissUtil: KeyguardDismissUtil
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var dialogLauncherAnimator: DialogLaunchAnimator
+ @Mock private lateinit var dialogFactory: SystemUIDialog.Factory
+ @Mock private lateinit var dialog: SystemUIDialog
+ private lateinit var testableLooper: TestableLooper
private lateinit var tile: RecordIssueTile
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(host.context).thenReturn(mContext)
+ whenever(dialogFactory.create(any())).thenReturn(dialog)
- val testableLooper = TestableLooper.get(this)
+ testableLooper = TestableLooper.get(this)
tile =
RecordIssueTile(
host,
@@ -72,7 +87,11 @@
metricsLogger,
statusBarStateController,
activityStarter,
- qsLogger
+ qsLogger,
+ keyguardDismissUtil,
+ keyguardStateController,
+ dialogLauncherAnimator,
+ dialogFactory
)
}
@@ -119,4 +138,18 @@
assertThat(testState.state).isEqualTo(Tile.STATE_ACTIVE)
}
+
+ @Test
+ fun showPrompt_shouldUseKeyguardDismissUtil_ToShowDialog() {
+ tile.isRecording = false
+ tile.handleClick(null)
+ testableLooper.processAllMessages()
+
+ verify(keyguardDismissUtil)
+ .executeWhenUnlocked(
+ isA(ActivityStarter.OnDismissAction::class.java),
+ eq(false),
+ eq(true)
+ )
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
new file mode 100644
index 0000000..bbc59d0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.recordissue
+
+import android.app.Dialog
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.widget.Button
+import android.widget.Switch
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.model.SysUiState
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class RecordIssueDialogDelegateTest : SysuiTestCase() {
+
+ private lateinit var dialog: SystemUIDialog
+ private lateinit var latch: CountDownLatch
+
+ @Before
+ fun setup() {
+ val dialogFactory =
+ SystemUIDialog.Factory(
+ context,
+ mock<FeatureFlags>(),
+ mock<SystemUIDialogManager>(),
+ mock<SysUiState>().apply {
+ whenever(setFlag(anyInt(), anyBoolean())).thenReturn(this)
+ },
+ mock<BroadcastDispatcher>(),
+ mock<DialogLaunchAnimator>()
+ )
+
+ latch = CountDownLatch(1)
+ dialog = RecordIssueDialogDelegate(dialogFactory) { latch.countDown() }.createDialog()
+ dialog.show()
+ }
+
+ @After
+ fun teardown() {
+ dialog.dismiss()
+ }
+
+ @Test
+ fun dialog_hasCorrectUiElements_afterCreation() {
+ dialog.requireViewById<Switch>(R.id.screenrecord_switch)
+ dialog.requireViewById<Button>(R.id.issue_type_button)
+
+ assertThat(dialog.getButton(Dialog.BUTTON_POSITIVE).text)
+ .isEqualTo(context.getString(R.string.qs_record_issue_start))
+ assertThat(dialog.getButton(Dialog.BUTTON_NEGATIVE).text)
+ .isEqualTo(context.getString(R.string.cancel))
+ }
+
+ @Test
+ fun onStarted_isCalled_afterStartButtonIsClicked() {
+ dialog.getButton(Dialog.BUTTON_POSITIVE).callOnClick()
+ latch.await(1L, TimeUnit.MILLISECONDS)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java
index 0f33aaf..2c7b606 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java
@@ -56,6 +56,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -138,6 +139,7 @@
}
@Test
+ @Ignore("b/315848285")
public void screenshotDisplayed_userConsented_screenshotExportedSuccessfully() {
ResultReceiver resultReceiver = createResultReceiver((resultCode, data) -> {
assertThat(resultCode).isEqualTo(RESULT_OK);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index c5d745a..4200f05 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -41,6 +41,10 @@
private val _deferKeyguardDone: MutableSharedFlow<KeyguardDone> = MutableSharedFlow()
override val keyguardDone: Flow<KeyguardDone> = _deferKeyguardDone
+ private val _keyguardDoneAnimationsFinished: MutableSharedFlow<Unit> =
+ MutableSharedFlow(extraBufferCapacity = 1)
+ override val keyguardDoneAnimationsFinished: Flow<Unit> = _keyguardDoneAnimationsFinished
+
private val _clockShouldBeCentered = MutableStateFlow<Boolean>(true)
override val clockShouldBeCentered: Flow<Boolean> = _clockShouldBeCentered
@@ -176,6 +180,10 @@
_deferKeyguardDone.emit(timing)
}
+ override fun keyguardDoneAnimationsFinished() {
+ _keyguardDoneAnimationsFinished.tryEmit(Unit)
+ }
+
override fun setClockShouldBeCentered(shouldBeCentered: Boolean) {
_clockShouldBeCentered.value = shouldBeCentered
}
diff --git a/services/autofill/bugfixes.aconfig b/services/autofill/bugfixes.aconfig
index ab678d9..ca6fefd 100644
--- a/services/autofill/bugfixes.aconfig
+++ b/services/autofill/bugfixes.aconfig
@@ -27,3 +27,10 @@
description: "Mitigation for view state reset to empty causing no save dialog to show issue"
bug: "297976948"
}
+
+flag {
+ name: "ignore_invisible_view_group_in_assist_structure"
+ namespace: "autofill"
+ description: "Mitigation for autofill providers miscalculating view visibility"
+ bug: "291795358"
+}
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 09e7986..136692e 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -1426,14 +1426,19 @@
/**
* Checks if package is quarantined for a specific user.
+ *
+ * @throws PackageManager.NameNotFoundException if the package is not found
*/
- public abstract boolean isPackageQuarantined(@NonNull String packageName,
- @UserIdInt int userId);
+ public abstract boolean isPackageQuarantined(@NonNull String packageName, @UserIdInt int userId)
+ throws PackageManager.NameNotFoundException;
/**
* Checks if package is stopped for a specific user.
+ *
+ * @throws PackageManager.NameNotFoundException if the package is not found
*/
- public abstract boolean isPackageStopped(@NonNull String packageName, @UserIdInt int userId);
+ public abstract boolean isPackageStopped(@NonNull String packageName, @UserIdInt int userId)
+ throws PackageManager.NameNotFoundException;
/**
* Sends the PACKAGE_RESTARTED broadcast.
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 7191684..df8f17a 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -5175,6 +5175,8 @@
return null;
}
+ final long startTimeNs = SystemClock.elapsedRealtimeNanos();
+
if (DEBUG_SERVICE) {
Slog.v(TAG_SERVICE, "Bringing up " + r + " " + r.intent + " fg=" + r.fgRequired);
}
@@ -5333,9 +5335,14 @@
bringDownServiceLocked(r, enqueueOomAdj);
return msg;
}
+ mAm.mProcessList.getAppStartInfoTracker().handleProcessServiceStart(startTimeNs, app, r,
+ hostingRecord, true);
if (isolated) {
r.isolationHostProc = app;
}
+ } else {
+ mAm.mProcessList.getAppStartInfoTracker().handleProcessServiceStart(startTimeNs, app, r,
+ hostingRecord, false);
}
if (r.fgRequired) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d9dcfab..2ee39c57 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1103,9 +1103,51 @@
private final ActivityMetricsLaunchObserver mActivityLaunchObserver =
new ActivityMetricsLaunchObserver() {
+
@Override
- public void onActivityLaunched(long id, ComponentName name, int temperature) {
+ public void onIntentStarted(@NonNull Intent intent, long timestampNanos) {
+ synchronized (this) {
+ mProcessList.getAppStartInfoTracker().onIntentStarted(intent, timestampNanos);
+ }
+ }
+
+ @Override
+ public void onIntentFailed(long id) {
+ mProcessList.getAppStartInfoTracker().onIntentFailed(id);
+ }
+
+ @Override
+ public void onActivityLaunched(long id, ComponentName name, int temperature, int userId) {
mAppProfiler.onActivityLaunched();
+ synchronized (ActivityManagerService.this) {
+ ProcessRecord record = null;
+ try {
+ record = getProcessRecordLocked(name.getPackageName(), mContext
+ .getPackageManager().getPackageUidAsUser(name.getPackageName(), 0,
+ userId));
+ } catch (NameNotFoundException nnfe) {
+ // Ignore, record will be lost.
+ }
+ mProcessList.getAppStartInfoTracker().onActivityLaunched(id, name, temperature,
+ record);
+ }
+ }
+
+ @Override
+ public void onActivityLaunchCancelled(long id) {
+ mProcessList.getAppStartInfoTracker().onActivityLaunchCancelled(id);
+ }
+
+ @Override
+ public void onActivityLaunchFinished(long id, ComponentName name, long timestampNanos,
+ int launchMode) {
+ mProcessList.getAppStartInfoTracker().onActivityLaunchFinished(id, name,
+ timestampNanos, launchMode);
+ }
+
+ @Override
+ public void onReportFullyDrawn(long id, long timestampNanos) {
+ mProcessList.getAppStartInfoTracker().onReportFullyDrawn(id, timestampNanos);
}
};
@@ -4488,13 +4530,13 @@
@GuardedBy("this")
private void attachApplicationLocked(@NonNull IApplicationThread thread,
int pid, int callingUid, long startSeq) {
-
// Find the application record that is being attached... either via
// the pid if we are running in multiple processes, or just pull the
// next app record if we are emulating process with anonymous threads.
ProcessRecord app;
long startTime = SystemClock.uptimeMillis();
long bindApplicationTimeMillis;
+ long bindApplicationTimeNanos;
if (pid != MY_PID && pid >= 0) {
synchronized (mPidsSelfLocked) {
app = mPidsSelfLocked.get(pid);
@@ -4698,6 +4740,7 @@
checkTime(startTime, "attachApplicationLocked: immediately before bindApplication");
bindApplicationTimeMillis = SystemClock.uptimeMillis();
+ bindApplicationTimeNanos = SystemClock.elapsedRealtimeNanos();
mAtmInternal.preBindApplication(app.getWindowProcessController());
final ActiveInstrumentation instr2 = app.getActiveInstrumentation();
if (mPlatformCompat != null) {
@@ -4754,6 +4797,8 @@
}
app.setBindApplicationTime(bindApplicationTimeMillis);
+ mProcessList.getAppStartInfoTracker()
+ .reportBindApplicationTimeNanos(app, bindApplicationTimeNanos);
// Make app active after binding application or client may be running requests (e.g
// starting activities) before it is ready.
@@ -9799,12 +9844,12 @@
final int uid = enforceDumpPermissionForPackage(packageName, userId, callingUid,
"getHistoricalProcessStartReasons");
if (uid != INVALID_UID) {
- mProcessList.mAppStartInfoTracker.getStartInfo(
+ mProcessList.getAppStartInfoTracker().getStartInfo(
packageName, userId, callingPid, maxNum, results);
}
} else {
// If no package name is given, use the caller's uid as the filter uid.
- mProcessList.mAppStartInfoTracker.getStartInfo(
+ mProcessList.getAppStartInfoTracker().getStartInfo(
packageName, callingUid, callingPid, maxNum, results);
}
return new ParceledListSlice<ApplicationStartInfo>(results);
@@ -9822,7 +9867,7 @@
}
final int callingUid = Binder.getCallingUid();
- mProcessList.mAppStartInfoTracker.addStartInfoCompleteListener(listener, callingUid);
+ mProcessList.getAppStartInfoTracker().addStartInfoCompleteListener(listener, callingUid);
}
@@ -9836,7 +9881,7 @@
}
final int callingUid = Binder.getCallingUid();
- mProcessList.mAppStartInfoTracker.clearStartInfoCompleteListener(callingUid, true);
+ mProcessList.getAppStartInfoTracker().clearStartInfoCompleteListener(callingUid, true);
}
@Override
@@ -10138,7 +10183,7 @@
pw.println();
if (dumpAll) {
pw.println("-------------------------------------------------------------------------------");
- mProcessList.mAppStartInfoTracker.dumpHistoryProcessStartInfo(pw, dumpPackage);
+ mProcessList.getAppStartInfoTracker().dumpHistoryProcessStartInfo(pw, dumpPackage);
pw.println("-------------------------------------------------------------------------------");
mProcessList.mAppExitInfoTracker.dumpHistoryProcessExitInfo(pw, dumpPackage);
}
@@ -10541,7 +10586,7 @@
dumpPackage = args[opti];
opti++;
}
- mProcessList.mAppStartInfoTracker.dumpHistoryProcessStartInfo(pw, dumpPackage);
+ mProcessList.getAppStartInfoTracker().dumpHistoryProcessStartInfo(pw, dumpPackage);
} else if ("exit-info".equals(cmd)) {
if (opti < args.length) {
dumpPackage = args[opti];
@@ -13831,6 +13876,7 @@
// activity manager to announce its creation.
public boolean bindBackupAgent(String packageName, int backupMode, int targetUserId,
@BackupDestination int backupDestination) {
+ long startTimeNs = SystemClock.elapsedRealtimeNanos();
if (DEBUG_BACKUP) {
Slog.v(TAG, "bindBackupAgent: app=" + packageName + " mode=" + backupMode
+ " targetUserId=" + targetUserId + " callingUid = " + Binder.getCallingUid()
@@ -13906,15 +13952,20 @@
? new ComponentName(app.packageName, app.backupAgentName)
: new ComponentName("android", "FullBackupAgent");
- // startProcessLocked() returns existing proc's record if it's already running
- ProcessRecord proc = startProcessLocked(app.processName, app,
- false, 0,
- new HostingRecord(HostingRecord.HOSTING_TYPE_BACKUP, hostingName),
- ZYGOTE_POLICY_FLAG_SYSTEM_PROCESS, false, false);
+ ProcessRecord proc = getProcessRecordLocked(app.processName, app.uid);
+ boolean isProcessStarted = proc != null;
+ if (!isProcessStarted) {
+ proc = startProcessLocked(app.processName, app,
+ false, 0,
+ new HostingRecord(HostingRecord.HOSTING_TYPE_BACKUP, hostingName),
+ ZYGOTE_POLICY_FLAG_SYSTEM_PROCESS, false, false);
+ }
if (proc == null) {
Slog.e(TAG, "Unable to start backup agent process " + r);
return false;
}
+ mProcessList.getAppStartInfoTracker().handleProcessBackupStart(startTimeNs, proc, r,
+ !isProcessStarted);
// If the app is a regular app (uid >= 10000) and not the system server or phone
// process, etc, then mark it as being in full backup so that certain calls to the
@@ -18741,8 +18792,12 @@
// If the process is known as top app, set a hint so when the process is
// started, the top priority can be applied immediately to avoid cpu being
// preempted by other processes before attaching the process of top app.
- startProcessLocked(processName, info, knownToBeDead, 0 /* intentFlags */,
- new HostingRecord(hostingType, hostingName, isTop),
+ final long startTimeNs = SystemClock.elapsedRealtimeNanos();
+ HostingRecord hostingRecord =
+ new HostingRecord(hostingType, hostingName, isTop);
+ ProcessRecord rec = getProcessRecordLocked(processName, info.uid);
+ ProcessRecord app = startProcessLocked(processName, info, knownToBeDead,
+ 0 /* intentFlags */, hostingRecord,
ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE, false /* allowWhileBooting */,
false /* isolated */);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index f3b2ef3..ae0cd65 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -1362,7 +1362,7 @@
}
userId = user.id;
}
- mInternal.mProcessList.mAppStartInfoTracker
+ mInternal.mProcessList.getAppStartInfoTracker()
.clearHistoryProcessStartInfo(packageName, userId);
return 0;
}
diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java
index edca74f..82e554e 100644
--- a/services/core/java/com/android/server/am/AppStartInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java
@@ -22,11 +22,12 @@
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import android.app.ActivityOptions;
+import android.annotation.NonNull;
import android.app.ApplicationStartInfo;
import android.app.Flags;
import android.app.IApplicationStartInfoCompleteListener;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -138,6 +139,15 @@
/** The path to the historical proc start info file, persisted in the storage. */
@VisibleForTesting File mProcStartInfoFile;
+
+ /**
+ * Temporary list of records that have not been completed.
+ *
+ * Key is timestamp of launch from {@link #ActivityMetricsLaunchObserver}.
+ */
+ @GuardedBy("mLock")
+ private ArrayMap<Long, ApplicationStartInfo> mInProgRecords = new ArrayMap<>();
+
AppStartInfoTracker() {
mCallbacks = new SparseArray<>();
mData = new ProcessMap<AppStartInfoContainer>();
@@ -174,68 +184,99 @@
});
}
- void handleProcessColdStarted(long startTimeNs, HostingRecord hostingRecord,
- ProcessRecord app) {
- synchronized (mLock) {
- if (!mEnabled) {
- return;
- }
- ApplicationStartInfo start = new ApplicationStartInfo();
- addBaseFieldsFromProcessRecord(start, app);
- start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
- start.addStartupTimestamp(
- ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs);
- start.addStartupTimestamp(
- ApplicationStartInfo.START_TIMESTAMP_FORK, app.getStartElapsedTime());
- start.setStartType(ApplicationStartInfo.START_TYPE_COLD);
- start.setReason(ApplicationStartInfo.START_REASON_OTHER);
- addStartInfoLocked(start);
- }
- }
-
- public void handleProcessActivityWarmOrHotStarted(long startTimeNs,
- ActivityOptions activityOptions, Intent intent) {
+ void onIntentStarted(@NonNull Intent intent, long timestampNanos) {
synchronized (mLock) {
if (!mEnabled) {
return;
}
ApplicationStartInfo start = new ApplicationStartInfo();
start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
- start.addStartupTimestamp(
- ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs);
start.setIntent(intent);
- start.setReason(ApplicationStartInfo.START_REASON_LAUNCHER);
- if (activityOptions != null) {
- start.setProcessName(activityOptions.getPackageName());
- }
- start.setStartType(ApplicationStartInfo.START_TYPE_WARM);
+ start.setStartType(ApplicationStartInfo.START_TYPE_UNSET);
+ start.addStartupTimestamp(ApplicationStartInfo.START_TIMESTAMP_LAUNCH, timestampNanos);
if (intent != null && intent.getCategories() != null
&& intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
start.setReason(ApplicationStartInfo.START_REASON_LAUNCHER);
} else {
start.setReason(ApplicationStartInfo.START_REASON_START_ACTIVITY);
}
- addStartInfoLocked(start);
+ mInProgRecords.put(timestampNanos, start);
}
}
- public void handleProcessActivityStartedFromRecents(long startTimeNs,
- ActivityOptions activityOptions) {
+ void onIntentFailed(long id) {
synchronized (mLock) {
if (!mEnabled) {
return;
}
- ApplicationStartInfo start = new ApplicationStartInfo();
- start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
- start.addStartupTimestamp(
- ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs);
- if (activityOptions != null) {
- start.setIntent(activityOptions.getResultData());
- start.setProcessName(activityOptions.getPackageName());
+ if (!mInProgRecords.containsKey(id)) {
+ return;
}
- start.setReason(ApplicationStartInfo.START_REASON_LAUNCHER_RECENTS);
- start.setStartType(ApplicationStartInfo.START_TYPE_WARM);
- addStartInfoLocked(start);
+ mInProgRecords.get(id).setStartupState(ApplicationStartInfo.STARTUP_STATE_ERROR);
+ mInProgRecords.remove(id);
+ }
+ }
+
+ void onActivityLaunched(long id, ComponentName name, long temperature, ProcessRecord app) {
+ synchronized (mLock) {
+ if (!mEnabled) {
+ return;
+ }
+ if (!mInProgRecords.containsKey(id)) {
+ return;
+ }
+ if (app != null) {
+ ApplicationStartInfo info = mInProgRecords.get(id);
+ info.setStartType((int) temperature);
+ addBaseFieldsFromProcessRecord(info, app);
+ addStartInfoLocked(info);
+ } else {
+ mInProgRecords.remove(id);
+ }
+ }
+ }
+
+ void onActivityLaunchCancelled(long id) {
+ synchronized (mLock) {
+ if (!mEnabled) {
+ return;
+ }
+ if (!mInProgRecords.containsKey(id)) {
+ return;
+ }
+ ApplicationStartInfo info = mInProgRecords.get(id);
+ info.setStartupState(ApplicationStartInfo.STARTUP_STATE_ERROR);
+ mInProgRecords.remove(id);
+ }
+ }
+
+ void onActivityLaunchFinished(long id, ComponentName name, long timestampNanos,
+ int launchMode) {
+ synchronized (mLock) {
+ if (!mEnabled) {
+ return;
+ }
+ if (!mInProgRecords.containsKey(id)) {
+ return;
+ }
+ ApplicationStartInfo info = mInProgRecords.get(id);
+ info.setStartupState(ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN);
+ info.setLaunchMode(launchMode);
+ }
+ }
+
+ void onReportFullyDrawn(long id, long timestampNanos) {
+ synchronized (mLock) {
+ if (!mEnabled) {
+ return;
+ }
+ if (!mInProgRecords.containsKey(id)) {
+ return;
+ }
+ ApplicationStartInfo info = mInProgRecords.get(id);
+ info.addStartupTimestamp(ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN,
+ timestampNanos);
+ mInProgRecords.remove(id);
}
}
@@ -347,7 +388,8 @@
ApplicationStartInfo.START_TIMESTAMP_APPLICATION_ONCREATE);
}
- void reportBindApplicationTimeNanos(ProcessRecord app, long timeNs) {
+ /** Report a bind application timestamp to add to {@link ApplicationStartInfo}. */
+ public void reportBindApplicationTimeNanos(ProcessRecord app, long timeNs) {
addTimestampToStart(app, timeNs,
ApplicationStartInfo.START_TIMESTAMP_BIND_APPLICATION);
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 4ff34b1..3156e9d 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -498,7 +498,7 @@
/** Manages the {@link android.app.ApplicationStartInfo} records. */
@GuardedBy("mAppStartInfoTracker")
- final AppStartInfoTracker mAppStartInfoTracker = new AppStartInfoTracker();
+ private final AppStartInfoTracker mAppStartInfoTracker = new AppStartInfoTracker();
/**
* The currently running SDK sandbox processes for a uid.
@@ -1523,6 +1523,10 @@
return mCachedRestoreLevel;
}
+ AppStartInfoTracker getAppStartInfoTracker() {
+ return mAppStartInfoTracker;
+ }
+
/**
* Set the out-of-memory badness adjustment for a process.
* If {@code pid <= 0}, this method will be a no-op.
@@ -2572,6 +2576,7 @@
boolean isSdkSandbox, int sdkSandboxUid, String sdkSandboxClientAppPackage,
String abiOverride, String entryPoint, String[] entryPointArgs, Runnable crashHandler) {
long startTime = SystemClock.uptimeMillis();
+ final long startTimeNs = SystemClock.elapsedRealtimeNanos();
ProcessRecord app;
if (!isolated) {
app = getProcessRecordLocked(processName, info.uid);
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 1e5e147..df179a9 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -1294,8 +1294,8 @@
if (android.content.pm.Flags.stayStopped()) {
try {
return mPackageManagerInternal.isPackageStopped(packageName, userId);
- } catch (IllegalArgumentException e) {
- Log.d(TAG, "Couldn't determine stopped state for unknown package: " + packageName);
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Couldn't determine stopped state for unknown package: " + packageName);
}
}
return false;
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 1687157..5831b29 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -194,10 +194,14 @@
addAndStartAction(new RequestActiveSourceAction(this, new IHdmiControlCallback.Stub() {
@Override
public void onComplete(int result) {
- if (result != HdmiControlManager.RESULT_SUCCESS) {
+ if (!mService.getLocalActiveSource().isValid()
+ && result != HdmiControlManager.RESULT_SUCCESS) {
mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(
getDeviceInfo().getLogicalAddress(),
getDeviceInfo().getPhysicalAddress()));
+ updateActiveSource(getDeviceInfo().getLogicalAddress(),
+ getDeviceInfo().getPhysicalAddress(),
+ "RequestActiveSourceAction#finishWithCallback()");
}
}
}));
@@ -257,6 +261,7 @@
if (isAlreadyActiveSource(targetDevice, targetAddress, callback)) {
return;
}
+ removeAction(RequestActiveSourceAction.class);
if (targetAddress == Constants.ADDR_INTERNAL) {
handleSelectInternalSource();
// Switching to internal source is always successful even when CEC control is disabled.
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index f526dbe..4089a81 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -215,10 +215,20 @@
/**
* Switch the keyboard layout in response to a keyboard shortcut.
*
- * @param direction {@code 1} to switch to the next subtype, {@code -1} to switch to the
- * previous subtype
+ * @param direction {@code 1} to switch to the next subtype, {@code -1} to switch to the
+ * previous subtype
+ * @param displayId the display to which the keyboard layout switch shortcut is
+ * dispatched. Note that there is no guarantee that an IME is
+ * associated with this display. This is more or less than a hint for
+ * cases when no IME is running for the given targetWindowToken. There
+ * is a longstanding discussion whether we should allow users to
+ * rotate keyboard layout even when there is no edit field, and this
+ * displayID would be helpful for such a situation.
+ * @param targetWindowToken the window token to which other keys are being sent while handling
+ * this shortcut.
*/
- public abstract void switchKeyboardLayout(int direction);
+ public abstract void onSwitchKeyboardLayoutShortcut(int direction, int displayId,
+ IBinder targetWindowToken);
/**
* Returns true if any InputConnection is currently active.
@@ -314,7 +324,8 @@
}
@Override
- public void switchKeyboardLayout(int direction) {
+ public void onSwitchKeyboardLayoutShortcut(int direction, int displayId,
+ IBinder targetWindowToken) {
}
@Override
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 30e9f5b..c440a64 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -5763,7 +5763,8 @@
}
@Override
- public void switchKeyboardLayout(int direction) {
+ public void onSwitchKeyboardLayoutShortcut(int direction, int displayId,
+ IBinder targetWindowToken) {
synchronized (ImfLock.class) {
switchKeyboardLayoutLocked(direction);
}
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index bab3cbe..9c27c22 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.hardware.contexthub.HostEndpointInfo;
+import android.hardware.contexthub.MessageDeliveryStatus;
import android.hardware.contexthub.NanSessionRequest;
import android.hardware.contexthub.V1_0.ContextHub;
import android.hardware.contexthub.V1_0.ContextHubMsg;
@@ -467,6 +468,11 @@
// TODO(271471342): Implement
}
+ public void handleMessageDeliveryStatus(char hostEndPointId,
+ MessageDeliveryStatus messageDeliveryStatus) {
+ // TODO(b/312417087): Implement reliable message support
+ }
+
public byte[] getUuid() {
return UUID;
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 42c2548..0c2eee5 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -209,7 +209,7 @@
* <li>Protect each user's data using their SP. For example, use the SP to encrypt/decrypt the
* user's credential-encrypted (CE) key for file-based encryption (FBE).</li>
*
- * <li>Generate, protect, and use profile passwords for managed profiles.</li>
+ * <li>Generate, protect, and use unified profile passwords.</li>
*
* <li>Support unlocking the SP by alternative means: resume-on-reboot (reboot escrow) for easier
* OTA updates, and escrow tokens when set up by the Device Policy Controller (DPC).</li>
@@ -287,7 +287,7 @@
private final java.security.KeyStore mJavaKeyStore;
private final RecoverableKeyStoreManager mRecoverableKeyStoreManager;
- private ManagedProfilePasswordCache mManagedProfilePasswordCache;
+ private final UnifiedProfilePasswordCache mUnifiedProfilePasswordCache;
private final RebootEscrowManager mRebootEscrowManager;
@@ -404,7 +404,8 @@
for (int i = 0; i < newPasswordChars.length; i++) {
newPassword[i] = (byte) newPasswordChars[i];
}
- LockscreenCredential credential = LockscreenCredential.createManagedPassword(newPassword);
+ LockscreenCredential credential =
+ LockscreenCredential.createUnifiedProfilePassword(newPassword);
Arrays.fill(newPasswordChars, '\u0000');
Arrays.fill(newPassword, (byte) 0);
Arrays.fill(randomLockSeed, (byte) 0);
@@ -424,7 +425,7 @@
if (!isCredentialSharableWithParent(profileUserId)) {
return;
}
- // Do not tie profile when work challenge is enabled
+ // Do not tie profile when separate challenge is enabled
if (getSeparateProfileChallengeEnabledInternal(profileUserId)) {
return;
}
@@ -462,7 +463,7 @@
setLockCredentialInternal(unifiedProfilePassword, profileUserPassword, profileUserId,
/* isLockTiedToParent= */ true);
tieProfileLockToParent(profileUserId, parent.id, unifiedProfilePassword);
- mManagedProfilePasswordCache.storePassword(profileUserId, unifiedProfilePassword,
+ mUnifiedProfilePasswordCache.storePassword(profileUserId, unifiedProfilePassword,
parentSid);
}
}
@@ -620,9 +621,9 @@
}
}
- public @NonNull ManagedProfilePasswordCache getManagedProfilePasswordCache(
+ public @NonNull UnifiedProfilePasswordCache getUnifiedProfilePasswordCache(
java.security.KeyStore ks) {
- return new ManagedProfilePasswordCache(ks);
+ return new UnifiedProfilePasswordCache(ks);
}
public boolean isHeadlessSystemUserMode() {
@@ -665,7 +666,7 @@
mGatekeeperPasswords = new LongSparseArray<>();
mSpManager = injector.getSyntheticPasswordManager(mStorage);
- mManagedProfilePasswordCache = injector.getManagedProfilePasswordCache(mJavaKeyStore);
+ mUnifiedProfilePasswordCache = injector.getUnifiedProfilePasswordCache(mJavaKeyStore);
mBiometricDeferredQueue = new BiometricDeferredQueue(mSpManager, mHandler);
mRebootEscrowManager = injector.getRebootEscrowManager(new RebootEscrowCallbacks(),
@@ -689,8 +690,8 @@
}
/**
- * If the account is credential-encrypted, show notification requesting the user to unlock the
- * device.
+ * If the user is a managed profile whose credential-encrypted storage is locked, show a
+ * notification requesting the user to unlock the device.
*/
private void maybeShowEncryptionNotificationForUser(@UserIdInt int userId, String reason) {
final UserInfo user = mUserManager.getUserInfo(userId);
@@ -846,7 +847,7 @@
mHandler.post(new Runnable() {
@Override
public void run() {
- // Hide notification first, as tie managed profile lock takes time
+ // Hide notification first, as tie profile lock takes time
hideEncryptionNotification(new UserHandle(userId));
if (isCredentialSharableWithParent(userId)) {
@@ -1458,13 +1459,13 @@
cipher.init(Cipher.DECRYPT_MODE, decryptionKey, new GCMParameterSpec(128, iv));
decryptionResult = cipher.doFinal(encryptedPassword);
- LockscreenCredential credential = LockscreenCredential.createManagedPassword(
+ LockscreenCredential credential = LockscreenCredential.createUnifiedProfilePassword(
decryptionResult);
Arrays.fill(decryptionResult, (byte) 0);
try {
long parentSid = getGateKeeperService().getSecureUserId(
mUserManager.getProfileParent(userId).id);
- mManagedProfilePasswordCache.storePassword(userId, credential, parentSid);
+ mUnifiedProfilePasswordCache.storePassword(userId, credential, parentSid);
} catch (RemoteException e) {
Slogf.w(TAG, "Failed to talk to GateKeeper service", e);
}
@@ -1550,7 +1551,7 @@
// so it goes into the cache
getDecryptedPasswordForTiedProfile(profile.id);
} catch (GeneralSecurityException | IOException e) {
- Slog.d(TAG, "Cache work profile password failed", e);
+ Slog.d(TAG, "Cache unified profile password failed", e);
}
}
}
@@ -1604,19 +1605,19 @@
}
/**
- * Synchronize all profile's work challenge of the given user if it's unified: tie or clear them
+ * Synchronize all profile's challenge of the given user if it's unified: tie or clear them
* depending on the parent user's secure state.
*
- * When clearing tied work challenges, a pre-computed password table for profiles are required,
- * since changing password for profiles requires existing password, and existing passwords can
- * only be computed before the parent user's password is cleared.
+ * When clearing tied challenges, a pre-computed password table for profiles are required, since
+ * changing password for profiles requires existing password, and existing passwords can only be
+ * computed before the parent user's password is cleared.
*
* Strictly this is a recursive function, since setLockCredentialInternal ends up calling this
* method again on profiles. However the recursion is guaranteed to terminate as this method
* terminates when the user is a profile that shares lock credentials with parent.
* (e.g. managed and clone profile).
*/
- private void synchronizeUnifiedWorkChallengeForProfiles(int userId,
+ private void synchronizeUnifiedChallengeForProfiles(int userId,
Map<Integer, LockscreenCredential> profilePasswordMap) {
if (isCredentialSharableWithParent(userId)) {
return;
@@ -1635,7 +1636,7 @@
tieProfileLockIfNecessary(profileUserId,
LockscreenCredential.createNone());
} else {
- // We use cached work profile password computed before clearing the parent's
+ // We use cached profile password computed before clearing the parent's
// credential, otherwise they get lost
if (profilePasswordMap != null
&& profilePasswordMap.containsKey(profileUserId)) {
@@ -1777,7 +1778,7 @@
notifyPasswordChanged(credential, userId);
}
if (isCredentialSharableWithParent(userId)) {
- // Make sure the profile doesn't get locked straight after setting work challenge.
+ // Make sure the profile doesn't get locked straight after setting challenge.
setDeviceUnlockedForUser(userId);
}
notifySeparateProfileChallengeChanged(userId);
@@ -2368,7 +2369,7 @@
}
try {
- // Unlock work profile, and work profile with unified lock must use password only
+ // Unlock profile with unified lock
return doVerifyCredential(getDecryptedPasswordForTiedProfile(userId),
userId, null /* progressCallback */, flags);
} catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
@@ -2492,7 +2493,7 @@
mStrongAuth.removeUser(userId);
AndroidKeyStoreMaintenance.onUserRemoved(userId);
- mManagedProfilePasswordCache.removePassword(userId);
+ mUnifiedProfilePasswordCache.removePassword(userId);
gateKeeperClearSecureUserId(userId);
removeKeystoreProfileKey(userId);
@@ -2982,7 +2983,7 @@
credential, sp, userId);
final Map<Integer, LockscreenCredential> profilePasswords;
if (!credential.isNone()) {
- // not needed by synchronizeUnifiedWorkChallengeForProfiles()
+ // not needed by synchronizeUnifiedChallengeForProfiles()
profilePasswords = null;
if (!mSpManager.hasSidForUser(userId)) {
@@ -2993,8 +2994,8 @@
}
}
} else {
- // Cache all profile password if they use unified work challenge. This will later be
- // used to clear the profile's password in synchronizeUnifiedWorkChallengeForProfiles()
+ // Cache all profile password if they use unified challenge. This will later be used to
+ // clear the profile's password in synchronizeUnifiedChallengeForProfiles().
profilePasswords = getDecryptedPasswordsForAllTiedProfiles(userId);
mSpManager.clearSidForUser(userId);
@@ -3010,10 +3011,10 @@
}
setCurrentLskfBasedProtectorId(newProtectorId, userId);
LockPatternUtils.invalidateCredentialTypeCache();
- synchronizeUnifiedWorkChallengeForProfiles(userId, profilePasswords);
+ synchronizeUnifiedChallengeForProfiles(userId, profilePasswords);
setUserPasswordMetrics(credential, userId);
- mManagedProfilePasswordCache.removePassword(userId);
+ mUnifiedProfilePasswordCache.removePassword(userId);
if (savedCredentialType != CREDENTIAL_TYPE_NONE) {
mSpManager.destroyAllWeakTokenBasedProtectors(userId);
}
@@ -3114,7 +3115,7 @@
try {
currentCredential = getDecryptedPasswordForTiedProfile(userId);
} catch (Exception e) {
- Slog.e(TAG, "Failed to get work profile credential", e);
+ Slog.e(TAG, "Failed to get unified profile password", e);
return null;
}
}
@@ -3284,7 +3285,7 @@
@Override
public boolean tryUnlockWithCachedUnifiedChallenge(int userId) {
checkPasswordReadPermission();
- try (LockscreenCredential cred = mManagedProfilePasswordCache.retrievePassword(userId)) {
+ try (LockscreenCredential cred = mUnifiedProfilePasswordCache.retrievePassword(userId)) {
if (cred == null) {
return false;
}
@@ -3296,7 +3297,7 @@
@Override
public void removeCachedUnifiedChallenge(int userId) {
checkWritePermission();
- mManagedProfilePasswordCache.removePassword(userId);
+ mUnifiedProfilePasswordCache.removePassword(userId);
}
static String timestampToString(long timestamp) {
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index 1e8b387..6d123cc 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -501,10 +501,10 @@
final UserInfo parentInfo = um.getProfileParent(userId);
if (parentInfo == null) {
- // This user owns its lock settings files - safe to delete them
+ // Delete files specific to non-profile users.
deleteFile(getRebootEscrowFile(userId));
} else {
- // Managed profile
+ // Delete files specific to profile users.
removeChildProfileLock(userId);
}
diff --git a/services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java b/services/core/java/com/android/server/locksettings/UnifiedProfilePasswordCache.java
similarity index 84%
rename from services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java
rename to services/core/java/com/android/server/locksettings/UnifiedProfilePasswordCache.java
index 1298fe8f..21caf76 100644
--- a/services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java
+++ b/services/core/java/com/android/server/locksettings/UnifiedProfilePasswordCache.java
@@ -43,30 +43,31 @@
import javax.crypto.spec.GCMParameterSpec;
/**
- * Caches *unified* work challenge for managed profiles. The cached credential is encrypted using
- * a keystore key auth-bound to the parent user's lockscreen credential, similar to how unified
- * work challenge is normally secured.
- *
- * <p> The cache is filled whenever the managed profile's unified challenge is created or derived
- * (as part of the parent user's credential verification flow). It's removed when the profile is
- * deleted or a (separate) lockscreen credential is explicitly set on the profile. There is also
- * an ADB command to evict the cache "cmd lock_settings remove-cache --user X", to assist
- * development and testing.
-
- * <p> The encrypted credential is stored in-memory only so the cache does not persist across
- * reboots.
+ * An in-memory cache for unified profile passwords. A "unified profile password" is the random
+ * password that the system automatically generates and manages for each profile that uses a unified
+ * challenge and where the parent user has a secure lock screen.
+ * <p>
+ * Each password in this cache is encrypted by a Keystore key that is auth-bound to the parent user.
+ * This is very similar to how the password is protected on-disk, but the in-memory cache uses a
+ * much longer timeout on the keys: 7 days instead of 30 seconds. This enables use cases like
+ * unpausing work apps without requiring authentication as frequently.
+ * <p>
+ * Unified profile passwords are cached when they are created, or when they are decrypted as part of
+ * the parent user's LSKF verification flow. They are removed when the profile is deleted or when a
+ * separate challenge is explicitly set on the profile. There is also an ADB command to evict a
+ * cached password, "locksettings remove-cache --user X", to assist development and testing.
*/
@VisibleForTesting // public visibility is needed for Mockito
-public class ManagedProfilePasswordCache {
+public class UnifiedProfilePasswordCache {
- private static final String TAG = "ManagedProfilePasswordCache";
+ private static final String TAG = "UnifiedProfilePasswordCache";
private static final int KEY_LENGTH = 256;
private static final int CACHE_TIMEOUT_SECONDS = (int) TimeUnit.DAYS.toSeconds(7);
private final SparseArray<byte[]> mEncryptedPasswords = new SparseArray<>();
private final KeyStore mKeyStore;
- public ManagedProfilePasswordCache(KeyStore keyStore) {
+ public UnifiedProfilePasswordCache(KeyStore keyStore) {
mKeyStore = keyStore;
}
@@ -151,7 +152,8 @@
Slog.d(TAG, "Cannot decrypt", e);
return null;
}
- LockscreenCredential result = LockscreenCredential.createManagedPassword(credential);
+ LockscreenCredential result =
+ LockscreenCredential.createUnifiedProfilePassword(credential);
Arrays.fill(credential, (byte) 0);
return result;
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index a62d8b8..66a9740 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -5406,6 +5406,7 @@
private void validateAutomaticZenRule(AutomaticZenRule rule) {
Objects.requireNonNull(rule, "automaticZenRule is null");
Objects.requireNonNull(rule.getName(), "Name is null");
+ rule.validate();
if (rule.getOwner() == null
&& rule.getConfigurationActivity() == null) {
throw new NullPointerException(
@@ -5463,6 +5464,7 @@
public void setAutomaticZenRuleState(String id, Condition condition) {
Objects.requireNonNull(id, "id is null");
Objects.requireNonNull(condition, "Condition is null");
+ condition.validate();
enforcePolicyAccess(Binder.getCallingUid(), "setAutomaticZenRuleState");
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 7ec94c3..3f8b595 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -627,6 +627,7 @@
try {
ApplicationInfo applicationInfo = mPm.getApplicationInfo(pkg, 0);
rule.name = applicationInfo.loadLabel(mPm).toString();
+ rule.iconResName = drawableResIdToResName(pkg, applicationInfo.icon);
} catch (PackageManager.NameNotFoundException e) {
// Should not happen, since it's the app calling us (?)
Log.w(TAG, "Package not found for creating implicit zen rule");
@@ -634,6 +635,9 @@
}
});
+ rule.type = AutomaticZenRule.TYPE_OTHER;
+ rule.triggerDescription = mContext.getString(R.string.zen_mode_implicit_trigger_description,
+ rule.name);
rule.condition = null;
rule.conditionId = new Uri.Builder()
.scheme(Condition.SCHEME)
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index bd725ed..27f4e11 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -508,12 +508,15 @@
boolean getApplicationHiddenSettingAsUser(@NonNull String packageName, @UserIdInt int userId);
- boolean isPackageSuspendedForUser(@NonNull String packageName, @UserIdInt int userId);
+ boolean isPackageSuspendedForUser(@NonNull String packageName, @UserIdInt int userId)
+ throws PackageManager.NameNotFoundException;
- boolean isPackageQuarantinedForUser(@NonNull String packageName, @UserIdInt int userId);
+ boolean isPackageQuarantinedForUser(@NonNull String packageName, @UserIdInt int userId)
+ throws PackageManager.NameNotFoundException;
/** Check if the package is in a stopped state for a given user. */
- boolean isPackageStoppedForUser(@NonNull String packageName, @UserIdInt int userId);
+ boolean isPackageStoppedForUser(@NonNull String packageName, @UserIdInt int userId)
+ throws PackageManager.NameNotFoundException;
boolean isSuspendingAnyPackages(@NonNull String suspendingPackage, @UserIdInt int userId);
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 2dc3fb7..abfd571 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -4980,31 +4980,34 @@
}
}
- private PackageUserStateInternal getUserStageOrDefaultForUser(@NonNull String packageName,
- int userId) {
+ private PackageUserStateInternal getUserStateOrDefaultForUser(@NonNull String packageName,
+ int userId) throws PackageManager.NameNotFoundException {
final int callingUid = Binder.getCallingUid();
enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */,
false /* checkShell */, "when asking about packages for user " + userId);
final PackageStateInternal ps = mSettings.getPackage(packageName);
if (ps == null || shouldFilterApplicationIncludingUninstalled(ps, callingUid, userId)) {
- throw new IllegalArgumentException("Unknown target package: " + packageName);
+ throw new PackageManager.NameNotFoundException(packageName);
}
return ps.getUserStateOrDefault(userId);
}
@Override
- public boolean isPackageSuspendedForUser(@NonNull String packageName, int userId) {
- return getUserStageOrDefaultForUser(packageName, userId).isSuspended();
+ public boolean isPackageSuspendedForUser(@NonNull String packageName, int userId)
+ throws PackageManager.NameNotFoundException {
+ return getUserStateOrDefaultForUser(packageName, userId).isSuspended();
}
@Override
- public boolean isPackageQuarantinedForUser(@NonNull String packageName, @UserIdInt int userId) {
- return getUserStageOrDefaultForUser(packageName, userId).isQuarantined();
+ public boolean isPackageQuarantinedForUser(@NonNull String packageName, @UserIdInt int userId)
+ throws PackageManager.NameNotFoundException {
+ return getUserStateOrDefaultForUser(packageName, userId).isQuarantined();
}
@Override
- public boolean isPackageStoppedForUser(@NonNull String packageName, @UserIdInt int userId) {
- return getUserStageOrDefaultForUser(packageName, userId).isStopped();
+ public boolean isPackageStoppedForUser(@NonNull String packageName, @UserIdInt int userId)
+ throws PackageManager.NameNotFoundException {
+ return getUserStateOrDefaultForUser(packageName, userId).isStopped();
}
@Override
diff --git a/services/core/java/com/android/server/pm/IPackageManagerBase.java b/services/core/java/com/android/server/pm/IPackageManagerBase.java
index 24a33f1..e3bbd2d 100644
--- a/services/core/java/com/android/server/pm/IPackageManagerBase.java
+++ b/services/core/java/com/android/server/pm/IPackageManagerBase.java
@@ -951,20 +951,32 @@
@Deprecated
public final boolean isPackageSuspendedForUser(@NonNull String packageName,
@UserIdInt int userId) {
- return snapshot().isPackageSuspendedForUser(packageName, userId);
+ try {
+ return snapshot().isPackageSuspendedForUser(packageName, userId);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new IllegalArgumentException("Unknown target package: " + packageName);
+ }
}
@Override
@Deprecated
public final boolean isPackageQuarantinedForUser(@NonNull String packageName,
@UserIdInt int userId) {
- return snapshot().isPackageQuarantinedForUser(packageName, userId);
+ try {
+ return snapshot().isPackageQuarantinedForUser(packageName, userId);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new IllegalArgumentException("Unknown target package: " + packageName);
+ }
}
@Override
public final boolean isPackageStoppedForUser(@NonNull String packageName,
@UserIdInt int userId) {
- return snapshot().isPackageStoppedForUser(packageName, userId);
+ try {
+ return snapshot().isPackageStoppedForUser(packageName, userId);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new IllegalArgumentException("Unknown target package: " + packageName);
+ }
}
@Override
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 58c1c05..ba66377 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -1575,6 +1575,30 @@
}
@Override
+ public List<String> getPreInstalledSystemPackages(UserHandle user) {
+ // Only system launchers, which have access to recents should have access to this API.
+ // TODO(b/303803157): Update access control for this API to default Launcher app.
+ if (!mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid())) {
+ throw new SecurityException("Caller is not the recents app");
+ }
+ if (!canAccessProfile(user.getIdentifier(),
+ "Can't access preinstalled packages for another user")) {
+ return null;
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ String userType = mUm.getUserInfo(user.getIdentifier()).userType;
+ Set<String> preInstalledPackages = mUm.getPreInstallableSystemPackages(userType);
+ if (preInstalledPackages == null) {
+ return new ArrayList<>();
+ }
+ return List.copyOf(preInstalledPackages);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void startActivityAsUser(IApplicationThread caller, String callingPackage,
String callingFeatureId, ComponentName component, Rect sourceBounds,
Bundle opts, UserHandle user) throws RemoteException {
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index e3bab3f..6b05edf 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -323,6 +323,7 @@
PackageStateInternal ps = getPackageState(packageName, snapshot,
Binder.getCallingUid(), userId);
verifyNotSystemApp(ps.getFlags());
+ verifyInstalled(ps, userId);
String responsibleInstallerPackage = getResponsibleInstallerPackage(ps);
verifyInstaller(responsibleInstallerPackage, userId);
ApplicationInfo installerInfo = snapshot.getApplicationInfo(
@@ -476,6 +477,14 @@
}
}
+ private void verifyInstalled(PackageStateInternal ps, int userId)
+ throws PackageManager.NameNotFoundException {
+ if (!ps.getUserStateOrDefault(userId).isInstalled()) {
+ throw new PackageManager.NameNotFoundException(
+ TextUtils.formatSimple("%s is not installed.", ps.getPackageName()));
+ }
+ }
+
/**
* Returns true if the app is archivable.
*/
@@ -519,11 +528,11 @@
/**
* Returns true if user has opted the app out of archiving through system settings.
*/
- // TODO(b/304256918) Switch this to a separate OP code for archiving.
private boolean isAppOptedOutOfArchiving(String packageName, int uid) {
return Binder.withCleanCallingIdentity(() ->
- getAppOpsManager().checkOp(AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED,
- uid, packageName) == MODE_IGNORED);
+ getAppOpsManager().checkOpNoThrow(
+ AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, uid, packageName)
+ == MODE_IGNORED);
}
private void verifyOptOutStatus(String packageName, int uid)
@@ -676,23 +685,51 @@
PackageStateInternal ps;
try {
ps = getPackageState(packageName, snapshot, callingUid, userId);
- snapshot.enforceCrossUserPermission(callingUid, userId, true, false,
- "getArchivedAppIcon");
- verifyArchived(ps, userId);
} catch (PackageManager.NameNotFoundException e) {
- throw new ParcelableException(e);
+ Slog.e(TAG, TextUtils.formatSimple("Package %s couldn't be found.", packageName), e);
+ return null;
}
- List<ArchiveActivityInfo> activityInfos = ps.getUserStateOrDefault(
- userId).getArchiveState().getActivityInfos();
- if (activityInfos.size() == 0) {
+ ArchiveState archiveState = getAnyArchiveState(ps, userId);
+ if (archiveState == null || archiveState.getActivityInfos().size() == 0) {
return null;
}
// TODO(b/298452477) Handle monochrome icons.
// In the rare case the archived app defined more than two launcher activities, we choose
// the first one arbitrarily.
- return includeCloudOverlay(decodeIcon(activityInfos.get(0)));
+ return includeCloudOverlay(decodeIcon(archiveState.getActivityInfos().get(0)));
+ }
+
+ /**
+ * This method first checks the ArchiveState for the provided userId and then tries to fallback
+ * to other users if the current user is not archived.
+ *
+ * <p> This fallback behaviour is required for archived apps to fit into the multi-user world
+ * where APKs are shared across users. E.g. current ways of fetching icons for apps that are
+ * only installed on the work profile also work when executed on the personal profile if you're
+ * using {@link PackageManager#MATCH_UNINSTALLED_PACKAGES}. Resource fetching from APKs is for
+ * the most part userId-agnostic, which we need to mimic here in order for existing methods
+ * like {@link PackageManager#getApplicationIcon} to continue working.
+ *
+ * @return {@link ArchiveState} for {@code userId} if present. If not present, false back to an
+ * arbitrary userId. If no user is archived, returns null.
+ */
+ @Nullable
+ private ArchiveState getAnyArchiveState(PackageStateInternal ps, int userId) {
+ PackageUserStateInternal userState = ps.getUserStateOrDefault(userId);
+ if (isArchived(userState)) {
+ return userState.getArchiveState();
+ }
+
+ for (int i = 0; i < ps.getUserStates().size(); i++) {
+ userState = ps.getUserStates().valueAt(i);
+ if (isArchived(userState)) {
+ return userState.getArchiveState();
+ }
+ }
+
+ return null;
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
index 1e7d043..c737b45 100644
--- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
+++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
@@ -762,13 +762,14 @@
}
@Override
- public boolean isPackageQuarantined(@NonNull String packageName,
- @UserIdInt int userId) {
+ public boolean isPackageQuarantined(@NonNull String packageName, @UserIdInt int userId)
+ throws PackageManager.NameNotFoundException {
return snapshot().isPackageQuarantinedForUser(packageName, userId);
}
@Override
- public boolean isPackageStopped(@NonNull String packageName, @UserIdInt int userId) {
+ public boolean isPackageStopped(@NonNull String packageName, @UserIdInt int userId)
+ throws PackageManager.NameNotFoundException {
return snapshot().isPackageStoppedForUser(packageName, userId);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 84d4754..56365b6 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -5252,14 +5252,18 @@
@Override
public String getSuspendingPackage(String packageName, int userId) {
- final int callingUid = Binder.getCallingUid();
- final Computer snapshot = snapshot();
- // This will do visibility checks as well.
- if (!snapshot.isPackageSuspendedForUser(packageName, userId)) {
+ try {
+ final int callingUid = Binder.getCallingUid();
+ final Computer snapshot = snapshot();
+ // This will do visibility checks as well.
+ if (!snapshot.isPackageSuspendedForUser(packageName, userId)) {
+ return null;
+ }
+ return mSuspendPackageHelper.getSuspendingPackage(snapshot, packageName, userId,
+ callingUid);
+ } catch (PackageManager.NameNotFoundException e) {
return null;
}
- return mSuspendPackageHelper.getSuspendingPackage(snapshot, packageName, userId,
- callingUid);
}
@Override
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index a7b52f4..c48eccf 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -532,9 +532,10 @@
}
final IntentSender target = intent.getParcelableExtra(Intent.EXTRA_INTENT, android.content.IntentSender.class);
final int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, UserHandle.USER_NULL);
+ final String callingPackage = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
// Call setQuietModeEnabled on bg thread to avoid ANR
BackgroundThread.getHandler().post(() ->
- setQuietModeEnabled(userId, false, target, /* callingPackage */ null));
+ setQuietModeEnabled(userId, false, target, callingPackage));
}
};
@@ -1410,7 +1411,7 @@
if (onlyIfCredentialNotRequired) {
return false;
}
- showConfirmCredentialToDisableQuietMode(userId, target);
+ showConfirmCredentialToDisableQuietMode(userId, target, callingPackage);
return false;
}
}
@@ -1434,7 +1435,7 @@
if (onlyIfCredentialNotRequired) {
return false;
}
- showConfirmCredentialToDisableQuietMode(userId, target);
+ showConfirmCredentialToDisableQuietMode(userId, target, callingPackage);
return false;
}
setQuietModeEnabled(userId, false /* enableQuietMode */, target, callingPackage);
@@ -1604,7 +1605,7 @@
* Show confirm credential screen to unlock user in order to turn off quiet mode.
*/
private void showConfirmCredentialToDisableQuietMode(
- @UserIdInt int userId, @Nullable IntentSender target) {
+ @UserIdInt int userId, @Nullable IntentSender target, @Nullable String callingPackage) {
if (android.app.admin.flags.Flags.quietModeCredentialBugFix()) {
// TODO (b/308121702) It may be brittle to rely on user states to check profile state
int state;
@@ -1635,6 +1636,7 @@
}
callBackIntent.putExtra(Intent.EXTRA_USER_ID, userId);
callBackIntent.setPackage(mContext.getPackageName());
+ callBackIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, callingPackage);
callBackIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
final PendingIntent pendingIntent = PendingIntent.getBroadcast(
mContext,
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index fed32e5..938ed23 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -808,7 +808,10 @@
handleScreenShot(msg.arg1);
break;
case MSG_SWITCH_KEYBOARD_LAYOUT:
- handleSwitchKeyboardLayout(msg.arg1, msg.arg2);
+ SwitchKeyboardLayoutMessageObject object =
+ (SwitchKeyboardLayoutMessageObject) msg.obj;
+ handleSwitchKeyboardLayout(object.keyEvent, object.direction,
+ object.focusedToken);
break;
case MSG_LOG_KEYBOARD_SYSTEM_EVENT:
handleKeyboardSystemEvent(KeyboardLogEvent.from(msg.arg1), (KeyEvent) msg.obj);
@@ -929,6 +932,10 @@
}
}
+ private record SwitchKeyboardLayoutMessageObject(KeyEvent keyEvent, IBinder focusedToken,
+ int direction) {
+ }
+
final IPersistentVrStateCallbacks mPersistentVrModeListener =
new IPersistentVrStateCallbacks.Stub() {
@Override
@@ -3641,7 +3648,7 @@
case KeyEvent.KEYCODE_LANGUAGE_SWITCH:
if (firstDown) {
int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
- sendSwitchKeyboardLayout(event, direction);
+ sendSwitchKeyboardLayout(event, focusedToken, direction);
logKeyboardSystemsEvent(event, KeyboardLogEvent.LANGUAGE_SWITCH);
return true;
}
@@ -3910,7 +3917,7 @@
+ ", policyFlags=" + policyFlags);
}
- if (interceptUnhandledKey(event)) {
+ if (interceptUnhandledKey(event, focusedToken)) {
return null;
}
@@ -3968,7 +3975,7 @@
return fallbackEvent;
}
- private boolean interceptUnhandledKey(KeyEvent event) {
+ private boolean interceptUnhandledKey(KeyEvent event, IBinder focusedToken) {
final int keyCode = event.getKeyCode();
final int repeatCount = event.getRepeatCount();
final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
@@ -3981,7 +3988,7 @@
if (KeyEvent.metaStateHasModifiers(metaState & ~KeyEvent.META_SHIFT_MASK,
KeyEvent.META_CTRL_ON)) {
int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
- sendSwitchKeyboardLayout(event, direction);
+ sendSwitchKeyboardLayout(event, focusedToken, direction);
return true;
}
}
@@ -4037,16 +4044,22 @@
}
}
- private void sendSwitchKeyboardLayout(@NonNull KeyEvent event, int direction) {
- mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, event.getDeviceId(),
- direction).sendToTarget();
+ private void sendSwitchKeyboardLayout(@NonNull KeyEvent event,
+ @Nullable IBinder focusedToken, int direction) {
+ SwitchKeyboardLayoutMessageObject object =
+ new SwitchKeyboardLayoutMessageObject(event, focusedToken, direction);
+ mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, object).sendToTarget();
}
- private void handleSwitchKeyboardLayout(int deviceId, int direction) {
+ private void handleSwitchKeyboardLayout(@NonNull KeyEvent event, int direction,
+ IBinder focusedToken) {
if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI)) {
- InputMethodManagerInternal.get().switchKeyboardLayout(direction);
+ IBinder targetWindowToken =
+ mWindowManagerInternal.getTargetWindowTokenFromInputToken(focusedToken);
+ InputMethodManagerInternal.get().onSwitchKeyboardLayoutShortcut(direction,
+ event.getDisplayId(), targetWindowToken);
} else {
- mWindowManagerFuncs.switchKeyboardLayout(deviceId, direction);
+ mWindowManagerFuncs.switchKeyboardLayout(event.getDeviceId(), direction);
}
}
@@ -4056,7 +4069,7 @@
if ((actions & ACTION_PASS_TO_USER) != 0) {
long delayMillis = interceptKeyBeforeDispatching(
focusedToken, fallbackEvent, policyFlags);
- if (delayMillis == 0 && !interceptUnhandledKey(fallbackEvent)) {
+ if (delayMillis == 0 && !interceptUnhandledKey(fallbackEvent, focusedToken)) {
return true;
}
}
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index eac4fc0..9a85c42 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -1608,14 +1608,12 @@
user.name, user.id, user.flags);
if (!user.supportsSwitchToByUser()) {
final boolean locked;
- if (user.isProfile()) {
- if (mLockPatternUtils.isSeparateProfileChallengeEnabled(user.id)) {
- fout.print(" (profile with separate challenge)");
- locked = isDeviceLockedInner(user.id);
- } else {
- fout.print(" (profile with unified challenge)");
- locked = isDeviceLockedInner(resolveProfileParent(user.id));
- }
+ if (mLockPatternUtils.isProfileWithUnifiedChallenge(user.id)) {
+ fout.print(" (profile with unified challenge)");
+ locked = isDeviceLockedInner(resolveProfileParent(user.id));
+ } else if (mLockPatternUtils.isSeparateProfileChallengeEnabled(user.id)) {
+ fout.print(" (profile with separate challenge)");
+ locked = isDeviceLockedInner(user.id);
} else {
fout.println(" (user that cannot be switched to)");
locked = isDeviceLockedInner(user.id);
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java b/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java
index 81e5fbd..769f01c 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java
@@ -142,8 +142,11 @@
* if the launching activity is started from an existing launch sequence (trampoline)
* but cannot coalesce to the existing one, e.g. to a different display.
* @param name The launching activity name.
+ * @param temperature The temperature at which a launch sequence had started.
+ * @param userId The id of the user the activity is being launched for.
*/
- public void onActivityLaunched(long id, ComponentName name, @Temperature int temperature) {
+ public void onActivityLaunched(long id, ComponentName name, @Temperature int temperature,
+ int userId) {
}
/**
@@ -177,13 +180,15 @@
* @param timestampNanos the timestamp of ActivityLaunchFinished event in nanoseconds.
* To compute the TotalTime duration, deduct the timestamp {@link #onIntentStarted}
* from {@code timestampNanos}.
+ * @param launchMode The activity launch mode.
*
* @apiNote The finishing activity isn't necessarily the same as the starting activity;
* in the case of a trampoline, multiple activities could've been started
* and only the latest activity that was top-most during first-frame drawn
* is reported here.
*/
- public void onActivityLaunchFinished(long id, ComponentName name, long timestampNanos) {
+ public void onActivityLaunchFinished(long id, ComponentName name, long timestampNanos,
+ int launchMode) {
}
/**
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 7b20529..78f501a 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -1737,7 +1737,8 @@
// Beginning a launch is timing sensitive and so should be observed as soon as possible.
mLaunchObserver.onActivityLaunched(info.mLaunchingState.mStartUptimeNs,
- info.mLastLaunchedActivity.mActivityComponent, temperature);
+ info.mLastLaunchedActivity.mActivityComponent, temperature,
+ info.mLastLaunchedActivity.mUserId);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -1774,7 +1775,8 @@
"MetricsLogger:launchObserverNotifyActivityLaunchFinished");
mLaunchObserver.onActivityLaunchFinished(info.mLaunchingState.mStartUptimeNs,
- info.mLastLaunchedActivity.mActivityComponent, timestampNs);
+ info.mLastLaunchedActivity.mActivityComponent, timestampNs,
+ info.mLastLaunchedActivity.launchMode);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index f8fda91..869bcc0 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -1275,7 +1275,6 @@
@Nullable String callingFeatureId, Intent intent, String resolvedType,
IBinder resultTo, String resultWho, int requestCode, int startFlags,
ProfilerInfo profilerInfo, Bundle bOptions, int userId, boolean validateIncomingUser) {
-
final SafeActivityOptions opts = SafeActivityOptions.fromBundle(bOptions);
assertPackageMatchesCallingUid(callingPackage);
@@ -1316,7 +1315,6 @@
.setActivityOptions(opts)
.setUserId(userId)
.execute();
-
}
@Override
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 92665af..4929df80 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -20,6 +20,7 @@
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
import static android.os.Process.SYSTEM_UID;
import static android.provider.DeviceConfig.NAMESPACE_WINDOW_MANAGER;
@@ -49,7 +50,6 @@
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager;
-import android.os.Build;
import android.os.Process;
import android.os.UserHandle;
import android.provider.DeviceConfig;
@@ -86,7 +86,7 @@
private static final int NO_PROCESS_UID = -1;
/** If enabled the creator will not allow BAL on its behalf by default. */
@ChangeId
- @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @EnabledAfter(targetSdkVersion = UPSIDE_DOWN_CAKE)
private static final long DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_CREATOR =
296478951;
public static final ActivityOptions ACTIVITY_OPTIONS_SYSTEM_DEFINED =
@@ -228,6 +228,7 @@
private final Intent mIntent;
private final WindowProcessController mCallerApp;
private final WindowProcessController mRealCallerApp;
+ private final boolean mIsCallForResult;
private BalState(int callingUid, int callingPid, final String callingPackage,
int realCallingUid, int realCallingPid,
@@ -247,8 +248,10 @@
mOriginatingPendingIntent = originatingPendingIntent;
mIntent = intent;
mRealCallingPackage = mService.getPackageNameIfUnique(realCallingUid, realCallingPid);
- if (originatingPendingIntent == null // not a PendingIntent
- || resultRecord != null // sent for result
+ mIsCallForResult = resultRecord != null;
+ if (balRequireOptInByPendingIntentCreator() // auto-opt in introduced with this feature
+ && (originatingPendingIntent == null // not a PendingIntent
+ || mIsCallForResult) // sent for result
) {
// grant BAL privileges unless explicitly opted out
mBalAllowedByPiCreatorWithHardening = mBalAllowedByPiCreator =
@@ -351,6 +354,19 @@
return name + "[debugOnly]";
}
+ /** @return valid targetSdk or <code>-1</code> */
+ private int getTargetSdk(String packageName) {
+ if (packageName == null) {
+ return -1;
+ }
+ try {
+ PackageManager pm = mService.mContext.getPackageManager();
+ return pm.getTargetSdkVersion(packageName);
+ } catch (Exception e) {
+ return -1;
+ }
+ }
+
private boolean hasRealCaller() {
return mRealCallingUid != NO_PROCESS_UID;
}
@@ -368,6 +384,7 @@
StringBuilder sb = new StringBuilder(2048);
sb.append("[callingPackage: ")
.append(getDebugPackageName(mCallingPackage, mCallingUid));
+ sb.append("; callingPackageTargetSdk: ").append(getTargetSdk(mCallingPackage));
sb.append("; callingUid: ").append(mCallingUid);
sb.append("; callingPid: ").append(mCallingPid);
sb.append("; appSwitchState: ").append(mAppSwitchState);
@@ -387,10 +404,13 @@
.append(mBalAllowedByPiCreatorWithHardening);
sb.append("; resultIfPiCreatorAllowsBal: ").append(resultIfPiCreatorAllowsBal);
sb.append("; hasRealCaller: ").append(hasRealCaller());
+ sb.append("; isCallForResult: ").append(mIsCallForResult);
sb.append("; isPendingIntent: ").append(isPendingIntent());
if (hasRealCaller()) {
sb.append("; realCallingPackage: ")
.append(getDebugPackageName(mRealCallingPackage, mRealCallingUid));
+ sb.append("; realCallingPackageTargetSdk: ")
+ .append(getTargetSdk(mRealCallingPackage));
sb.append("; realCallingUid: ").append(mRealCallingUid);
sb.append("; realCallingPid: ").append(mRealCallingPid);
sb.append("; realCallingUidHasAnyVisibleWindow: ")
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index 4a3d0c1..32d60c5 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static com.android.input.flags.Flags.enablePointerChoreographer;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -23,6 +24,7 @@
import android.annotation.NonNull;
import android.content.ClipData;
import android.content.Context;
+import android.hardware.input.InputManagerGlobal;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -31,6 +33,8 @@
import android.util.Slog;
import android.view.Display;
import android.view.IWindow;
+import android.view.InputDevice;
+import android.view.PointerIcon;
import android.view.SurfaceControl;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
@@ -97,8 +101,8 @@
}
IBinder performDrag(int callerPid, int callerUid, IWindow window, int flags,
- SurfaceControl surface, int touchSource, float touchX, float touchY,
- float thumbCenterX, float thumbCenterY, ClipData data) {
+ SurfaceControl surface, int touchSource, int touchDeviceId, int touchPointerId,
+ float touchX, float touchY, float thumbCenterX, float thumbCenterY, ClipData data) {
if (DEBUG_DRAG) {
Slog.d(TAG_WM, "perform drag: win=" + window + " surface=" + surface + " flags=" +
Integer.toHexString(flags) + " data=" + data + " touch(" + touchX + ","
@@ -208,7 +212,17 @@
final SurfaceControl surfaceControl = mDragState.mSurfaceControl;
mDragState.broadcastDragStartedLocked(touchX, touchY);
- mDragState.overridePointerIconLocked(touchSource);
+ if (enablePointerChoreographer()) {
+ if ((touchSource & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) {
+ InputManagerGlobal.getInstance().setPointerIcon(
+ PointerIcon.getSystemIcon(
+ mService.mContext, PointerIcon.TYPE_GRABBING),
+ mDragState.mDisplayContent.getDisplayId(), touchDeviceId,
+ touchPointerId, mDragState.getInputToken());
+ }
+ } else {
+ mDragState.overridePointerIconLocked(touchSource);
+ }
// remember the thumb offsets for later
mDragState.mThumbOffsetX = thumbCenterX;
mDragState.mThumbOffsetY = thumbCenterY;
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index a888f84..adbe3bc 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -416,6 +416,13 @@
return mInputInterceptor == null ? null : mInputInterceptor.mDragWindowHandle;
}
+ IBinder getInputToken() {
+ if (mInputInterceptor == null || mInputInterceptor.mClientChannel == null) {
+ return null;
+ }
+ return mInputInterceptor.mClientChannel.getToken();
+ }
+
/**
* @param display The Display that the window being dragged is on.
*/
diff --git a/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java b/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java
index 9cbc1bd..4c73a41 100644
--- a/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java
+++ b/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java
@@ -84,10 +84,10 @@
}
@Override
- public void onActivityLaunched(long id, ComponentName name, int temperature) {
+ public void onActivityLaunched(long id, ComponentName name, int temperature, int userId) {
mHandler.sendMessage(PooledLambda.obtainMessage(
LaunchObserverRegistryImpl::handleOnActivityLaunched,
- this, id, name, temperature));
+ this, id, name, temperature, userId));
}
@Override
@@ -97,10 +97,11 @@
}
@Override
- public void onActivityLaunchFinished(long id, ComponentName name, long timestampNs) {
+ public void onActivityLaunchFinished(long id, ComponentName name, long timestampNs,
+ int launchMode) {
mHandler.sendMessage(PooledLambda.obtainMessage(
LaunchObserverRegistryImpl::handleOnActivityLaunchFinished,
- this, id, name, timestampNs));
+ this, id, name, timestampNs, launchMode));
}
@Override
@@ -137,10 +138,10 @@
}
private void handleOnActivityLaunched(long id, ComponentName name,
- @Temperature int temperature) {
+ @Temperature int temperature, int userId) {
// Traverse start-to-end to meet the registerLaunchObserver multi-cast order guarantee.
for (int i = 0; i < mList.size(); i++) {
- mList.get(i).onActivityLaunched(id, name, temperature);
+ mList.get(i).onActivityLaunched(id, name, temperature, userId);
}
}
@@ -151,10 +152,11 @@
}
}
- private void handleOnActivityLaunchFinished(long id, ComponentName name, long timestampNs) {
+ private void handleOnActivityLaunchFinished(long id, ComponentName name, long timestampNs,
+ int launchMode) {
// Traverse start-to-end to meet the registerLaunchObserver multi-cast order guarantee.
for (int i = 0; i < mList.size(); i++) {
- mList.get(i).onActivityLaunchFinished(id, name, timestampNs);
+ mList.get(i).onActivityLaunchFinished(id, name, timestampNs, launchMode);
}
}
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 56f9aa4..57939bc 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -340,7 +340,8 @@
@Override
public IBinder performDrag(IWindow window, int flags, SurfaceControl surface, int touchSource,
- float touchX, float touchY, float thumbCenterX, float thumbCenterY, ClipData data) {
+ int touchDeviceId, int touchPointerId, float touchX, float touchY, float thumbCenterX,
+ float thumbCenterY, ClipData data) {
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
// Validate and resolve ClipDescription data before clearing the calling identity
@@ -349,7 +350,8 @@
final long ident = Binder.clearCallingIdentity();
try {
return mDragDropController.performDrag(mPid, mUid, window, flags, surface, touchSource,
- touchX, touchY, thumbCenterX, thumbCenterY, data);
+ touchDeviceId, touchPointerId, touchX, touchY, thumbCenterX, thumbCenterY,
+ data);
} finally {
Binder.restoreCallingIdentity(ident);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 808a11d..516d37c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -866,6 +866,11 @@
public abstract ImeTargetInfo onToggleImeRequested(boolean show,
@NonNull IBinder focusedToken, @NonNull IBinder requestToken, int displayId);
+ /**
+ * Returns the token to identify the target window that the IME is associated with.
+ */
+ public abstract @Nullable IBinder getTargetWindowTokenFromInputToken(IBinder inputToken);
+
/** The information of input method target when IME is requested to show or hide. */
public static class ImeTargetInfo {
public final String focusedWindowName;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 10dd334..2125c63 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8552,6 +8552,12 @@
fromOrientations, toOrientations);
}
}
+
+ @Override
+ public @Nullable IBinder getTargetWindowTokenFromInputToken(IBinder inputToken) {
+ InputTarget inputTarget = WindowManagerService.this.getInputTargetFromToken(inputToken);
+ return inputTarget == null ? null : inputTarget.getWindowToken();
+ }
}
private final class ImeTargetVisibilityPolicyImpl extends ImeTargetVisibilityPolicy {
diff --git a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
index 3d4f866..d66b9b9 100644
--- a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
+++ b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
@@ -182,7 +182,7 @@
auto block_count = 1 + (fileSize - 1) / INCFS_DATA_FILE_BLOCK_SIZE;
auto hash_block_count = block_count;
- for (auto i = 0; hash_block_count > 1; i++) {
+ while (hash_block_count > 1) {
hash_block_count = (hash_block_count + hash_per_block - 1) / hash_per_block;
total_tree_block_count += hash_block_count;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java
index 23886a1..00fe3d9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java
@@ -25,6 +25,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -167,8 +168,12 @@
}
private void setStoppedState(int uid, String pkgName, boolean stopped) {
- doReturn(stopped).when(mPackageManagerInternal).isPackageStopped(pkgName, uid);
- sendPackageStoppedBroadcast(uid, pkgName, stopped);
+ try {
+ doReturn(stopped).when(mPackageManagerInternal).isPackageStopped(pkgName, uid);
+ sendPackageStoppedBroadcast(uid, pkgName, stopped);
+ } catch (PackageManager.NameNotFoundException e) {
+ fail("Unable to set stopped state for unknown package: " + pkgName);
+ }
}
private void sendPackageStoppedBroadcast(int uid, String pkgName, boolean stopped) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
index 2332988..e5ecdc4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -62,6 +62,7 @@
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
import android.text.TextUtils;
+import android.util.SparseArray;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -174,6 +175,7 @@
mUserState = new PackageUserStateImpl().setInstalled(true);
mPackageSetting.setUserState(mUserId, mUserState);
when(mPackageState.getUserStateOrDefault(eq(mUserId))).thenReturn(mUserState);
+ when(mPackageState.getUserStates()).thenReturn(new SparseArray<>());
when(mContext.getSystemService(LauncherApps.class)).thenReturn(mLauncherApps);
when(mContext.getSystemService(AppOpsManager.class)).thenReturn(
@@ -343,7 +345,7 @@
@Test
public void archiveApp_appOptedOutOfArchiving() {
- when(mAppOpsManager.checkOp(
+ when(mAppOpsManager.checkOpNoThrow(
eq(AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED),
anyInt(), eq(PACKAGE))).thenReturn(MODE_IGNORED);
@@ -430,7 +432,7 @@
@Test
public void isAppArchivable_appOptedOutOfArchiving()
throws PackageManager.NameNotFoundException {
- when(mAppOpsManager.checkOp(
+ when(mAppOpsManager.checkOpNoThrow(
eq(AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED),
anyInt(), eq(PACKAGE))).thenReturn(MODE_IGNORED);
@@ -551,22 +553,12 @@
when(mComputer.getPackageStateFiltered(eq(PACKAGE), anyInt(), anyInt())).thenReturn(
null);
- Exception e = assertThrows(
- ParcelableException.class,
- () -> mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT));
- assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
- assertThat(e.getCause()).hasMessageThat().isEqualTo(
- String.format("Package %s not found.", PACKAGE));
+ assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT)).isNull();
}
@Test
public void getArchivedAppIcon_notArchived() {
- Exception e = assertThrows(
- ParcelableException.class,
- () -> mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT));
- assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
- assertThat(e.getCause()).hasMessageThat().isEqualTo(
- String.format("Package %s is not currently archived.", PACKAGE));
+ assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT)).isNull();
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index a2a8424..0973d46 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -23,6 +23,7 @@
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2;
import static com.android.server.hdmi.Constants.ADDR_RECORDER_1;
import static com.android.server.hdmi.Constants.ADDR_TV;
+import static com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
import static com.google.common.truth.Truth.assertThat;
@@ -1712,13 +1713,14 @@
HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV);
HdmiCecMessage activeSourceFromTv =
HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
-
mHdmiControlService.getHdmiCecNetwork().clearLocalDevices();
mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS);
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
+ assertThat(mHdmiControlService.getLocalActiveSource()).isEqualTo(
+ new ActiveSource(Constants.ADDR_INVALID, Constants.INVALID_PHYSICAL_ADDRESS));
mNativeWrapper.clearResultMessages();
mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
mTestLooper.dispatchAll();
@@ -1728,16 +1730,72 @@
mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getResultMessages()).contains(activeSourceFromTv);
+ assertThat(mHdmiControlService.getLocalActiveSource()).isEqualTo(
+ new ActiveSource(mTvLogicalAddress, mTvPhysicalAddress));
}
@Test
+ public void requestActiveSourceActionComplete_validLocalActiveSource_doNotSendActiveSource() {
+ HdmiCecMessage requestActiveSource =
+ HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV);
+ HdmiCecMessage activeSourceFromTv =
+ HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+ mHdmiControlService.getHdmiCecNetwork().clearLocalDevices();
+ mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ mTestLooper.dispatchAll();
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
+ mHdmiControlService.setActiveSource(mTvLogicalAddress, mTvPhysicalAddress,
+ "HdmiCecLocalDeviceTvTest");
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2);
+ mTestLooper.dispatchAll();
+
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSourceFromTv);
+ }
+
+ @Test
+ public void onAddressAllocated_startRequestActiveSourceAction_cancelOnDeviceSelect() {
+ HdmiCecMessage requestActiveSource =
+ HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV);
+ HdmiCecMessage activeSourceFromTv =
+ HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+ mHdmiControlService.getHdmiCecNetwork().clearLocalDevices();
+ mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ HdmiDeviceInfo playbackDevice = HdmiDeviceInfo.cecDeviceBuilder()
+ .setLogicalAddress(ADDR_PLAYBACK_1)
+ .setPhysicalAddress(0x1000)
+ .setPortId(PORT_1)
+ .setDeviceType(HdmiDeviceInfo.DEVICE_PLAYBACK)
+ .setVendorId(0x1234)
+ .setDisplayName("Playback 1")
+ .setDevicePowerStatus(HdmiControlManager.POWER_STATUS_ON)
+ .setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B)
+ .build();
+ mHdmiControlService.getHdmiCecNetwork().addCecDevice(playbackDevice);
+ mTestLooper.dispatchAll();
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
+ mNativeWrapper.clearResultMessages();
+ mHdmiCecLocalDeviceTv.deviceSelect(playbackDevice.getId(), null);
+ mTestLooper.dispatchAll();
+
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2);
+ mTestLooper.dispatchAll();
+
+ // RequestActiveSourceAction should be cancelled and TV will not broadcast <Active Source>.
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSourceFromTv);
+ }
+
+
+ @Test
public void newDeviceConnectedIfOnlyOneGiveOsdNameSent() {
mHdmiControlService.getHdmiCecNetwork().clearDeviceList();
assertThat(mHdmiControlService.getHdmiCecNetwork().getDeviceInfoList(false))
.isEmpty();
HdmiCecMessage reportPhysicalAddress =
HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
- ADDR_PLAYBACK_2, 0x1000, HdmiDeviceInfo.DEVICE_PLAYBACK);
+ ADDR_PLAYBACK_2, 0x1000, HdmiDeviceInfo.DEVICE_PLAYBACK);
HdmiCecMessage giveOsdName = HdmiCecMessageBuilder.buildGiveOsdNameCommand(
ADDR_TV, ADDR_PLAYBACK_2);
mNativeWrapper.onCecMessage(reportPhysicalAddress);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
index 18961c0..ee076c6 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
@@ -140,9 +140,9 @@
}
@Override
- public ManagedProfilePasswordCache getManagedProfilePasswordCache(
+ public UnifiedProfilePasswordCache getUnifiedProfilePasswordCache(
java.security.KeyStore ks) {
- return mock(ManagedProfilePasswordCache.class);
+ return mock(UnifiedProfilePasswordCache.class);
}
@Override
diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp
index 6335b3b..4e1c72a 100644
--- a/services/tests/uiservicestests/Android.bp
+++ b/services/tests/uiservicestests/Android.bp
@@ -46,6 +46,7 @@
"testng",
"flag-junit",
"notification_flags_lib",
+ "platform-test-rules",
],
libs: [
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 36d55a4..14b551a 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -211,6 +211,9 @@
import android.permission.PermissionManager;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
+import android.platform.test.rule.DeniedDevices;
+import android.platform.test.rule.DeviceProduct;
+import android.platform.test.rule.LimitDevicesRule;
import android.provider.DeviceConfig;
import android.provider.MediaStore;
import android.provider.Settings;
@@ -281,6 +284,7 @@
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
+import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
@@ -313,6 +317,7 @@
@RunWith(AndroidTestingRunner.class)
@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
@RunWithLooper
+@DeniedDevices(denied = {DeviceProduct.CF_AUTO})
public class NotificationManagerServiceTest extends UiServiceTestCase {
private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId";
private static final String TEST_PACKAGE = "The.name.is.Package.Test.Package";
@@ -331,6 +336,9 @@
private final int mUid = Binder.getCallingUid();
private final @UserIdInt int mUserId = UserHandle.getUserId(mUid);
+ @ClassRule
+ public static final LimitDevicesRule sLimitDevicesRule = new LimitDevicesRule();
+
@Rule
public TestRule compatChangeRule = new PlatformCompatChangeRule();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 646ee3f..1aea56c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -80,6 +80,7 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.notNull;
import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -265,10 +266,12 @@
.thenReturn(CUSTOM_PKG_UID);
when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(
new String[] {pkg});
- ApplicationInfo mockAppInfo = mock(ApplicationInfo.class);
- when(mockAppInfo.loadLabel(any())).thenReturn(CUSTOM_APP_LABEL);
+
+ ApplicationInfo appInfoSpy = spy(new ApplicationInfo());
+ appInfoSpy.icon = ICON_RES_ID;
+ when(appInfoSpy.loadLabel(any())).thenReturn(CUSTOM_APP_LABEL);
when(mPackageManager.getApplicationInfo(eq(CUSTOM_PKG_NAME), anyInt()))
- .thenReturn(mockAppInfo);
+ .thenReturn(appInfoSpy);
mZenModeHelper.mPm = mPackageManager;
mZenModeEventLogger.reset();
@@ -3753,6 +3756,10 @@
rule.zenPolicy = policy;
rule.pkg = ownerPkg;
rule.name = CUSTOM_APP_LABEL;
+ rule.iconResName = ICON_RES_NAME;
+ rule.triggerDescription = mContext.getString(R.string.zen_mode_implicit_trigger_description,
+ CUSTOM_APP_LABEL);
+ rule.type = AutomaticZenRule.TYPE_OTHER;
rule.enabled = true;
return rule;
}
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/voiceinteraction/SetSandboxedTrainingDataAllowedTest.java b/services/tests/voiceinteractiontests/src/com/android/server/voiceinteraction/SetSandboxedTrainingDataAllowedTest.java
index 656957c..159c760 100644
--- a/services/tests/voiceinteractiontests/src/com/android/server/voiceinteraction/SetSandboxedTrainingDataAllowedTest.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/voiceinteraction/SetSandboxedTrainingDataAllowedTest.java
@@ -120,12 +120,12 @@
}
@Test
- public void setIsReceiveSandboxedTrainingDataAllowed_currentAndPreinstalledAssistant_setsOp() {
+ public void setShouldReceiveSandboxedTrainingData_currentAndPreinstalledAssistant_setsOp() {
// Set application info so current app is the current and preinstalled assistant.
mApplicationInfo.uid = Process.myUid();
mApplicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
- mVoiceInteractionManagerServiceStub.setIsReceiveSandboxedTrainingDataAllowed(
+ mVoiceInteractionManagerServiceStub.setShouldReceiveSandboxedTrainingData(
/* allowed= */ true);
verify(mAppOpsManager).setUidMode(mOpIdCaptor.capture(), mUidCaptor.capture(),
@@ -137,7 +137,7 @@
}
@Test
- public void setIsReceiveSandboxedTrainingDataAllowed_missingPermission_doesNotSetOp() {
+ public void setShouldReceiveSandboxedTrainingData_missingPermission_doesNotSetOp() {
// Set application info so current app is the current and preinstalled assistant.
mApplicationInfo.uid = Process.myUid();
mApplicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
@@ -146,33 +146,33 @@
mPermissionEnforcer.revoke(Manifest.permission.MANAGE_HOTWORD_DETECTION);
assertThrows(SecurityException.class,
- () -> mVoiceInteractionManagerServiceStub.setIsReceiveSandboxedTrainingDataAllowed(
+ () -> mVoiceInteractionManagerServiceStub.setShouldReceiveSandboxedTrainingData(
/* allowed= */ true));
verify(mAppOpsManager, never()).setUidMode(anyInt(), anyInt(), anyInt());
}
@Test
- public void setIsReceiveSandboxedTrainingDataAllowed_notPreinstalledAssistant_doesNotSetOp() {
+ public void setShouldReceiveSandboxedTrainingData_notPreinstalledAssistant_doesNotSetOp() {
// Set application info so current app is not preinstalled assistant.
mApplicationInfo.uid = Process.myUid();
mApplicationInfo.flags = ApplicationInfo.FLAG_INSTALLED; // Does not contain FLAG_SYSTEM.
assertThrows(SecurityException.class,
- () -> mVoiceInteractionManagerServiceStub.setIsReceiveSandboxedTrainingDataAllowed(
+ () -> mVoiceInteractionManagerServiceStub.setShouldReceiveSandboxedTrainingData(
/* allowed= */ true));
verify(mAppOpsManager, never()).setUidMode(anyInt(), anyInt(), anyInt());
}
@Test
- public void setIsReceiveSandboxedTrainingDataAllowed_notCurrentAssistant_doesNotSetOp() {
+ public void setShouldReceiveSandboxedTrainingData_notCurrentAssistant_doesNotSetOp() {
// Set application info so current app is not current assistant.
mApplicationInfo.uid = Process.SHELL_UID; // Set current assistant uid to shell UID.
mApplicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
assertThrows(SecurityException.class,
- () -> mVoiceInteractionManagerServiceStub.setIsReceiveSandboxedTrainingDataAllowed(
+ () -> mVoiceInteractionManagerServiceStub.setShouldReceiveSandboxedTrainingData(
/* allowed= */ true));
verify(mAppOpsManager, never()).setUidMode(anyInt(), anyInt(), anyInt());
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
index 8d236ed..0382ca0 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
@@ -61,6 +61,7 @@
META_SHORTCUTS.append(KEYCODE_P, Intent.CATEGORY_APP_MUSIC);
META_SHORTCUTS.append(KEYCODE_S, Intent.CATEGORY_APP_MESSAGING);
}
+ private static final int ANY_DISPLAY_ID = 123;
@Before
public void setUp() {
@@ -96,8 +97,9 @@
*/
@Test
public void testCtrlSpace() {
- sendKeyCombination(new int[]{KEYCODE_CTRL_LEFT, KEYCODE_SPACE}, 0);
- mPhoneWindowManager.assertSwitchKeyboardLayout(1);
+ sendKeyCombination(new int[]{KEYCODE_CTRL_LEFT, KEYCODE_SPACE}, /* duration= */ 0,
+ ANY_DISPLAY_ID);
+ mPhoneWindowManager.assertSwitchKeyboardLayout(/* direction= */ 1, ANY_DISPLAY_ID);
}
/**
@@ -105,8 +107,9 @@
*/
@Test
public void testCtrlShiftSpace() {
- sendKeyCombination(new int[]{KEYCODE_CTRL_LEFT, KEYCODE_SHIFT_LEFT, KEYCODE_SPACE}, 0);
- mPhoneWindowManager.assertSwitchKeyboardLayout(-1);
+ sendKeyCombination(new int[]{KEYCODE_CTRL_LEFT, KEYCODE_SHIFT_LEFT, KEYCODE_SPACE},
+ /* duration= */ 0, ANY_DISPLAY_ID);
+ mPhoneWindowManager.assertSwitchKeyboardLayout(/* direction= */ -1, ANY_DISPLAY_ID);
}
/**
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
index 9cdec25..157d162 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
@@ -114,7 +114,7 @@
}
}
- void sendKeyCombination(int[] keyCodes, long durationMillis, boolean longPress) {
+ void sendKeyCombination(int[] keyCodes, long durationMillis, boolean longPress, int displayId) {
final long downTime = mPhoneWindowManager.getCurrentTime();
final int count = keyCodes.length;
int metaState = 0;
@@ -124,7 +124,7 @@
final KeyEvent event = new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode,
0 /*repeat*/, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/,
0 /*flags*/, InputDevice.SOURCE_KEYBOARD);
- event.setDisplayId(DEFAULT_DISPLAY);
+ event.setDisplayId(displayId);
interceptKey(event);
// The order is important here, metaState could be updated and applied to the next key.
metaState |= MODIFIER.getOrDefault(keyCode, 0);
@@ -142,7 +142,7 @@
KeyEvent.ACTION_DOWN, keyCode, 1 /*repeat*/, metaState,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/,
KeyEvent.FLAG_LONG_PRESS /*flags*/, InputDevice.SOURCE_KEYBOARD);
- nextDownEvent.setDisplayId(DEFAULT_DISPLAY);
+ nextDownEvent.setDisplayId(displayId);
interceptKey(nextDownEvent);
}
}
@@ -153,18 +153,23 @@
final KeyEvent upEvent = new KeyEvent(downTime, eventTime, KeyEvent.ACTION_UP, keyCode,
0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, 0 /*flags*/,
InputDevice.SOURCE_KEYBOARD);
- upEvent.setDisplayId(DEFAULT_DISPLAY);
+ upEvent.setDisplayId(displayId);
interceptKey(upEvent);
metaState &= ~MODIFIER.getOrDefault(keyCode, 0);
}
}
void sendKeyCombination(int[] keyCodes, long durationMillis) {
- sendKeyCombination(keyCodes, durationMillis, false /* longPress */);
+ sendKeyCombination(keyCodes, durationMillis, false /* longPress */, DEFAULT_DISPLAY);
+ }
+
+ void sendKeyCombination(int[] keyCodes, long durationMillis, int displayId) {
+ sendKeyCombination(keyCodes, durationMillis, false /* longPress */, displayId);
}
void sendLongPressKeyCombination(int[] keyCodes) {
- sendKeyCombination(keyCodes, ViewConfiguration.getLongPressTimeout(), true /* longPress */);
+ sendKeyCombination(keyCodes, ViewConfiguration.getLongPressTimeout(), true /* longPress */,
+ DEFAULT_DISPLAY);
}
void sendKey(int keyCode) {
@@ -172,7 +177,7 @@
}
void sendKey(int keyCode, boolean longPress) {
- sendKeyCombination(new int[]{keyCode}, 0 /*durationMillis*/, longPress);
+ sendKeyCombination(new int[]{keyCode}, 0 /*durationMillis*/, longPress, DEFAULT_DISPLAY);
}
private void interceptKey(KeyEvent keyEvent) {
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 48d3503..0678210 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -166,6 +166,9 @@
@Mock
private PhoneWindowManager.ButtonOverridePermissionChecker mButtonOverridePermissionChecker;
+ @Mock private IBinder mInputToken;
+ @Mock private IBinder mImeTargetWindowToken;
+
private StaticMockitoSession mMockitoSession;
private OffsettableClock mClock = new OffsettableClock();
private TestLooper mTestLooper = new TestLooper(() -> mClock.now());
@@ -327,6 +330,9 @@
doNothing().when(mPhoneWindowManager).finishedWakingUp(anyInt(), anyInt());
doNothing().when(mPhoneWindowManager).lockNow(any());
+ doReturn(mImeTargetWindowToken)
+ .when(mWindowManagerInternal).getTargetWindowTokenFromInputToken(mInputToken);
+
mPhoneWindowManager.init(new TestInjector(mContext, mWindowManagerFuncsImpl));
mPhoneWindowManager.systemReady();
mPhoneWindowManager.systemBooted();
@@ -365,12 +371,12 @@
}
long interceptKeyBeforeDispatching(KeyEvent event) {
- return mPhoneWindowManager.interceptKeyBeforeDispatching(null /*focusedToken*/,
- event, FLAG_INTERACTIVE);
+ return mPhoneWindowManager.interceptKeyBeforeDispatching(mInputToken, event,
+ FLAG_INTERACTIVE);
}
void dispatchUnhandledKey(KeyEvent event) {
- mPhoneWindowManager.dispatchUnhandledKey(null /*focusedToken*/, event, FLAG_INTERACTIVE);
+ mPhoneWindowManager.dispatchUnhandledKey(mInputToken, event, FLAG_INTERACTIVE);
}
long getCurrentTime() {
@@ -646,14 +652,16 @@
verify(mStatusBarManagerInternal).startAssist(any());
}
- void assertSwitchKeyboardLayout(int direction) {
+ void assertSwitchKeyboardLayout(int direction, int displayId) {
mTestLooper.dispatchAll();
if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI)) {
- verify(mInputMethodManagerInternal).switchKeyboardLayout(eq(direction));
+ verify(mInputMethodManagerInternal).onSwitchKeyboardLayoutShortcut(eq(direction),
+ eq(displayId), eq(mImeTargetWindowToken));
verify(mWindowManagerFuncsImpl, never()).switchKeyboardLayout(anyInt(), anyInt());
} else {
verify(mWindowManagerFuncsImpl).switchKeyboardLayout(anyInt(), eq(direction));
- verify(mInputMethodManagerInternal, never()).switchKeyboardLayout(anyInt());
+ verify(mInputMethodManagerInternal, never())
+ .onSwitchKeyboardLayoutShortcut(anyInt(), anyInt(), any());
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index 65e77dc..d4ba3b2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -124,7 +124,7 @@
private void verifyOnActivityLaunched(ActivityRecord activity) {
final ArgumentCaptor<Long> idCaptor = ArgumentCaptor.forClass(Long.class);
verifyAsync(mLaunchObserver).onActivityLaunched(idCaptor.capture(),
- eq(activity.mActivityComponent), anyInt());
+ eq(activity.mActivityComponent), anyInt(), anyInt());
final long id = idCaptor.getValue();
setExpectedStartedId(id, activity);
mLastLaunchedIds.put(activity.mActivityComponent, id);
@@ -132,7 +132,7 @@
private void verifyOnActivityLaunchFinished(ActivityRecord activity) {
verifyAsync(mLaunchObserver).onActivityLaunchFinished(eq(mExpectedStartedId),
- eq(activity.mActivityComponent), anyLong());
+ eq(activity.mActivityComponent), anyLong(), anyInt());
}
private void setExpectedStartedId(long id, Object reason) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 50fe042..1fb7cd8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -553,7 +553,7 @@
assertTrue(mWm.mInputManager.transferTouchFocus(new InputChannel(),
new InputChannel(), true /* isDragDrop */));
mToken = mTarget.performDrag(TEST_PID, 0, mWindow.mClient,
- flag, surface, 0, 0, 0, 0, 0, data);
+ flag, surface, 0, 0, 0, 0, 0, 0, 0, data);
assertNotNull(mToken);
r.run();
@@ -575,7 +575,7 @@
private void startA11yDrag(int flags, ClipData data, Runnable r) {
mToken = mTarget.performDrag(0, 0, mWindow.mClient,
- flags | View.DRAG_FLAG_ACCESSIBILITY_ACTION, null, 0, 0, 0, 0, 0, data);
+ flags | View.DRAG_FLAG_ACCESSIBILITY_ACTION, null, 0, 0, 0, 0, 0, 0, 0, data);
assertNotNull(mToken);
r.run();
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 6e4f13a..c902d459 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -1569,13 +1569,13 @@
@Override
@EnforcePermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION)
- public void setIsReceiveSandboxedTrainingDataAllowed(boolean allowed) {
- super.setIsReceiveSandboxedTrainingDataAllowed_enforcePermission();
+ public void setShouldReceiveSandboxedTrainingData(boolean allowed) {
+ super.setShouldReceiveSandboxedTrainingData_enforcePermission();
synchronized (this) {
if (mImpl == null) {
throw new IllegalStateException(
- "setIsReceiveSandboxedTrainingDataAllowed without running voice "
+ "setShouldReceiveSandboxedTrainingData without running voice "
+ "interaction service");
}