Merge changes from topic "UpsideDownCake-SDK-Finalization" into udc-dev
* changes:
DO NOT MERGE: Revert "Add VanillaIceCream to android.os.Build"
UpsideDownCake is now 34
Add VanillaIceCream to android.os.Build
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index c4e8b0e..a8b6c0b 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -105,6 +105,7 @@
static const char DISPLAYS_PROP_NAME[] = "persist.service.bootanim.displays";
static const char CLOCK_ENABLED_PROP_NAME[] = "persist.sys.bootanim.clock.enabled";
static const int ANIM_ENTRY_NAME_MAX = ANIM_PATH_MAX + 1;
+static const int MAX_CHECK_EXIT_INTERVAL_US = 50000;
static constexpr size_t TEXT_POS_LEN_MAX = 16;
static const int DYNAMIC_COLOR_COUNT = 4;
static const char U_TEXTURE[] = "uTexture";
@@ -1678,7 +1679,17 @@
checkExit();
}
- usleep(part.pause * ns2us(frameDuration));
+ int pauseDuration = part.pause * ns2us(frameDuration);
+ while(pauseDuration > 0 && !exitPending()){
+ if (pauseDuration > MAX_CHECK_EXIT_INTERVAL_US) {
+ usleep(MAX_CHECK_EXIT_INTERVAL_US);
+ pauseDuration -= MAX_CHECK_EXIT_INTERVAL_US;
+ } else {
+ usleep(pauseDuration);
+ break;
+ }
+ checkExit();
+ }
if (exitPending() && !part.count && mCurrentInset >= mTargetInset &&
!part.hasFadingPhase()) {
diff --git a/core/api/current.txt b/core/api/current.txt
index 14d9a3e..288ab47 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -13662,7 +13662,6 @@
}
public final class CredentialOption implements android.os.Parcelable {
- ctor @Deprecated public CredentialOption(@NonNull String, @NonNull android.os.Bundle, @NonNull android.os.Bundle, boolean);
method public int describeContents();
method @NonNull public java.util.Set<android.content.ComponentName> getAllowedProviders();
method @NonNull public android.os.Bundle getCandidateQueryData();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index e4e62a9..ace7d59 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -10117,7 +10117,7 @@
public final class NetworkProviderInfo implements android.os.Parcelable {
method public int describeContents();
method @IntRange(from=0, to=100) public int getBatteryPercentage();
- method @IntRange(from=0, to=3) public int getConnectionStrength();
+ method @IntRange(from=0, to=4) public int getConnectionStrength();
method @NonNull public String getDeviceName();
method public int getDeviceType();
method @NonNull public android.os.Bundle getExtras();
@@ -10136,7 +10136,7 @@
ctor public NetworkProviderInfo.Builder(@NonNull String, @NonNull String);
method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo build();
method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryPercentage(@IntRange(from=0, to=100) int);
- method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setConnectionStrength(@IntRange(from=0, to=3) int);
+ method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setConnectionStrength(@IntRange(from=0, to=4) int);
method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setDeviceName(@NonNull String);
method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setDeviceType(int);
method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setExtras(@NonNull android.os.Bundle);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 2dfda51..9b5e31a 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1651,6 +1651,11 @@
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.KeyphraseMetadata> CREATOR;
}
+ public class SoundTrigger {
+ field public static final int MODEL_PARAM_INVALID = -1; // 0xffffffff
+ field public static final int MODEL_PARAM_THRESHOLD_FACTOR = 0; // 0x0
+ }
+
public static final class SoundTrigger.KeyphraseRecognitionExtra implements android.os.Parcelable {
ctor public SoundTrigger.KeyphraseRecognitionExtra(int, int, int);
}
@@ -1663,6 +1668,19 @@
ctor public SoundTrigger.ModuleProperties(int, @NonNull String, @NonNull String, @NonNull String, int, @NonNull String, int, int, int, int, boolean, int, boolean, int, boolean, int);
}
+ public static final class SoundTrigger.RecognitionConfig implements android.os.Parcelable {
+ ctor public SoundTrigger.RecognitionConfig(boolean, boolean, @Nullable android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[], @Nullable byte[], int);
+ ctor public SoundTrigger.RecognitionConfig(boolean, boolean, @Nullable android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[], @Nullable byte[]);
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.RecognitionConfig> CREATOR;
+ field public final boolean allowMultipleTriggers;
+ field public final int audioCapabilities;
+ field public final boolean captureRequested;
+ field @NonNull public final byte[] data;
+ field @NonNull public final android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[] keyphrases;
+ }
+
public static class SoundTrigger.RecognitionEvent {
ctor public SoundTrigger.RecognitionEvent(int, int, boolean, int, int, int, boolean, @NonNull android.media.AudioFormat, @Nullable byte[], long);
}
@@ -2000,6 +2018,57 @@
}
+package android.media.soundtrigger {
+
+ public final class SoundTriggerInstrumentation {
+ method public void setResourceContention(boolean);
+ method public void triggerOnResourcesAvailable();
+ method public void triggerRestart();
+ }
+
+ public static interface SoundTriggerInstrumentation.GlobalCallback {
+ method public default void onClientAttached();
+ method public default void onClientDetached();
+ method public default void onFrameworkDetached();
+ method public void onModelLoaded(@NonNull android.media.soundtrigger.SoundTriggerInstrumentation.ModelSession);
+ method public default void onPreempted();
+ method public default void onRestarted();
+ }
+
+ public static interface SoundTriggerInstrumentation.ModelCallback {
+ method public default void onModelUnloaded();
+ method public default void onParamSet(int, int);
+ method public void onRecognitionStarted(@NonNull android.media.soundtrigger.SoundTriggerInstrumentation.RecognitionSession);
+ }
+
+ public class SoundTriggerInstrumentation.ModelSession {
+ method public void clearModelCallback();
+ method @NonNull public java.util.List<android.hardware.soundtrigger.SoundTrigger.Keyphrase> getPhrases();
+ method @NonNull public android.media.soundtrigger.SoundTriggerManager.Model getSoundModel();
+ method public boolean isKeyphrase();
+ method public void setModelCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.soundtrigger.SoundTriggerInstrumentation.ModelCallback);
+ method public void triggerUnloadModel();
+ }
+
+ public static interface SoundTriggerInstrumentation.RecognitionCallback {
+ method public void onRecognitionStopped();
+ }
+
+ public class SoundTriggerInstrumentation.RecognitionSession {
+ method public void clearRecognitionCallback();
+ method public int getAudioSession();
+ method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig getRecognitionConfig();
+ method public void setRecognitionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.soundtrigger.SoundTriggerInstrumentation.RecognitionCallback);
+ method public void triggerAbortRecognition();
+ method public void triggerRecognitionEvent(@NonNull byte[], @Nullable java.util.List<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra>);
+ }
+
+ public final class SoundTriggerManager {
+ method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public static android.media.soundtrigger.SoundTriggerInstrumentation attachInstrumentation(@NonNull java.util.concurrent.Executor, @NonNull android.media.soundtrigger.SoundTriggerInstrumentation.GlobalCallback);
+ }
+
+}
+
package android.media.tv {
public final class TvInputManager {
@@ -2024,6 +2093,14 @@
}
+package android.media.voice {
+
+ public final class KeyphraseModelManager {
+ method @RequiresPermission("android.permission.MANAGE_VOICE_KEYPHRASES") public void setModelDatabaseForTestEnabled(boolean);
+ }
+
+}
+
package android.net {
public class NetworkPolicyManager {
@@ -2944,6 +3021,7 @@
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorForTest(@NonNull String, @NonNull java.util.Locale, @NonNull android.hardware.soundtrigger.SoundTrigger.ModuleProperties, @NonNull java.util.concurrent.Executor, @NonNull android.service.voice.AlwaysOnHotwordDetector.Callback);
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorForTest(@NonNull String, @NonNull java.util.Locale, @Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull android.hardware.soundtrigger.SoundTrigger.ModuleProperties, @NonNull java.util.concurrent.Executor, @NonNull android.service.voice.AlwaysOnHotwordDetector.Callback);
method @NonNull public final java.util.List<android.hardware.soundtrigger.SoundTrigger.ModuleProperties> listModuleProperties();
+ method public final void setTestModuleForAlwaysOnHotwordDetectorEnabled(boolean);
}
public static class VoiceInteractionSession.ActivityId {
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 0293bb5..95e446d 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -343,7 +343,170 @@
*/
public abstract boolean hasRunningActivity(int uid, @Nullable String packageName);
- public abstract void updateOomAdj();
+ /**
+ * Oom Adj Reason: none - internal use only, do not use it.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_NONE = 0;
+
+ /**
+ * Oom Adj Reason: activity changes.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_ACTIVITY = 1;
+
+ /**
+ * Oom Adj Reason: finishing a broadcast receiver.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_FINISH_RECEIVER = 2;
+
+ /**
+ * Oom Adj Reason: starting a broadcast receiver.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_START_RECEIVER = 3;
+
+ /**
+ * Oom Adj Reason: binding to a service.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_BIND_SERVICE = 4;
+
+ /**
+ * Oom Adj Reason: unbinding from a service.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_UNBIND_SERVICE = 5;
+
+ /**
+ * Oom Adj Reason: starting a service.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_START_SERVICE = 6;
+
+ /**
+ * Oom Adj Reason: connecting to a content provider.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_GET_PROVIDER = 7;
+
+ /**
+ * Oom Adj Reason: disconnecting from a content provider.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_REMOVE_PROVIDER = 8;
+
+ /**
+ * Oom Adj Reason: UI visibility changes.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_UI_VISIBILITY = 9;
+
+ /**
+ * Oom Adj Reason: device power allowlist changes.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_ALLOWLIST = 10;
+
+ /**
+ * Oom Adj Reason: starting a process.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_PROCESS_BEGIN = 11;
+
+ /**
+ * Oom Adj Reason: ending a process.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_PROCESS_END = 12;
+
+ /**
+ * Oom Adj Reason: short FGS timeout.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_SHORT_FGS_TIMEOUT = 13;
+
+ /**
+ * Oom Adj Reason: system initialization.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_SYSTEM_INIT = 14;
+
+ /**
+ * Oom Adj Reason: backup/restore.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_BACKUP = 15;
+
+ /**
+ * Oom Adj Reason: instrumented by the SHELL.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_SHELL = 16;
+
+ /**
+ * Oom Adj Reason: task stack is being removed.
+ */
+ public static final int OOM_ADJ_REASON_REMOVE_TASK = 17;
+
+ /**
+ * Oom Adj Reason: uid idle.
+ */
+ public static final int OOM_ADJ_REASON_UID_IDLE = 18;
+
+ /**
+ * Oom Adj Reason: stop service.
+ */
+ public static final int OOM_ADJ_REASON_STOP_SERVICE = 19;
+
+ /**
+ * Oom Adj Reason: executing service.
+ */
+ public static final int OOM_ADJ_REASON_EXECUTING_SERVICE = 20;
+
+ /**
+ * Oom Adj Reason: background restriction changes.
+ */
+ public static final int OOM_ADJ_REASON_RESTRICTION_CHANGE = 21;
+
+ /**
+ * Oom Adj Reason: A package or its component is disabled.
+ */
+ public static final int OOM_ADJ_REASON_COMPONENT_DISABLED = 22;
+
+ @IntDef(prefix = {"OOM_ADJ_REASON_"}, value = {
+ OOM_ADJ_REASON_NONE,
+ OOM_ADJ_REASON_ACTIVITY,
+ OOM_ADJ_REASON_FINISH_RECEIVER,
+ OOM_ADJ_REASON_START_RECEIVER,
+ OOM_ADJ_REASON_BIND_SERVICE,
+ OOM_ADJ_REASON_UNBIND_SERVICE,
+ OOM_ADJ_REASON_START_SERVICE,
+ OOM_ADJ_REASON_GET_PROVIDER,
+ OOM_ADJ_REASON_REMOVE_PROVIDER,
+ OOM_ADJ_REASON_UI_VISIBILITY,
+ OOM_ADJ_REASON_ALLOWLIST,
+ OOM_ADJ_REASON_PROCESS_BEGIN,
+ OOM_ADJ_REASON_PROCESS_END,
+ OOM_ADJ_REASON_SHORT_FGS_TIMEOUT,
+ OOM_ADJ_REASON_SYSTEM_INIT,
+ OOM_ADJ_REASON_BACKUP,
+ OOM_ADJ_REASON_SHELL,
+ OOM_ADJ_REASON_REMOVE_TASK,
+ OOM_ADJ_REASON_UID_IDLE,
+ OOM_ADJ_REASON_STOP_SERVICE,
+ OOM_ADJ_REASON_EXECUTING_SERVICE,
+ OOM_ADJ_REASON_RESTRICTION_CHANGE,
+ OOM_ADJ_REASON_COMPONENT_DISABLED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface OomAdjReason {}
+
+ /**
+ * Request to update oom adj.
+ */
+ public abstract void updateOomAdj(@OomAdjReason int oomAdjReason);
public abstract void updateCpuStats();
/**
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index b48a8fb..1dddf06 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1461,9 +1461,25 @@
*/
public static final int OP_USE_FULL_SCREEN_INTENT = AppProtoEnums.APP_OP_USE_FULL_SCREEN_INTENT;
+ /**
+ * Hides camera indicator for sandboxed detection apps that directly access the service.
+ *
+ * @hide
+ */
+ public static final int OP_CAMERA_SANDBOXED =
+ AppProtoEnums.APP_OP_CAMERA_SANDBOXED;
+
+ /**
+ * Hides microphone indicator for sandboxed detection apps that directly access the service.
+ *
+ * @hide
+ */
+ public static final int OP_RECORD_AUDIO_SANDBOXED =
+ AppProtoEnums.APP_OP_RECORD_AUDIO_SANDBOXED;
+
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int _NUM_OP = 134;
+ public static final int _NUM_OP = 136;
/**
* All app ops represented as strings.
@@ -1605,6 +1621,8 @@
OPSTR_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD,
OPSTR_BODY_SENSORS_WRIST_TEMPERATURE,
OPSTR_USE_FULL_SCREEN_INTENT,
+ OPSTR_CAMERA_SANDBOXED,
+ OPSTR_RECORD_AUDIO_SANDBOXED
})
public @interface AppOpString {}
@@ -2013,6 +2031,20 @@
public static final String OPSTR_COARSE_LOCATION_SOURCE = "android:coarse_location_source";
/**
+ * Camera is being recorded in sandboxed detection process.
+ *
+ * @hide
+ */
+ public static final String OPSTR_CAMERA_SANDBOXED = "android:camera_sandboxed";
+
+ /**
+ * Audio is being recorded in sandboxed detection process.
+ *
+ * @hide
+ */
+ public static final String OPSTR_RECORD_AUDIO_SANDBOXED = "android:record_audio_sandboxed";
+
+ /**
* Allow apps to create the requests to manage the media files without user confirmation.
*
* @see android.Manifest.permission#MANAGE_MEDIA
@@ -2738,7 +2770,11 @@
.setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
new AppOpInfo.Builder(OP_USE_FULL_SCREEN_INTENT, OPSTR_USE_FULL_SCREEN_INTENT,
"USE_FULL_SCREEN_INTENT").setPermission(Manifest.permission.USE_FULL_SCREEN_INTENT)
- .build()
+ .build(),
+ new AppOpInfo.Builder(OP_CAMERA_SANDBOXED, OPSTR_CAMERA_SANDBOXED,
+ "CAMERA_SANDBOXED").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_RECORD_AUDIO_SANDBOXED, OPSTR_RECORD_AUDIO_SANDBOXED,
+ "RECORD_AUDIO_SANDBOXED").setDefaultMode(AppOpsManager.MODE_ALLOWED).build()
};
// The number of longs needed to form a full bitmask of app ops
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 746b8f7..0b48621 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -24,6 +24,7 @@
import android.app.NotificationChannelGroup;
import android.app.NotificationHistory;
import android.app.NotificationManager;
+import android.content.AttributionSource;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ParceledListSlice;
@@ -225,6 +226,7 @@
void setNotificationDelegate(String callingPkg, String delegate);
String getNotificationDelegate(String callingPkg);
boolean canNotifyAsPackage(String callingPkg, String targetPkg, int userId);
+ boolean canUseFullScreenIntent(in AttributionSource attributionSource);
void setPrivateNotificationsAllowed(boolean allow);
boolean getPrivateNotificationsAllowed();
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 80f64e0..785470f 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -31,7 +31,6 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.PermissionChecker;
import android.content.pm.ParceledListSlice;
import android.content.pm.ShortcutInfo;
import android.graphics.drawable.Icon;
@@ -877,19 +876,11 @@
* {@link android.provider.Settings#ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT}.
*/
public boolean canUseFullScreenIntent() {
- final int result = PermissionChecker.checkPermissionForPreflight(mContext,
- android.Manifest.permission.USE_FULL_SCREEN_INTENT,
- mContext.getAttributionSource());
-
- switch (result) {
- case PermissionChecker.PERMISSION_GRANTED:
- return true;
- case PermissionChecker.PERMISSION_SOFT_DENIED:
- case PermissionChecker.PERMISSION_HARD_DENIED:
- return false;
- default:
- if (localLOGV) Log.v(TAG, "Unknown PermissionChecker result: " + result);
- return false;
+ INotificationManager service = getService();
+ try {
+ return service.canUseFullScreenIntent(mContext.getAttributionSource());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
}
diff --git a/core/java/android/credentials/CredentialOption.java b/core/java/android/credentials/CredentialOption.java
index e933123..df948f17 100644
--- a/core/java/android/credentials/CredentialOption.java
+++ b/core/java/android/credentials/CredentialOption.java
@@ -37,8 +37,7 @@
/**
* Information about a specific type of credential to be requested during a {@link
- * CredentialManager#getCredential(GetCredentialRequest, Activity, CancellationSignal, Executor,
- * OutcomeReceiver)} operation.
+ * CredentialManager#getCredential} operation.
*/
public final class CredentialOption implements Parcelable {
@@ -196,9 +195,8 @@
* @throws NullPointerException If {@code credentialRetrievalData}, or
* {@code candidateQueryData} is null.
*
- * @deprecated replaced by Builder
+ * @hide
*/
- @Deprecated
public CredentialOption(
@NonNull String type,
@NonNull Bundle credentialRetrievalData,
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index fa678fc..2e40f60 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -142,6 +142,7 @@
private PromptInfo mPromptInfo;
private ButtonInfo mNegativeButtonInfo;
private Context mContext;
+ private IAuthService mService;
/**
* Creates a builder for a {@link BiometricPrompt} dialog.
@@ -212,6 +213,18 @@
}
/**
+ * @param service
+ * @return This builder.
+ * @hide
+ */
+ @RequiresPermission(TEST_BIOMETRIC)
+ @NonNull
+ public Builder setService(@NonNull IAuthService service) {
+ mService = service;
+ return this;
+ }
+
+ /**
* Sets an optional title, subtitle, and/or description that will override other text when
* the user is authenticating with PIN/pattern/password. Currently for internal use only.
* @return This builder.
@@ -472,7 +485,9 @@
throw new IllegalArgumentException("Can't have both negative button behavior"
+ " and device credential enabled");
}
- return new BiometricPrompt(mContext, mPromptInfo, mNegativeButtonInfo);
+ mService = (mService == null) ? IAuthService.Stub.asInterface(
+ ServiceManager.getService(Context.AUTH_SERVICE)) : mService;
+ return new BiometricPrompt(mContext, mPromptInfo, mNegativeButtonInfo, mService);
}
}
@@ -521,7 +536,6 @@
public void onAuthenticationFailed() {
mExecutor.execute(() -> {
mAuthenticationCallback.onAuthenticationFailed();
- mIsPromptShowing = false;
});
}
@@ -604,12 +618,12 @@
private boolean mIsPromptShowing;
- private BiometricPrompt(Context context, PromptInfo promptInfo, ButtonInfo negativeButtonInfo) {
+ private BiometricPrompt(Context context, PromptInfo promptInfo, ButtonInfo negativeButtonInfo,
+ IAuthService service) {
mContext = context;
mPromptInfo = promptInfo;
mNegativeButtonInfo = negativeButtonInfo;
- mService = IAuthService.Stub.asInterface(
- ServiceManager.getService(Context.AUTH_SERVICE));
+ mService = service;
mIsPromptShowing = false;
}
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index fa16e16..6d43ddf 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -1051,6 +1051,29 @@
return "ModelParamRange [start=" + mStart + ", end=" + mEnd + "]";
}
}
+ /**
+ * SoundTrigger model parameter types.
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "MODEL_PARAM" }, value = {
+ MODEL_PARAM_INVALID,
+ MODEL_PARAM_THRESHOLD_FACTOR
+ })
+ public @interface ModelParamTypes {}
+
+ /**
+ * See {@link ModelParams.INVALID}
+ * @hide
+ */
+ @TestApi
+ public static final int MODEL_PARAM_INVALID = ModelParams.INVALID;
+ /**
+ * See {@link ModelParams.THRESHOLD_FACTOR}
+ * @hide
+ */
+ @TestApi
+ public static final int MODEL_PARAM_THRESHOLD_FACTOR = ModelParams.THRESHOLD_FACTOR;
/**
* Modes for key phrase recognition
@@ -1450,7 +1473,8 @@
*
* @hide
*/
- public static class RecognitionConfig implements Parcelable {
+ @TestApi
+ public static final class RecognitionConfig implements Parcelable {
/** True if the DSP should capture the trigger sound and make it available for further
* capture. */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -1464,6 +1488,7 @@
* options for each keyphrase. */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@NonNull
+ @SuppressLint("ArrayReturn")
public final KeyphraseRecognitionExtra keyphrases[];
/** Opaque data for use by system applications who know about voice engine internals,
* typically during enrollment. */
@@ -1479,8 +1504,8 @@
public final int audioCapabilities;
public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
- @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data,
- int audioCapabilities) {
+ @SuppressLint("ArrayReturn") @Nullable KeyphraseRecognitionExtra[] keyphrases,
+ @Nullable byte[] data, int audioCapabilities) {
this.captureRequested = captureRequested;
this.allowMultipleTriggers = allowMultipleTriggers;
this.keyphrases = keyphrases != null ? keyphrases : new KeyphraseRecognitionExtra[0];
@@ -1490,7 +1515,8 @@
@UnsupportedAppUsage
public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
- @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) {
+ @SuppressLint("ArrayReturn") @Nullable KeyphraseRecognitionExtra[] keyphrases,
+ @Nullable byte[] data) {
this(captureRequested, allowMultipleTriggers, keyphrases, data, 0);
}
@@ -1517,7 +1543,7 @@
}
@Override
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeByte((byte) (captureRequested ? 1 : 0));
dest.writeByte((byte) (allowMultipleTriggers ? 1 : 0));
dest.writeTypedArray(keyphrases, flags);
diff --git a/core/java/android/permission/OWNERS b/core/java/android/permission/OWNERS
index d34b45b..4603e43f 100644
--- a/core/java/android/permission/OWNERS
+++ b/core/java/android/permission/OWNERS
@@ -1,18 +1,19 @@
# Bug component: 137825
-evanseverson@google.com
-evanxinchen@google.com
ashfall@google.com
-guojing@google.com
+augale@google.com
+evanseverson@google.com
+fayey@google.com
jaysullivan@google.com
+joecastro@google.com
kvakil@google.com
mrulhania@google.com
narayan@google.com
ntmyren@google.com
olekarg@google.com
pyuli@google.com
-raphk@google.com
rmacgregor@google.com
sergeynv@google.com
theianchen@google.com
+yutingfang@google.com
zhanghai@google.com
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 402da28..828c062 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -45,7 +45,6 @@
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.PluralsMessageFormatter;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
@@ -59,7 +58,6 @@
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
@@ -310,86 +308,6 @@
return buffer.toString();
}
- public Diff diff(ZenModeConfig to) {
- final Diff d = new Diff();
- if (to == null) {
- return d.addLine("config", "delete");
- }
- if (user != to.user) {
- d.addLine("user", user, to.user);
- }
- if (allowAlarms != to.allowAlarms) {
- d.addLine("allowAlarms", allowAlarms, to.allowAlarms);
- }
- if (allowMedia != to.allowMedia) {
- d.addLine("allowMedia", allowMedia, to.allowMedia);
- }
- if (allowSystem != to.allowSystem) {
- d.addLine("allowSystem", allowSystem, to.allowSystem);
- }
- if (allowCalls != to.allowCalls) {
- d.addLine("allowCalls", allowCalls, to.allowCalls);
- }
- if (allowReminders != to.allowReminders) {
- d.addLine("allowReminders", allowReminders, to.allowReminders);
- }
- if (allowEvents != to.allowEvents) {
- d.addLine("allowEvents", allowEvents, to.allowEvents);
- }
- if (allowRepeatCallers != to.allowRepeatCallers) {
- d.addLine("allowRepeatCallers", allowRepeatCallers, to.allowRepeatCallers);
- }
- if (allowMessages != to.allowMessages) {
- d.addLine("allowMessages", allowMessages, to.allowMessages);
- }
- if (allowCallsFrom != to.allowCallsFrom) {
- d.addLine("allowCallsFrom", allowCallsFrom, to.allowCallsFrom);
- }
- if (allowMessagesFrom != to.allowMessagesFrom) {
- d.addLine("allowMessagesFrom", allowMessagesFrom, to.allowMessagesFrom);
- }
- if (suppressedVisualEffects != to.suppressedVisualEffects) {
- d.addLine("suppressedVisualEffects", suppressedVisualEffects,
- to.suppressedVisualEffects);
- }
- final ArraySet<String> allRules = new ArraySet<>();
- addKeys(allRules, automaticRules);
- addKeys(allRules, to.automaticRules);
- final int N = allRules.size();
- for (int i = 0; i < N; i++) {
- final String rule = allRules.valueAt(i);
- final ZenRule fromRule = automaticRules != null ? automaticRules.get(rule) : null;
- final ZenRule toRule = to.automaticRules != null ? to.automaticRules.get(rule) : null;
- ZenRule.appendDiff(d, "automaticRule[" + rule + "]", fromRule, toRule);
- }
- ZenRule.appendDiff(d, "manualRule", manualRule, to.manualRule);
-
- if (areChannelsBypassingDnd != to.areChannelsBypassingDnd) {
- d.addLine("areChannelsBypassingDnd", areChannelsBypassingDnd,
- to.areChannelsBypassingDnd);
- }
- return d;
- }
-
- public static Diff diff(ZenModeConfig from, ZenModeConfig to) {
- if (from == null) {
- final Diff d = new Diff();
- if (to != null) {
- d.addLine("config", "insert");
- }
- return d;
- }
- return from.diff(to);
- }
-
- private static <T> void addKeys(ArraySet<T> set, ArrayMap<T, ?> map) {
- if (map != null) {
- for (int i = 0; i < map.size(); i++) {
- set.add(map.keyAt(i));
- }
- }
- }
-
public boolean isValid() {
if (!isValidManualRule(manualRule)) return false;
final int N = automaticRules.size();
@@ -1922,66 +1840,6 @@
proto.end(token);
}
- private static void appendDiff(Diff d, String item, ZenRule from, ZenRule to) {
- if (d == null) return;
- if (from == null) {
- if (to != null) {
- d.addLine(item, "insert");
- }
- return;
- }
- from.appendDiff(d, item, to);
- }
-
- private void appendDiff(Diff d, String item, ZenRule to) {
- if (to == null) {
- d.addLine(item, "delete");
- return;
- }
- if (enabled != to.enabled) {
- d.addLine(item, "enabled", enabled, to.enabled);
- }
- if (snoozing != to.snoozing) {
- d.addLine(item, "snoozing", snoozing, to.snoozing);
- }
- if (!Objects.equals(name, to.name)) {
- d.addLine(item, "name", name, to.name);
- }
- if (zenMode != to.zenMode) {
- d.addLine(item, "zenMode", zenMode, to.zenMode);
- }
- if (!Objects.equals(conditionId, to.conditionId)) {
- d.addLine(item, "conditionId", conditionId, to.conditionId);
- }
- if (!Objects.equals(condition, to.condition)) {
- d.addLine(item, "condition", condition, to.condition);
- }
- if (!Objects.equals(component, to.component)) {
- d.addLine(item, "component", component, to.component);
- }
- if (!Objects.equals(configurationActivity, to.configurationActivity)) {
- d.addLine(item, "configActivity", configurationActivity, to.configurationActivity);
- }
- if (!Objects.equals(id, to.id)) {
- d.addLine(item, "id", id, to.id);
- }
- if (creationTime != to.creationTime) {
- d.addLine(item, "creationTime", creationTime, to.creationTime);
- }
- if (!Objects.equals(enabler, to.enabler)) {
- d.addLine(item, "enabler", enabler, to.enabler);
- }
- if (!Objects.equals(zenPolicy, to.zenPolicy)) {
- d.addLine(item, "zenPolicy", zenPolicy, to.zenPolicy);
- }
- if (modified != to.modified) {
- d.addLine(item, "modified", modified, to.modified);
- }
- if (!Objects.equals(pkg, to.pkg)) {
- d.addLine(item, "pkg", pkg, to.pkg);
- }
- }
-
@Override
public boolean equals(@Nullable Object o) {
if (!(o instanceof ZenRule)) return false;
@@ -2040,40 +1898,6 @@
};
}
- public static class Diff {
- private final ArrayList<String> lines = new ArrayList<>();
-
- @Override
- public String toString() {
- final StringBuilder sb = new StringBuilder("Diff[");
- final int N = lines.size();
- for (int i = 0; i < N; i++) {
- if (i > 0) {
- sb.append(",\n");
- }
- sb.append(lines.get(i));
- }
- return sb.append(']').toString();
- }
-
- private Diff addLine(String item, String action) {
- lines.add(item + ":" + action);
- return this;
- }
-
- public Diff addLine(String item, String subitem, Object from, Object to) {
- return addLine(item + "." + subitem, from, to);
- }
-
- public Diff addLine(String item, Object from, Object to) {
- return addLine(item, from + "->" + to);
- }
-
- public boolean isEmpty() {
- return lines.isEmpty();
- }
- }
-
/**
* Determines whether dnd behavior should mute all ringer-controlled sounds
* This includes notification, ringer and system sounds
diff --git a/core/java/android/service/notification/ZenModeDiff.java b/core/java/android/service/notification/ZenModeDiff.java
new file mode 100644
index 0000000..c7b89eb
--- /dev/null
+++ b/core/java/android/service/notification/ZenModeDiff.java
@@ -0,0 +1,542 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.notification;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * ZenModeDiff is a utility class meant to encapsulate the diff between ZenModeConfigs and their
+ * subcomponents (automatic and manual ZenRules).
+ * @hide
+ */
+public class ZenModeDiff {
+ /**
+ * Enum representing whether the existence of a config or rule has changed (added or removed,
+ * or "none" meaning there is no change, which may either mean both null, or there exists a
+ * diff in fields rather than add/remove).
+ */
+ @IntDef(value = {
+ NONE,
+ ADDED,
+ REMOVED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ExistenceChange{}
+
+ public static final int NONE = 0;
+ public static final int ADDED = 1;
+ public static final int REMOVED = 2;
+
+ /**
+ * Diff class representing an individual field diff.
+ * @param <T> The type of the field.
+ */
+ public static class FieldDiff<T> {
+ private final T mFrom;
+ private final T mTo;
+
+ /**
+ * Constructor to create a FieldDiff object with the given values.
+ * @param from from (old) value
+ * @param to to (new) value
+ */
+ public FieldDiff(@Nullable T from, @Nullable T to) {
+ mFrom = from;
+ mTo = to;
+ }
+
+ /**
+ * Get the "from" value
+ */
+ public T from() {
+ return mFrom;
+ }
+
+ /**
+ * Get the "to" value
+ */
+ public T to() {
+ return mTo;
+ }
+
+ /**
+ * Get the string representation of this field diff, in the form of "from->to".
+ */
+ @Override
+ public String toString() {
+ return mFrom + "->" + mTo;
+ }
+
+ /**
+ * Returns whether this represents an actual diff.
+ */
+ public boolean hasDiff() {
+ // note that Objects.equals handles null values gracefully.
+ return !Objects.equals(mFrom, mTo);
+ }
+ }
+
+ /**
+ * Base diff class that contains info about whether something was added, and a set of named
+ * fields that changed.
+ * Extend for diffs of specific types of objects.
+ */
+ private abstract static class BaseDiff {
+ // Whether the diff was added or removed
+ @ExistenceChange private int mExists = NONE;
+
+ // Map from field name to diffs for any standalone fields in the object.
+ private ArrayMap<String, FieldDiff> mFields = new ArrayMap<>();
+
+ // Functions for actually diffing objects and string representations have to be implemented
+ // by subclasses.
+
+ /**
+ * Return whether this diff represents any changes.
+ */
+ public abstract boolean hasDiff();
+
+ /**
+ * Return a string representation of the diff.
+ */
+ public abstract String toString();
+
+ /**
+ * Constructor that takes the two objects meant to be compared. This constructor sets
+ * whether there is an existence change (added or removed).
+ * @param from previous Object
+ * @param to new Object
+ */
+ BaseDiff(Object from, Object to) {
+ if (from == null) {
+ if (to != null) {
+ mExists = ADDED;
+ }
+ // If both are null, there isn't an existence change; callers/inheritors must handle
+ // the both null case.
+ } else if (to == null) {
+ // in this case, we know that from != null
+ mExists = REMOVED;
+ }
+
+ // Subclasses should implement the actual diffing functionality in their own
+ // constructors.
+ }
+
+ /**
+ * Add a diff for a specific field to the map.
+ * @param name field name
+ * @param diff FieldDiff object representing the diff
+ */
+ final void addField(String name, FieldDiff diff) {
+ mFields.put(name, diff);
+ }
+
+ /**
+ * Returns whether this diff represents a config being newly added.
+ */
+ public final boolean wasAdded() {
+ return mExists == ADDED;
+ }
+
+ /**
+ * Returns whether this diff represents a config being removed.
+ */
+ public final boolean wasRemoved() {
+ return mExists == REMOVED;
+ }
+
+ /**
+ * Returns whether this diff represents an object being either added or removed.
+ */
+ public final boolean hasExistenceChange() {
+ return mExists != NONE;
+ }
+
+ /**
+ * Returns whether there are any individual field diffs.
+ */
+ public final boolean hasFieldDiffs() {
+ return mFields.size() > 0;
+ }
+
+ /**
+ * Returns the diff for the specific named field if it exists
+ */
+ public final FieldDiff getDiffForField(String name) {
+ return mFields.getOrDefault(name, null);
+ }
+
+ /**
+ * Get the set of all field names with some diff.
+ */
+ public final Set<String> fieldNamesWithDiff() {
+ return mFields.keySet();
+ }
+ }
+
+ /**
+ * Diff class representing a diff between two ZenModeConfigs.
+ */
+ public static class ConfigDiff extends BaseDiff {
+ // Rules. Automatic rule map is keyed by the rule name.
+ private final ArrayMap<String, RuleDiff> mAutomaticRulesDiff = new ArrayMap<>();
+ private RuleDiff mManualRuleDiff;
+
+ // Helpers for string generation
+ private static final String ALLOW_CALLS_FROM_FIELD = "allowCallsFrom";
+ private static final String ALLOW_MESSAGES_FROM_FIELD = "allowMessagesFrom";
+ private static final String ALLOW_CONVERSATIONS_FROM_FIELD = "allowConversationsFrom";
+ private static final Set<String> PEOPLE_TYPE_FIELDS =
+ Set.of(ALLOW_CALLS_FROM_FIELD, ALLOW_MESSAGES_FROM_FIELD);
+
+ /**
+ * Create a diff that contains diffs between the "from" and "to" ZenModeConfigs.
+ *
+ * @param from previous ZenModeConfig
+ * @param to new ZenModeConfig
+ */
+ public ConfigDiff(ZenModeConfig from, ZenModeConfig to) {
+ super(from, to);
+ // If both are null skip
+ if (from == null && to == null) {
+ return;
+ }
+ if (hasExistenceChange()) {
+ // either added or removed; return here. otherwise (they're not both null) there's
+ // field diffs.
+ return;
+ }
+
+ // Now we compare all the fields, knowing there's a diff and that neither is null
+ if (from.user != to.user) {
+ addField("user", new FieldDiff<>(from.user, to.user));
+ }
+ if (from.allowAlarms != to.allowAlarms) {
+ addField("allowAlarms", new FieldDiff<>(from.allowAlarms, to.allowAlarms));
+ }
+ if (from.allowMedia != to.allowMedia) {
+ addField("allowMedia", new FieldDiff<>(from.allowMedia, to.allowMedia));
+ }
+ if (from.allowSystem != to.allowSystem) {
+ addField("allowSystem", new FieldDiff<>(from.allowSystem, to.allowSystem));
+ }
+ if (from.allowCalls != to.allowCalls) {
+ addField("allowCalls", new FieldDiff<>(from.allowCalls, to.allowCalls));
+ }
+ if (from.allowReminders != to.allowReminders) {
+ addField("allowReminders",
+ new FieldDiff<>(from.allowReminders, to.allowReminders));
+ }
+ if (from.allowEvents != to.allowEvents) {
+ addField("allowEvents", new FieldDiff<>(from.allowEvents, to.allowEvents));
+ }
+ if (from.allowRepeatCallers != to.allowRepeatCallers) {
+ addField("allowRepeatCallers",
+ new FieldDiff<>(from.allowRepeatCallers, to.allowRepeatCallers));
+ }
+ if (from.allowMessages != to.allowMessages) {
+ addField("allowMessages",
+ new FieldDiff<>(from.allowMessages, to.allowMessages));
+ }
+ if (from.allowConversations != to.allowConversations) {
+ addField("allowConversations",
+ new FieldDiff<>(from.allowConversations, to.allowConversations));
+ }
+ if (from.allowCallsFrom != to.allowCallsFrom) {
+ addField("allowCallsFrom",
+ new FieldDiff<>(from.allowCallsFrom, to.allowCallsFrom));
+ }
+ if (from.allowMessagesFrom != to.allowMessagesFrom) {
+ addField("allowMessagesFrom",
+ new FieldDiff<>(from.allowMessagesFrom, to.allowMessagesFrom));
+ }
+ if (from.allowConversationsFrom != to.allowConversationsFrom) {
+ addField("allowConversationsFrom",
+ new FieldDiff<>(from.allowConversationsFrom, to.allowConversationsFrom));
+ }
+ if (from.suppressedVisualEffects != to.suppressedVisualEffects) {
+ addField("suppressedVisualEffects",
+ new FieldDiff<>(from.suppressedVisualEffects, to.suppressedVisualEffects));
+ }
+ if (from.areChannelsBypassingDnd != to.areChannelsBypassingDnd) {
+ addField("areChannelsBypassingDnd",
+ new FieldDiff<>(from.areChannelsBypassingDnd, to.areChannelsBypassingDnd));
+ }
+
+ // Compare automatic and manual rules
+ final ArraySet<String> allRules = new ArraySet<>();
+ addKeys(allRules, from.automaticRules);
+ addKeys(allRules, to.automaticRules);
+ final int num = allRules.size();
+ for (int i = 0; i < num; i++) {
+ final String rule = allRules.valueAt(i);
+ final ZenModeConfig.ZenRule
+ fromRule = from.automaticRules != null ? from.automaticRules.get(rule)
+ : null;
+ final ZenModeConfig.ZenRule
+ toRule = to.automaticRules != null ? to.automaticRules.get(rule) : null;
+ RuleDiff ruleDiff = new RuleDiff(fromRule, toRule);
+ if (ruleDiff.hasDiff()) {
+ mAutomaticRulesDiff.put(rule, ruleDiff);
+ }
+ }
+ // If there's no diff this may turn out to be null, but that's also fine
+ RuleDiff manualRuleDiff = new RuleDiff(from.manualRule, to.manualRule);
+ if (manualRuleDiff.hasDiff()) {
+ mManualRuleDiff = manualRuleDiff;
+ }
+ }
+
+ private static <T> void addKeys(ArraySet<T> set, ArrayMap<T, ?> map) {
+ if (map != null) {
+ for (int i = 0; i < map.size(); i++) {
+ set.add(map.keyAt(i));
+ }
+ }
+ }
+
+ /**
+ * Returns whether this diff object contains any diffs in any field.
+ */
+ @Override
+ public boolean hasDiff() {
+ return hasExistenceChange()
+ || hasFieldDiffs()
+ || mManualRuleDiff != null
+ || mAutomaticRulesDiff.size() > 0;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("Diff[");
+ if (!hasDiff()) {
+ sb.append("no changes");
+ }
+
+ // If added or deleted, then that's just the end of it
+ if (hasExistenceChange()) {
+ if (wasAdded()) {
+ sb.append("added");
+ } else if (wasRemoved()) {
+ sb.append("removed");
+ }
+ }
+
+ // Handle top-level field change
+ boolean first = true;
+ for (String key : fieldNamesWithDiff()) {
+ FieldDiff diff = getDiffForField(key);
+ if (diff == null) {
+ // this shouldn't happen, but
+ continue;
+ }
+ if (first) {
+ first = false;
+ } else {
+ sb.append(",\n");
+ }
+
+ // Some special handling for people- and conversation-type fields for readability
+ if (PEOPLE_TYPE_FIELDS.contains(key)) {
+ sb.append(key);
+ sb.append(":");
+ sb.append(ZenModeConfig.sourceToString((int) diff.from()));
+ sb.append("->");
+ sb.append(ZenModeConfig.sourceToString((int) diff.to()));
+ } else if (key.equals(ALLOW_CONVERSATIONS_FROM_FIELD)) {
+ sb.append(key);
+ sb.append(":");
+ sb.append(ZenPolicy.conversationTypeToString((int) diff.from()));
+ sb.append("->");
+ sb.append(ZenPolicy.conversationTypeToString((int) diff.to()));
+ } else {
+ sb.append(key);
+ sb.append(":");
+ sb.append(diff);
+ }
+ }
+
+ // manual rule
+ if (mManualRuleDiff != null && mManualRuleDiff.hasDiff()) {
+ if (first) {
+ first = false;
+ } else {
+ sb.append(",\n");
+ }
+ sb.append("manualRule:");
+ sb.append(mManualRuleDiff);
+ }
+
+ // automatic rules
+ for (String rule : mAutomaticRulesDiff.keySet()) {
+ RuleDiff diff = mAutomaticRulesDiff.get(rule);
+ if (diff != null && diff.hasDiff()) {
+ if (first) {
+ first = false;
+ } else {
+ sb.append(",\n");
+ }
+ sb.append("automaticRule[");
+ sb.append(rule);
+ sb.append("]:");
+ sb.append(diff);
+ }
+ }
+
+ return sb.append(']').toString();
+ }
+
+ /**
+ * Get the diff in manual rule, if it exists.
+ */
+ public RuleDiff getManualRuleDiff() {
+ return mManualRuleDiff;
+ }
+
+ /**
+ * Get the full map of automatic rule diffs, or null if there are no diffs.
+ */
+ public ArrayMap<String, RuleDiff> getAllAutomaticRuleDiffs() {
+ return (mAutomaticRulesDiff.size() > 0) ? mAutomaticRulesDiff : null;
+ }
+ }
+
+ /**
+ * Diff class representing a change between two ZenRules.
+ */
+ public static class RuleDiff extends BaseDiff {
+ /**
+ * Create a RuleDiff representing the difference between two ZenRule objects.
+ * @param from previous ZenRule
+ * @param to new ZenRule
+ * @return The diff between the two given ZenRules
+ */
+ public RuleDiff(ZenModeConfig.ZenRule from, ZenModeConfig.ZenRule to) {
+ super(from, to);
+ // Short-circuit the both-null case
+ if (from == null && to == null) {
+ return;
+ }
+ // Return if the diff was added or removed
+ if (hasExistenceChange()) {
+ return;
+ }
+
+ if (from.enabled != to.enabled) {
+ addField("enabled", new FieldDiff<>(from.enabled, to.enabled));
+ }
+ if (from.snoozing != to.snoozing) {
+ addField("snoozing", new FieldDiff<>(from.snoozing, to.snoozing));
+ }
+ if (!Objects.equals(from.name, to.name)) {
+ addField("name", new FieldDiff<>(from.name, to.name));
+ }
+ if (from.zenMode != to.zenMode) {
+ addField("zenMode", new FieldDiff<>(from.zenMode, to.zenMode));
+ }
+ if (!Objects.equals(from.conditionId, to.conditionId)) {
+ addField("conditionId", new FieldDiff<>(from.conditionId, to.conditionId));
+ }
+ if (!Objects.equals(from.condition, to.condition)) {
+ addField("condition", new FieldDiff<>(from.condition, to.condition));
+ }
+ if (!Objects.equals(from.component, to.component)) {
+ addField("component", new FieldDiff<>(from.component, to.component));
+ }
+ if (!Objects.equals(from.configurationActivity, to.configurationActivity)) {
+ addField("configurationActivity", new FieldDiff<>(
+ from.configurationActivity, to.configurationActivity));
+ }
+ if (!Objects.equals(from.id, to.id)) {
+ addField("id", new FieldDiff<>(from.id, to.id));
+ }
+ if (from.creationTime != to.creationTime) {
+ addField("creationTime",
+ new FieldDiff<>(from.creationTime, to.creationTime));
+ }
+ if (!Objects.equals(from.enabler, to.enabler)) {
+ addField("enabler", new FieldDiff<>(from.enabler, to.enabler));
+ }
+ if (!Objects.equals(from.zenPolicy, to.zenPolicy)) {
+ addField("zenPolicy", new FieldDiff<>(from.zenPolicy, to.zenPolicy));
+ }
+ if (from.modified != to.modified) {
+ addField("modified", new FieldDiff<>(from.modified, to.modified));
+ }
+ if (!Objects.equals(from.pkg, to.pkg)) {
+ addField("pkg", new FieldDiff<>(from.pkg, to.pkg));
+ }
+ }
+
+ /**
+ * Returns whether this object represents an actual diff.
+ */
+ @Override
+ public boolean hasDiff() {
+ return hasExistenceChange() || hasFieldDiffs();
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("ZenRuleDiff{");
+ // If there's no diff, probably we haven't actually let this object continue existing
+ // but might as well handle this case.
+ if (!hasDiff()) {
+ sb.append("no changes");
+ }
+
+ // If added or deleted, then that's just the end of it
+ if (hasExistenceChange()) {
+ if (wasAdded()) {
+ sb.append("added");
+ } else if (wasRemoved()) {
+ sb.append("removed");
+ }
+ }
+
+ // Go through all of the individual fields
+ boolean first = true;
+ for (String key : fieldNamesWithDiff()) {
+ FieldDiff diff = getDiffForField(key);
+ if (diff == null) {
+ // this shouldn't happen, but
+ continue;
+ }
+ if (first) {
+ first = false;
+ } else {
+ sb.append(", ");
+ }
+
+ sb.append(key);
+ sb.append(":");
+ sb.append(diff);
+ }
+
+ return sb.append("}").toString();
+ }
+ }
+}
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index fcc64b0..68cce4a 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -50,6 +50,7 @@
import android.util.ArraySet;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IVoiceActionCheckCallback;
import com.android.internal.app.IVoiceInteractionManagerService;
@@ -200,6 +201,9 @@
private final Set<HotwordDetector> mActiveDetectors = new ArraySet<>();
+ // True if any of the createAOHD methods should use the test ST module.
+ @GuardedBy("mLock")
+ private boolean mTestModuleForAlwaysOnHotwordDetectorEnabled = false;
private void onDetectorRemoteException(@NonNull IBinder token, int detectorType) {
Log.d(TAG, "onDetectorRemoteException for " + HotwordDetector.detectorTypeToString(
@@ -512,14 +516,14 @@
Objects.requireNonNull(callback);
return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,
/* supportHotwordDetectionService= */ false, /* options= */ null,
- /* sharedMemory= */ null, /* moduleProperties */ null, executor, callback);
+ /* sharedMemory= */ null, /* moduleProperties= */ null, executor, callback);
}
/**
* Same as {@link createAlwaysOnHotwordDetector(String, Locale, Executor,
* AlwaysOnHotwordDetector.Callback)}, but allow explicit selection of the underlying ST
* module to attach to.
- * Use {@link listModuleProperties} to get available modules to attach to.
+ * Use {@link #listModuleProperties()} to get available modules to attach to.
* @hide
*/
@TestApi
@@ -645,14 +649,14 @@
Objects.requireNonNull(callback);
return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,
/* supportHotwordDetectionService= */ true, options, sharedMemory,
- /* moduleProperties */ null, executor, callback);
+ /* moduleProperties= */ null, executor, callback);
}
/**
* Same as {@link createAlwaysOnHotwordDetector(String, Locale,
* PersistableBundle, SharedMemory, Executor, AlwaysOnHotwordDetector.Callback)},
* but allow explicit selection of the underlying ST module to attach to.
- * Use {@link listModuleProperties} to get available modules to attach to.
+ * Use {@link #listModuleProperties()} to get available modules to attach to.
* @hide
*/
@TestApi
@@ -717,6 +721,10 @@
try {
dspDetector.registerOnDestroyListener(this::onHotwordDetectorDestroyed);
+ // Check if we are currently overridden, and should use the test module.
+ if (mTestModuleForAlwaysOnHotwordDetectorEnabled) {
+ moduleProperties = getTestModuleProperties();
+ }
// If moduleProperties is null, the default STModule is used.
dspDetector.initialize(options, sharedMemory, moduleProperties);
} catch (Exception e) {
@@ -990,6 +998,44 @@
return mKeyphraseEnrollmentInfo;
}
+
+ /**
+ * Configure {@link createAlwaysOnHotwordDetector(String, Locale,
+ * SoundTrigger.ModuleProperties, Executor, AlwaysOnHotwordDetector.Callback)}
+ * and similar overloads to utilize the test SoundTrigger module instead of the
+ * actual DSP module.
+ * @param isEnabled - {@code true} if subsequently created {@link AlwaysOnHotwordDetector}
+ * objects should attach to a test module. {@code false} if subsequently created
+ * {@link AlwaysOnHotwordDetector} should attach to the actual DSP module.
+ * @hide
+ */
+ @TestApi
+ public final void setTestModuleForAlwaysOnHotwordDetectorEnabled(boolean isEnabled) {
+ synchronized (mLock) {
+ mTestModuleForAlwaysOnHotwordDetectorEnabled = isEnabled;
+ }
+ }
+
+ /**
+ * Get the {@link SoundTrigger.ModuleProperties} representing the fake
+ * STHAL to attach to via {@link createAlwaysOnHotwordDetector(String, Locale,
+ * SoundTrigger.ModuleProperties, Executor, AlwaysOnHotwordDetector.Callback)} and
+ * similar overloads for test purposes.
+ * @return ModuleProperties to use for test purposes.
+ */
+ private final @NonNull SoundTrigger.ModuleProperties getTestModuleProperties() {
+ var moduleProps = listModuleProperties()
+ .stream()
+ .filter((SoundTrigger.ModuleProperties prop)
+ -> prop.getSupportedModelArch().equals(SoundTrigger.FAKE_HAL_ARCH))
+ .findFirst()
+ .orElse(null);
+ if (moduleProps == null) {
+ throw new IllegalStateException("Fake ST HAL should always be available");
+ }
+ return moduleProps;
+ }
+
/**
* Checks if a given keyphrase and locale are supported to create an
* {@link AlwaysOnHotwordDetector}.
diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java
index d35aff9..3812d37 100644
--- a/core/java/android/view/InputWindowHandle.java
+++ b/core/java/android/view/InputWindowHandle.java
@@ -215,6 +215,7 @@
.append(", scaleFactor=").append(scaleFactor)
.append(", transform=").append(transform)
.append(", windowToken=").append(windowToken)
+ .append(", displayId=").append(displayId)
.append(", isClone=").append((inputConfig & InputConfig.CLONE) != 0)
.toString();
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index ac50d09..cd89a56 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -99,9 +99,16 @@
@Override
public ISurfaceSyncGroup getSurfaceSyncGroup() {
CompletableFuture<ISurfaceSyncGroup> surfaceSyncGroup = new CompletableFuture<>();
- mViewRoot.mHandler.post(
- () -> surfaceSyncGroup.complete(
- mViewRoot.getOrCreateSurfaceSyncGroup().mISurfaceSyncGroup));
+ // If the call came from in process and it's already running on the UI thread, return
+ // results immediately instead of posting to the main thread. If we post to the main
+ // thread, it will block itself and the return value will always be null.
+ if (Thread.currentThread() == mViewRoot.mThread) {
+ return mViewRoot.getOrCreateSurfaceSyncGroup().mISurfaceSyncGroup;
+ } else {
+ mViewRoot.mHandler.post(
+ () -> surfaceSyncGroup.complete(
+ mViewRoot.getOrCreateSurfaceSyncGroup().mISurfaceSyncGroup));
+ }
try {
return surfaceSyncGroup.get(1, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 2f5cd54..055b5cb 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -6946,6 +6946,7 @@
return;
}
final boolean needsStylusPointerIcon = event.isStylusPointer()
+ && event.isHoverEvent()
&& mInputManager.isStylusPointerIconEnabled();
if (needsStylusPointerIcon || event.isFromSource(InputDevice.SOURCE_MOUSE)) {
if (event.getActionMasked() == MotionEvent.ACTION_HOVER_ENTER
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 088065d2..7931d1a 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -8095,12 +8095,14 @@
private boolean mIsInsertModeActive;
private InsertModeTransformationMethod mInsertModeTransformationMethod;
private final Paint mHighlightPaint;
+ private final Path mHighlightPath;
InsertModeController(@NonNull TextView textView) {
mTextView = Objects.requireNonNull(textView);
mIsInsertModeActive = false;
mInsertModeTransformationMethod = null;
mHighlightPaint = new Paint();
+ mHighlightPath = new Path();
// The highlight color is supposed to be 12% of the color primary40. We can't
// directly access Material 3 theme. But because Material 3 sets the colorPrimary to
@@ -8168,10 +8170,8 @@
((InsertModeTransformationMethod.TransformedText) transformedText);
final int highlightStart = insertModeTransformedText.getHighlightStart();
final int highlightEnd = insertModeTransformedText.getHighlightEnd();
- final Layout.SelectionRectangleConsumer consumer =
- (left, top, right, bottom, textSelectionLayout) ->
- canvas.drawRect(left, top, right, bottom, mHighlightPaint);
- layout.getSelection(highlightStart, highlightEnd, consumer);
+ layout.getSelectionPath(highlightStart, highlightEnd, mHighlightPath);
+ canvas.drawPath(mHighlightPath, mHighlightPaint);
}
}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 18874f7..3165654 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1780,6 +1780,21 @@
Object value = getParameterValue(view);
try {
MethodHandle method = getMethod(view, this.methodName, param, true /* async */);
+ // Upload the bitmap to GPU if the parameter is of type Bitmap or Icon.
+ // Since bitmaps in framework are seldomly modified, this is supposed to accelerate
+ // the operations.
+ if (value instanceof Bitmap bitmap) {
+ bitmap.prepareToDraw();
+ }
+
+ if (value instanceof Icon icon
+ && (icon.getType() == Icon.TYPE_BITMAP
+ || icon.getType() == Icon.TYPE_ADAPTIVE_BITMAP)) {
+ Bitmap bitmap = icon.getBitmap();
+ if (bitmap != null) {
+ bitmap.prepareToDraw();
+ }
+ }
if (method != null) {
Runnable endAction = (Runnable) method.invoke(view, value);
diff --git a/core/java/com/android/internal/app/ISoundTriggerService.aidl b/core/java/com/android/internal/app/ISoundTriggerService.aidl
index ab7f602..ed751cb 100644
--- a/core/java/com/android/internal/app/ISoundTriggerService.aidl
+++ b/core/java/com/android/internal/app/ISoundTriggerService.aidl
@@ -16,8 +16,9 @@
package com.android.internal.app;
-import android.media.permission.Identity;
import android.hardware.soundtrigger.SoundTrigger;
+import android.media.permission.Identity;
+import android.media.soundtrigger_middleware.ISoundTriggerInjection;
import com.android.internal.app.ISoundTriggerSession;
/**
@@ -74,4 +75,8 @@
*/
List<SoundTrigger.ModuleProperties> listModuleProperties(in Identity originatorIdentity);
+ /**
+ * Attach an HAL injection interface.
+ */
+ void attachInjection(ISoundTriggerInjection injection);
}
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 6b40d98..24d5afc 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -96,6 +96,21 @@
* @RequiresPermission Manifest.permission.MANAGE_VOICE_KEYPHRASES
*/
int deleteKeyphraseSoundModel(int keyphraseId, in String bcp47Locale);
+
+ /**
+ * Override the persistent enrolled model database with an in-memory
+ * fake for testing purposes.
+ *
+ * @param enabled - {@code true} to enable the test database. {@code false} to enable
+ * the real, persistent database.
+ * @param token - IBinder used to register a death listener to clean-up the override
+ * if tests do not clean up gracefully.
+ */
+ @EnforcePermission("MANAGE_VOICE_KEYPHRASES")
+ @JavaPassthrough(annotation= "@android.annotation.RequiresPermission(" +
+ "android.Manifest.permission.MANAGE_VOICE_KEYPHRASES)")
+ void setModelDatabaseForTestEnabled(boolean enabled, IBinder token);
+
/**
* Indicates if there's a keyphrase sound model available for the given keyphrase ID and the
* user ID of the caller.
@@ -106,6 +121,7 @@
* @param bcp47Locale The BCP47 language tag for the keyphrase's locale.
*/
boolean isEnrolledForKeyphrase(int keyphraseId, String bcp47Locale);
+
/**
* Generates KeyphraseMetadata for an enrolled sound model based on keyphrase string, locale,
* and the user ID of the caller.
diff --git a/core/java/com/android/internal/util/DumpUtils.java b/core/java/com/android/internal/util/DumpUtils.java
index f6d80a5..8fe2b9c 100644
--- a/core/java/com/android/internal/util/DumpUtils.java
+++ b/core/java/com/android/internal/util/DumpUtils.java
@@ -25,6 +25,7 @@
import android.os.Handler;
import android.text.TextUtils;
import android.util.Slog;
+import android.util.SparseArray;
import java.io.PrintWriter;
import java.io.StringWriter;
@@ -312,5 +313,85 @@
|| cn.flattenToString().toLowerCase().contains(filterString.toLowerCase());
};
}
-}
+ /**
+ * Lambda used to dump a key (and its index) while iterating though a collection.
+ */
+ public interface KeyDumper {
+
+ /** Dumps the index and key.*/
+ void dump(int index, int key);
+ }
+
+ /**
+ * Lambda used to dump a value while iterating though a collection.
+ *
+ * @param <T> type of the value.
+ */
+ public interface ValueDumper<T> {
+
+ /** Dumps the value.*/
+ void dump(T value);
+ }
+
+ /**
+ * Dumps a sparse array.
+ */
+ public static void dumpSparseArray(PrintWriter pw, String prefix, SparseArray<?> array,
+ String name) {
+ dumpSparseArray(pw, prefix, array, name, /* keyDumper= */ null, /* valueDumper= */ null);
+ }
+
+ /**
+ * Dumps the values of a sparse array.
+ */
+ public static <T> void dumpSparseArrayValues(PrintWriter pw, String prefix,
+ SparseArray<T> array, String name) {
+ dumpSparseArray(pw, prefix, array, name, (i, k) -> {
+ pw.printf("%s%s", prefix, prefix);
+ }, /* valueDumper= */ null);
+ }
+
+ /**
+ * Dumps a sparse array, customizing each line.
+ */
+ public static <T> void dumpSparseArray(PrintWriter pw, String prefix, SparseArray<T> array,
+ String name, @Nullable KeyDumper keyDumper, @Nullable ValueDumper<T> valueDumper) {
+ int size = array.size();
+ if (size == 0) {
+ pw.print(prefix);
+ pw.print("No ");
+ pw.print(name);
+ pw.println("s");
+ return;
+ }
+ pw.print(prefix);
+ pw.print(size);
+ pw.print(' ');
+ pw.print(name);
+ pw.println("(s):");
+
+ String prefix2 = prefix + prefix;
+ for (int i = 0; i < size; i++) {
+ int key = array.keyAt(i);
+ T value = array.valueAt(i);
+ if (keyDumper != null) {
+ keyDumper.dump(i, key);
+ } else {
+ pw.print(prefix2);
+ pw.print(i);
+ pw.print(": ");
+ pw.print(key);
+ pw.print("->");
+ }
+ if (value == null) {
+ pw.print("(null)");
+ } else if (valueDumper != null) {
+ valueDumper.dump(value);
+ } else {
+ pw.print(value);
+ }
+ pw.println();
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
index 316c70c..c6bb07b 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
@@ -26,7 +26,7 @@
import android.compat.testing.PlatformCompatChangeRule;
import android.os.Bundle;
import android.platform.test.annotations.IwTest;
-import android.platform.test.annotations.Postsubmit;
+import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
import android.util.PollingCheck;
import android.view.View;
@@ -60,7 +60,7 @@
*/
@RunWith(AndroidJUnit4.class)
@LargeTest
-@Postsubmit
+@Presubmit
public class FontScaleConverterActivityTest {
@Rule
public ActivityScenarioRule<TestActivity> rule = new ActivityScenarioRule<>(TestActivity.class);
diff --git a/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java b/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java
new file mode 100644
index 0000000..66f3bca
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.biometrics;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.junit.MockitoRule;
+
+import java.util.concurrent.Executor;
+
+
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public class BiometricPromptTest {
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private IAuthService mService;
+ private BiometricPrompt mBiometricPrompt;
+
+ private CancellationSignal mCancellationSignal;
+
+ private final TestLooper mLooper = new TestLooper();
+ private final Handler mHandler = new Handler(mLooper.getLooper());
+ private final Executor mExecutor = mHandler::post;
+
+ @Before
+ public void setUp() throws RemoteException {
+ mBiometricPrompt = new BiometricPrompt.Builder(mContext)
+ .setUseDefaultSubtitle()
+ .setUseDefaultTitle()
+ .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG
+ | BiometricManager.Authenticators.DEVICE_CREDENTIAL)
+ .setService(mService)
+ .build();
+
+ mCancellationSignal = new CancellationSignal();
+ when(mService.authenticate(any(), anyLong(), anyInt(), any(), anyString(), any()))
+ .thenReturn(0L);
+ when(mContext.getPackageName()).thenReturn("BiometricPromptTest");
+ }
+
+ @Test
+ public void testCancellationAfterAuthenticationFailed() throws RemoteException {
+ ArgumentCaptor<IBiometricServiceReceiver> biometricServiceReceiverCaptor =
+ ArgumentCaptor.forClass(IBiometricServiceReceiver.class);
+ BiometricPrompt.AuthenticationCallback callback =
+ new BiometricPrompt.AuthenticationCallback() {
+ @Override
+ public void onAuthenticationError(int errorCode, CharSequence errString) {
+ super.onAuthenticationError(errorCode, errString);
+ }};
+ mBiometricPrompt.authenticate(mCancellationSignal, mExecutor, callback);
+ mLooper.dispatchAll();
+
+ verify(mService).authenticate(any(), anyLong(), anyInt(),
+ biometricServiceReceiverCaptor.capture(), anyString(), any());
+
+ biometricServiceReceiverCaptor.getValue().onAuthenticationFailed();
+ mLooper.dispatchAll();
+ mCancellationSignal.cancel();
+
+ verify(mService).cancelAuthentication(any(), anyString(), anyLong());
+ }
+}
diff --git a/core/tests/coretests/src/android/hardware/biometrics/OWNERS b/core/tests/coretests/src/android/hardware/biometrics/OWNERS
new file mode 100644
index 0000000..6a2192a
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/biometrics/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/biometrics/OWNERS
diff --git a/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java b/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java
index 4716312..36c2a62 100644
--- a/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java
@@ -23,16 +23,25 @@
import static com.android.internal.util.DumpUtils.isPlatformNonCriticalPackage;
import static com.android.internal.util.DumpUtils.isPlatformPackage;
+import static com.google.common.truth.Truth.assertWithMessage;
+
import android.content.ComponentName;
+import android.util.SparseArray;
import junit.framework.TestCase;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
/**
* Run with:
atest FrameworksCoreTests:DumpUtilsTest
*/
public class DumpUtilsTest extends TestCase {
+ private final StringWriter mStringWriter = new StringWriter();
+ private final PrintWriter mPrintWriter = new PrintWriter(mStringWriter);
+
private static ComponentName cn(String componentName) {
if (componentName == null) {
return null;
@@ -168,4 +177,144 @@
Integer.toHexString(System.identityHashCode(component))).test(
wcn("com.google/.abc")));
}
+
+ public void testDumpSparseArray_empty() {
+ SparseArray<String> array = new SparseArray<>();
+
+ DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ "...", array, "whatever");
+
+ String output = flushPrintWriter();
+
+ assertWithMessage("empty array dump").that(output).isEqualTo("...No whatevers\n");
+ }
+
+ public void testDumpSparseArray_oneElement() {
+ SparseArray<String> array = new SparseArray<>();
+ array.put(1, "uno");
+
+ DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "number");
+
+ String output = flushPrintWriter();
+
+ assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+ + ".1 number(s):\n"
+ + "..0: 1->uno\n");
+ }
+
+ public void testDumpSparseArray_oneNullElement() {
+ SparseArray<String> array = new SparseArray<>();
+ array.put(1, null);
+
+ DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "NULL");
+
+ String output = flushPrintWriter();
+
+ assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+ + ".1 NULL(s):\n"
+ + "..0: 1->(null)\n");
+ }
+
+ public void testDumpSparseArray_multipleElements() {
+ SparseArray<String> array = new SparseArray<>();
+ array.put(1, "uno");
+ array.put(2, "duo");
+ array.put(42, null);
+
+ DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "number");
+
+ String output = flushPrintWriter();
+
+ assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+ + ".3 number(s):\n"
+ + "..0: 1->uno\n"
+ + "..1: 2->duo\n"
+ + "..2: 42->(null)\n");
+ }
+
+ public void testDumpSparseArray_keyDumperOnly() {
+ SparseArray<String> array = new SparseArray<>();
+ array.put(1, "uno");
+ array.put(2, "duo");
+ array.put(42, null);
+
+ DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "number",
+ (i, k) -> {
+ mPrintWriter.printf("_%d=%d_", i, k);
+ }, /* valueDumper= */ null);
+
+ String output = flushPrintWriter();
+
+ assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+ + ".3 number(s):\n"
+ + "_0=1_uno\n"
+ + "_1=2_duo\n"
+ + "_2=42_(null)\n");
+ }
+
+ public void testDumpSparseArray_valueDumperOnly() {
+ SparseArray<String> array = new SparseArray<>();
+ array.put(1, "uno");
+ array.put(2, "duo");
+ array.put(42, null);
+
+ DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "number",
+ /* keyDumper= */ null,
+ s -> {
+ mPrintWriter.print(s.toUpperCase());
+ });
+
+ String output = flushPrintWriter();
+
+ assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+ + ".3 number(s):\n"
+ + "..0: 1->UNO\n"
+ + "..1: 2->DUO\n"
+ + "..2: 42->(null)\n");
+ }
+
+ public void testDumpSparseArray_keyAndValueDumpers() {
+ SparseArray<String> array = new SparseArray<>();
+ array.put(1, "uno");
+ array.put(2, "duo");
+ array.put(42, null);
+
+ DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "number",
+ (i, k) -> {
+ mPrintWriter.printf("_%d=%d_", i, k);
+ },
+ s -> {
+ mPrintWriter.print(s.toUpperCase());
+ });
+
+ String output = flushPrintWriter();
+
+ assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+ + ".3 number(s):\n"
+ + "_0=1_UNO\n"
+ + "_1=2_DUO\n"
+ + "_2=42_(null)\n");
+ }
+
+ public void testDumpSparseArrayValues() {
+ SparseArray<String> array = new SparseArray<>();
+ array.put(1, "uno");
+ array.put(2, "duo");
+ array.put(42, null);
+
+ DumpUtils.dumpSparseArrayValues(mPrintWriter, /* prefix= */ ".", array, "number");
+
+ String output = flushPrintWriter();
+
+ assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+ + ".3 numbers:\n"
+ + "..uno\n"
+ + "..duo\n"
+ + "..(null)\n");
+ }
+
+ private String flushPrintWriter() {
+ mPrintWriter.flush();
+
+ return mStringWriter.toString();
+ }
}
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 25b074d..2307d60 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -401,8 +401,9 @@
/**
* This is called by methods that want to throw an exception if the bitmap
* has already been recycled.
+ * @hide
*/
- private void checkRecycled(String errorMessage) {
+ void checkRecycled(String errorMessage) {
if (mRecycled) {
throw new IllegalStateException(errorMessage);
}
diff --git a/graphics/java/android/graphics/BitmapShader.java b/graphics/java/android/graphics/BitmapShader.java
index 2f6dd46..5c06577 100644
--- a/graphics/java/android/graphics/BitmapShader.java
+++ b/graphics/java/android/graphics/BitmapShader.java
@@ -120,6 +120,7 @@
if (bitmap == null) {
throw new IllegalArgumentException("Bitmap must be non-null");
}
+ bitmap.checkRecycled("Cannot create BitmapShader for recycled bitmap");
mBitmap = bitmap;
mTileX = tileX;
mTileY = tileY;
@@ -188,6 +189,8 @@
/** @hide */
@Override
protected long createNativeInstance(long nativeMatrix, boolean filterFromPaint) {
+ mBitmap.checkRecycled("BitmapShader's bitmap has been recycled");
+
boolean enableLinearFilter = mFilterMode == FILTER_MODE_LINEAR;
if (mFilterMode == FILTER_MODE_DEFAULT) {
mFilterFromPaint = filterFromPaint;
diff --git a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
index d785c3c..f26b50e 100644
--- a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
+++ b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
@@ -21,10 +21,7 @@
import android.content.Context;
import android.content.pm.FeatureInfo;
import android.content.pm.PackageManager;
-import android.os.RemoteException;
import android.os.ServiceManager;
-import android.security.GenerateRkpKey;
-import android.security.keymaster.KeymasterDefs;
class CredstoreIdentityCredentialStore extends IdentityCredentialStore {
@@ -125,18 +122,7 @@
@NonNull String docType) throws AlreadyPersonalizedException,
DocTypeNotSupportedException {
try {
- IWritableCredential wc;
- wc = mStore.createCredential(credentialName, docType);
- try {
- GenerateRkpKey keyGen = new GenerateRkpKey(mContext);
- // We don't know what the security level is for the backing keymint, so go ahead and
- // poke the provisioner for both TEE and SB.
- keyGen.notifyKeyGenerated(KeymasterDefs.KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT);
- keyGen.notifyKeyGenerated(KeymasterDefs.KM_SECURITY_LEVEL_STRONGBOX);
- } catch (RemoteException e) {
- // Not really an error state. Does not apply at all if RKP is unsupported or
- // disabled on a given device.
- }
+ IWritableCredential wc = mStore.createCredential(credentialName, docType);
return new CredstoreWritableIdentityCredential(mContext, credentialName, docType, wc);
} catch (android.os.RemoteException e) {
throw new RuntimeException("Unexpected RemoteException ", e);
diff --git a/keystore/java/android/security/GenerateRkpKey.java b/keystore/java/android/security/GenerateRkpKey.java
deleted file mode 100644
index 6981332..0000000
--- a/keystore/java/android/security/GenerateRkpKey.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.security;
-
-import android.annotation.CheckResult;
-import android.annotation.IntDef;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-
-/**
- * GenerateKey is a helper class to handle interactions between Keystore and the RemoteProvisioner
- * app. There are two cases where Keystore should use this class.
- *
- * (1) : An app generates a new attested key pair, so Keystore calls notifyKeyGenerated to let the
- * RemoteProvisioner app check if the state of the attestation key pool is getting low enough
- * to warrant provisioning more attestation certificates early.
- *
- * (2) : An app attempts to generate a new key pair, but the keystore service discovers it is out of
- * attestation key pairs and cannot provide one for the given application. Keystore can then
- * make a blocking call on notifyEmpty to allow the RemoteProvisioner app to get another
- * attestation certificate chain provisioned.
- *
- * In most cases, the proper usage of (1) should preclude the need for (2).
- *
- * @hide
- */
-public class GenerateRkpKey {
- private static final String TAG = "GenerateRkpKey";
-
- private static final int NOTIFY_EMPTY = 0;
- private static final int NOTIFY_KEY_GENERATED = 1;
- private static final int TIMEOUT_MS = 1000;
-
- private IGenerateRkpKeyService mBinder;
- private Context mContext;
- private CountDownLatch mCountDownLatch;
-
- /** @hide */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(flag = true, value = {
- IGenerateRkpKeyService.Status.OK,
- IGenerateRkpKeyService.Status.NO_NETWORK_CONNECTIVITY,
- IGenerateRkpKeyService.Status.NETWORK_COMMUNICATION_ERROR,
- IGenerateRkpKeyService.Status.DEVICE_NOT_REGISTERED,
- IGenerateRkpKeyService.Status.HTTP_CLIENT_ERROR,
- IGenerateRkpKeyService.Status.HTTP_SERVER_ERROR,
- IGenerateRkpKeyService.Status.HTTP_UNKNOWN_ERROR,
- IGenerateRkpKeyService.Status.INTERNAL_ERROR,
- })
- public @interface Status {
- }
-
- private ServiceConnection mConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName className, IBinder service) {
- mBinder = IGenerateRkpKeyService.Stub.asInterface(service);
- mCountDownLatch.countDown();
- }
-
- @Override public void onBindingDied(ComponentName className) {
- mCountDownLatch.countDown();
- }
-
- @Override
- public void onServiceDisconnected(ComponentName className) {
- mBinder = null;
- }
- };
-
- /**
- * Constructor which takes a Context object.
- */
- public GenerateRkpKey(Context context) {
- mContext = context;
- }
-
- @Status
- private int bindAndSendCommand(int command, int securityLevel) throws RemoteException {
- Intent intent = new Intent(IGenerateRkpKeyService.class.getName());
- ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
- int returnCode = IGenerateRkpKeyService.Status.OK;
- if (comp == null) {
- // On a system that does not use RKP, the RemoteProvisioner app won't be installed.
- return returnCode;
- }
- intent.setComponent(comp);
- mCountDownLatch = new CountDownLatch(1);
- Executor executor = Executors.newCachedThreadPool();
- if (!mContext.bindService(intent, Context.BIND_AUTO_CREATE, executor, mConnection)) {
- throw new RemoteException("Failed to bind to GenerateRkpKeyService");
- }
- try {
- mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {
- Log.e(TAG, "Interrupted: ", e);
- }
- if (mBinder != null) {
- switch (command) {
- case NOTIFY_EMPTY:
- returnCode = mBinder.generateKey(securityLevel);
- break;
- case NOTIFY_KEY_GENERATED:
- mBinder.notifyKeyGenerated(securityLevel);
- break;
- default:
- Log.e(TAG, "Invalid case for command");
- }
- } else {
- Log.e(TAG, "Binder object is null; failed to bind to GenerateRkpKeyService.");
- returnCode = IGenerateRkpKeyService.Status.INTERNAL_ERROR;
- }
- mContext.unbindService(mConnection);
- return returnCode;
- }
-
- /**
- * Fulfills the use case of (2) described in the class documentation. Blocks until the
- * RemoteProvisioner application can get new attestation keys signed by the server.
- * @return the status of the key generation
- */
- @CheckResult
- @Status
- public int notifyEmpty(int securityLevel) throws RemoteException {
- return bindAndSendCommand(NOTIFY_EMPTY, securityLevel);
- }
-
- /**
- * Fulfills the use case of (1) described in the class documentation. Non blocking call.
- */
- public void notifyKeyGenerated(int securityLevel) throws RemoteException {
- bindAndSendCommand(NOTIFY_KEY_GENERATED, securityLevel);
- }
-}
diff --git a/keystore/java/android/security/GenerateRkpKeyException.java b/keystore/java/android/security/GenerateRkpKeyException.java
deleted file mode 100644
index a2d65e4..0000000
--- a/keystore/java/android/security/GenerateRkpKeyException.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.security;
-
-/**
- * Thrown on problems in attempting to attest to a key using a remotely provisioned key.
- *
- * @hide
- */
-public class GenerateRkpKeyException extends Exception {
-
- /**
- * Constructs a new {@code GenerateRkpKeyException}.
- */
- public GenerateRkpKeyException() {
- }
-}
diff --git a/keystore/java/android/security/IGenerateRkpKeyService.aidl b/keystore/java/android/security/IGenerateRkpKeyService.aidl
deleted file mode 100644
index eeaeb27..0000000
--- a/keystore/java/android/security/IGenerateRkpKeyService.aidl
+++ /dev/null
@@ -1,60 +0,0 @@
-/**
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.security;
-
-/**
- * Interface to allow the framework to notify the RemoteProvisioner app when keys are empty. This
- * will be used if Keystore replies with an error code NO_KEYS_AVAILABLE in response to an
- * attestation request. The framework can then synchronously call generateKey() to get more
- * attestation keys generated and signed. Upon return, the caller can be certain an attestation key
- * is available.
- *
- * @hide
- */
-interface IGenerateRkpKeyService {
- @JavaDerive(toString=true)
- @Backing(type="int")
- enum Status {
- /** No error(s) occurred */
- OK = 0,
- /** Unable to provision keys due to a lack of internet connectivity. */
- NO_NETWORK_CONNECTIVITY = 1,
- /** An error occurred while communicating with the RKP server. */
- NETWORK_COMMUNICATION_ERROR = 2,
- /** The given device was not registered with the RKP backend. */
- DEVICE_NOT_REGISTERED = 4,
- /** The RKP server returned an HTTP client error, indicating a misbehaving client. */
- HTTP_CLIENT_ERROR = 5,
- /** The RKP server returned an HTTP server error, indicating something went wrong on the server. */
- HTTP_SERVER_ERROR = 6,
- /** The RKP server returned an HTTP status that is unknown. This should never happen. */
- HTTP_UNKNOWN_ERROR = 7,
- /** An unexpected internal error occurred. This should never happen. */
- INTERNAL_ERROR = 8,
- }
-
- /**
- * Ping the provisioner service to let it know an app generated a key. This may or may not have
- * consumed a remotely provisioned attestation key, so the RemoteProvisioner app should check.
- */
- oneway void notifyKeyGenerated(in int securityLevel);
-
- /**
- * Ping the provisioner service to indicate there are no remaining attestation keys left.
- */
- Status generateKey(in int securityLevel);
-}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index c3b0f9b..474b7ea 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -20,7 +20,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.ActivityThread;
import android.content.Context;
import android.hardware.security.keymint.EcCurve;
import android.hardware.security.keymint.KeyParameter;
@@ -28,9 +27,6 @@
import android.hardware.security.keymint.SecurityLevel;
import android.hardware.security.keymint.Tag;
import android.os.Build;
-import android.os.RemoteException;
-import android.security.GenerateRkpKey;
-import android.security.IGenerateRkpKeyService;
import android.security.KeyPairGeneratorSpec;
import android.security.KeyStore2;
import android.security.KeyStoreException;
@@ -621,45 +617,6 @@
@Override
public KeyPair generateKeyPair() {
- GenerateKeyPairHelperResult result = new GenerateKeyPairHelperResult(0, null);
- for (int i = 0; i < 2; i++) {
- /**
- * NOTE: There is no need to delay between re-tries because the call to
- * GenerateRkpKey.notifyEmpty() will delay for a while before returning.
- */
- result = generateKeyPairHelper();
- if (result.rkpStatus == KeyStoreException.RKP_SUCCESS && result.keyPair != null) {
- return result.keyPair;
- }
- }
-
- // RKP failure
- if (result.rkpStatus != KeyStoreException.RKP_SUCCESS) {
- KeyStoreException ksException = new KeyStoreException(ResponseCode.OUT_OF_KEYS,
- "Could not get RKP keys", result.rkpStatus);
- throw new ProviderException("Failed to provision new attestation keys.", ksException);
- }
-
- return result.keyPair;
- }
-
- private static class GenerateKeyPairHelperResult {
- // Zero indicates success, non-zero indicates failure. Values should be
- // {@link android.security.KeyStoreException#RKP_TEMPORARILY_UNAVAILABLE},
- // {@link android.security.KeyStoreException#RKP_SERVER_REFUSED_ISSUANCE},
- // {@link android.security.KeyStoreException#RKP_FETCHING_PENDING_CONNECTIVITY}
- // {@link android.security.KeyStoreException#RKP_FETCHING_PENDING_SOFTWARE_REBOOT}
- public final int rkpStatus;
- @Nullable
- public final KeyPair keyPair;
-
- private GenerateKeyPairHelperResult(int rkpStatus, KeyPair keyPair) {
- this.rkpStatus = rkpStatus;
- this.keyPair = keyPair;
- }
- }
-
- private GenerateKeyPairHelperResult generateKeyPairHelper() {
if (mKeyStore == null || mSpec == null) {
throw new IllegalStateException("Not initialized");
}
@@ -697,26 +654,12 @@
AndroidKeyStorePublicKey publicKey =
AndroidKeyStoreProvider.makeAndroidKeyStorePublicKeyFromKeyEntryResponse(
descriptor, metadata, iSecurityLevel, mKeymasterAlgorithm);
- GenerateRkpKey keyGen = new GenerateRkpKey(ActivityThread
- .currentApplication());
- try {
- if (mSpec.getAttestationChallenge() != null) {
- keyGen.notifyKeyGenerated(securityLevel);
- }
- } catch (RemoteException e) {
- // This is not really an error state, and necessarily does not apply to non RKP
- // systems or hybrid systems where RKP is not currently turned on.
- Log.d(TAG, "Couldn't connect to the RemoteProvisioner backend.", e);
- }
success = true;
- KeyPair kp = new KeyPair(publicKey, publicKey.getPrivateKey());
- return new GenerateKeyPairHelperResult(0, kp);
+ return new KeyPair(publicKey, publicKey.getPrivateKey());
} catch (KeyStoreException e) {
switch (e.getErrorCode()) {
case KeymasterDefs.KM_ERROR_HARDWARE_TYPE_UNAVAILABLE:
throw new StrongBoxUnavailableException("Failed to generated key pair.", e);
- case ResponseCode.OUT_OF_KEYS:
- return checkIfRetryableOrThrow(e, securityLevel);
default:
ProviderException p = new ProviderException("Failed to generate key pair.", e);
if ((mSpec.getPurposes() & KeyProperties.PURPOSE_WRAP_KEY) != 0) {
@@ -742,55 +685,6 @@
}
}
- // In case keystore reports OUT_OF_KEYS, call this handler in an attempt to remotely provision
- // some keys.
- GenerateKeyPairHelperResult checkIfRetryableOrThrow(KeyStoreException e, int securityLevel) {
- GenerateRkpKey keyGen = new GenerateRkpKey(ActivityThread
- .currentApplication());
- KeyStoreException ksException;
- try {
- final int keyGenStatus = keyGen.notifyEmpty(securityLevel);
- // Default stance: temporary error. This is a hint to the caller to try again with
- // exponential back-off.
- int rkpStatus;
- switch (keyGenStatus) {
- case IGenerateRkpKeyService.Status.NO_NETWORK_CONNECTIVITY:
- rkpStatus = KeyStoreException.RKP_FETCHING_PENDING_CONNECTIVITY;
- break;
- case IGenerateRkpKeyService.Status.DEVICE_NOT_REGISTERED:
- rkpStatus = KeyStoreException.RKP_SERVER_REFUSED_ISSUANCE;
- break;
- case IGenerateRkpKeyService.Status.OK:
- // Explicitly return not-OK here so we retry in generateKeyPair. All other cases
- // should throw because a retry doesn't make sense if we didn't actually
- // provision fresh keys.
- return new GenerateKeyPairHelperResult(
- KeyStoreException.RKP_TEMPORARILY_UNAVAILABLE, null);
- case IGenerateRkpKeyService.Status.NETWORK_COMMUNICATION_ERROR:
- case IGenerateRkpKeyService.Status.HTTP_CLIENT_ERROR:
- case IGenerateRkpKeyService.Status.HTTP_SERVER_ERROR:
- case IGenerateRkpKeyService.Status.HTTP_UNKNOWN_ERROR:
- case IGenerateRkpKeyService.Status.INTERNAL_ERROR:
- default:
- // These errors really should never happen. The best we can do is assume they
- // are transient and hint to the caller to retry with back-off.
- rkpStatus = KeyStoreException.RKP_TEMPORARILY_UNAVAILABLE;
- break;
- }
- ksException = new KeyStoreException(
- ResponseCode.OUT_OF_KEYS,
- "Out of RKP keys due to IGenerateRkpKeyService status: " + keyGenStatus,
- rkpStatus);
- } catch (RemoteException f) {
- ksException = new KeyStoreException(
- ResponseCode.OUT_OF_KEYS,
- "Remote exception: " + f.getMessage(),
- KeyStoreException.RKP_TEMPORARILY_UNAVAILABLE);
- }
- ksException.initCause(e);
- throw new ProviderException("Failed to provision new attestation keys.", ksException);
- }
-
private void addAttestationParameters(@NonNull List<KeyParameter> params)
throws ProviderException, IllegalArgumentException, DeviceIdAttestationException {
byte[] challenge = mSpec.getAttestationChallenge();
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 dd91a37..04cb17c 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
@@ -690,6 +690,10 @@
if (options1 == null) options1 = new Bundle();
if (taskId2 == INVALID_TASK_ID) {
// Launching a solo task.
+ // Exit split first if this task under split roots.
+ if (mMainStage.containsTask(taskId1) || mSideStage.containsTask(taskId1)) {
+ exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
+ }
ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter));
options1 = activityOptions.toBundle();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 060dc4e..dfde7e6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -110,19 +110,11 @@
final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
final WindowContainerTransaction wct = new WindowContainerTransaction();
- final int outsetLeftId = R.dimen.freeform_resize_handle;
- final int outsetTopId = R.dimen.freeform_resize_handle;
- final int outsetRightId = R.dimen.freeform_resize_handle;
- final int outsetBottomId = R.dimen.freeform_resize_handle;
-
mRelayoutParams.reset();
mRelayoutParams.mRunningTaskInfo = taskInfo;
mRelayoutParams.mLayoutResId = R.layout.caption_window_decor;
mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height;
mRelayoutParams.mShadowRadiusId = shadowRadiusID;
- if (isDragResizeable) {
- mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId);
- }
relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
// After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index f9c0e60..a004e37 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -208,11 +208,6 @@
final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
final WindowContainerTransaction wct = new WindowContainerTransaction();
- final int outsetLeftId = R.dimen.freeform_resize_handle;
- final int outsetTopId = R.dimen.freeform_resize_handle;
- final int outsetRightId = R.dimen.freeform_resize_handle;
- final int outsetBottomId = R.dimen.freeform_resize_handle;
-
final int windowDecorLayoutId = getDesktopModeWindowDecorLayoutId(
taskInfo.getWindowingMode());
mRelayoutParams.reset();
@@ -220,9 +215,6 @@
mRelayoutParams.mLayoutResId = windowDecorLayoutId;
mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height;
mRelayoutParams.mShadowRadiusId = shadowRadiusID;
- if (isDragResizeable) {
- mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId);
- }
relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
// After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
@@ -424,13 +416,12 @@
if (mRelayoutParams.mLayoutResId
== R.layout.desktop_mode_app_controls_window_decor) {
// Align the handle menu to the left of the caption.
- menuX = mRelayoutParams.mCaptionX - mResult.mDecorContainerOffsetX + mMarginMenuStart;
- menuY = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY + mMarginMenuTop;
+ menuX = mRelayoutParams.mCaptionX + mMarginMenuStart;
+ menuY = mRelayoutParams.mCaptionY + mMarginMenuTop;
} else {
// Position the handle menu at the center of the caption.
- menuX = mRelayoutParams.mCaptionX + (captionWidth / 2) - (mMenuWidth / 2)
- - mResult.mDecorContainerOffsetX;
- menuY = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY + mMarginMenuStart;
+ menuX = mRelayoutParams.mCaptionX + (captionWidth / 2) - (mMenuWidth / 2);
+ menuY = mRelayoutParams.mCaptionY + mMarginMenuStart;
}
// App Info pill setup.
@@ -497,23 +488,18 @@
final boolean pointInAppInfoPill = pointInView(
mHandleMenuAppInfoPill.mWindowViewHost.getView(),
- inputPoint.x - mHandleMenuAppInfoPillPosition.x - mResult.mDecorContainerOffsetX,
- inputPoint.y - mHandleMenuAppInfoPillPosition.y
- - mResult.mDecorContainerOffsetY);
+ inputPoint.x - mHandleMenuAppInfoPillPosition.x,
+ inputPoint.y - mHandleMenuAppInfoPillPosition.y);
boolean pointInWindowingPill = false;
if (mHandleMenuWindowingPill != null) {
pointInWindowingPill = pointInView(mHandleMenuWindowingPill.mWindowViewHost.getView(),
- inputPoint.x - mHandleMenuWindowingPillPosition.x
- - mResult.mDecorContainerOffsetX,
- inputPoint.y - mHandleMenuWindowingPillPosition.y
- - mResult.mDecorContainerOffsetY);
+ inputPoint.x - mHandleMenuWindowingPillPosition.x,
+ inputPoint.y - mHandleMenuWindowingPillPosition.y);
}
final boolean pointInMoreActionsPill = pointInView(
mHandleMenuMoreActionsPill.mWindowViewHost.getView(),
- inputPoint.x - mHandleMenuMoreActionsPillPosition.x
- - mResult.mDecorContainerOffsetX,
- inputPoint.y - mHandleMenuMoreActionsPillPosition.y
- - mResult.mDecorContainerOffsetY);
+ inputPoint.x - mHandleMenuMoreActionsPillPosition.x,
+ inputPoint.y - mHandleMenuMoreActionsPillPosition.y);
if (!pointInAppInfoPill && !pointInWindowingPill
&& !pointInMoreActionsPill && !pointInOpenMenuButton) {
closeHandleMenu();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 8cb575c..d5437c7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -64,8 +64,8 @@
private final TaskResizeInputEventReceiver mInputEventReceiver;
private final DragPositioningCallback mCallback;
- private int mWidth;
- private int mHeight;
+ private int mTaskWidth;
+ private int mTaskHeight;
private int mResizeHandleThickness;
private int mCornerSize;
@@ -128,78 +128,84 @@
* This is also used to update the touch regions of this handler every event dispatched here is
* a potential resize request.
*
- * @param width The width of the drag resize handler in pixels, including resize handle
- * thickness. That is task width + 2 * resize handle thickness.
- * @param height The height of the drag resize handler in pixels, including resize handle
- * thickness. That is task height + 2 * resize handle thickness.
+ * @param taskWidth The width of the task.
+ * @param taskHeight The height of the task.
* @param resizeHandleThickness The thickness of the resize handle in pixels.
* @param cornerSize The size of the resize handle centered in each corner.
* @param touchSlop The distance in pixels user has to drag with touch for it to register as
* a resize action.
*/
- void setGeometry(int width, int height, int resizeHandleThickness, int cornerSize,
+ void setGeometry(int taskWidth, int taskHeight, int resizeHandleThickness, int cornerSize,
int touchSlop) {
- if (mWidth == width && mHeight == height
+ if (mTaskWidth == taskWidth && mTaskHeight == taskHeight
&& mResizeHandleThickness == resizeHandleThickness
&& mCornerSize == cornerSize) {
return;
}
- mWidth = width;
- mHeight = height;
+ mTaskWidth = taskWidth;
+ mTaskHeight = taskHeight;
mResizeHandleThickness = resizeHandleThickness;
mCornerSize = cornerSize;
mDragDetector.setTouchSlop(touchSlop);
Region touchRegion = new Region();
- final Rect topInputBounds = new Rect(0, 0, mWidth, mResizeHandleThickness);
+ final Rect topInputBounds = new Rect(
+ -mResizeHandleThickness,
+ -mResizeHandleThickness,
+ mTaskWidth + mResizeHandleThickness,
+ 0);
touchRegion.union(topInputBounds);
- final Rect leftInputBounds = new Rect(0, mResizeHandleThickness,
- mResizeHandleThickness, mHeight - mResizeHandleThickness);
+ final Rect leftInputBounds = new Rect(
+ -mResizeHandleThickness,
+ 0,
+ 0,
+ mTaskHeight);
touchRegion.union(leftInputBounds);
final Rect rightInputBounds = new Rect(
- mWidth - mResizeHandleThickness, mResizeHandleThickness,
- mWidth, mHeight - mResizeHandleThickness);
+ mTaskWidth,
+ 0,
+ mTaskWidth + mResizeHandleThickness,
+ mTaskHeight);
touchRegion.union(rightInputBounds);
- final Rect bottomInputBounds = new Rect(0, mHeight - mResizeHandleThickness,
- mWidth, mHeight);
+ final Rect bottomInputBounds = new Rect(
+ -mResizeHandleThickness,
+ mTaskHeight,
+ mTaskWidth + mResizeHandleThickness,
+ mTaskHeight + mResizeHandleThickness);
touchRegion.union(bottomInputBounds);
// Set up touch areas in each corner.
int cornerRadius = mCornerSize / 2;
mLeftTopCornerBounds = new Rect(
- mResizeHandleThickness - cornerRadius,
- mResizeHandleThickness - cornerRadius,
- mResizeHandleThickness + cornerRadius,
- mResizeHandleThickness + cornerRadius
- );
+ -cornerRadius,
+ -cornerRadius,
+ cornerRadius,
+ cornerRadius);
touchRegion.union(mLeftTopCornerBounds);
mRightTopCornerBounds = new Rect(
- mWidth - mResizeHandleThickness - cornerRadius,
- mResizeHandleThickness - cornerRadius,
- mWidth - mResizeHandleThickness + cornerRadius,
- mResizeHandleThickness + cornerRadius
- );
+ mTaskWidth - cornerRadius,
+ -cornerRadius,
+ mTaskWidth + cornerRadius,
+ cornerRadius);
touchRegion.union(mRightTopCornerBounds);
mLeftBottomCornerBounds = new Rect(
- mResizeHandleThickness - cornerRadius,
- mHeight - mResizeHandleThickness - cornerRadius,
- mResizeHandleThickness + cornerRadius,
- mHeight - mResizeHandleThickness + cornerRadius
- );
+ -cornerRadius,
+ mTaskHeight - cornerRadius,
+ cornerRadius,
+ mTaskHeight + cornerRadius);
touchRegion.union(mLeftBottomCornerBounds);
mRightBottomCornerBounds = new Rect(
- mWidth - mResizeHandleThickness - cornerRadius,
- mHeight - mResizeHandleThickness - cornerRadius,
- mWidth - mResizeHandleThickness + cornerRadius,
- mHeight - mResizeHandleThickness + cornerRadius
- );
+ mTaskWidth - cornerRadius,
+ mTaskHeight - cornerRadius,
+ mTaskWidth + cornerRadius,
+ mTaskHeight + cornerRadius);
touchRegion.union(mRightBottomCornerBounds);
try {
@@ -358,16 +364,16 @@
@TaskPositioner.CtrlType
private int calculateResizeHandlesCtrlType(float x, float y) {
int ctrlType = 0;
- if (x < mResizeHandleThickness) {
+ if (x < 0) {
ctrlType |= TaskPositioner.CTRL_TYPE_LEFT;
}
- if (x > mWidth - mResizeHandleThickness) {
+ if (x > mTaskWidth) {
ctrlType |= TaskPositioner.CTRL_TYPE_RIGHT;
}
- if (y < mResizeHandleThickness) {
+ if (y < 0) {
ctrlType |= TaskPositioner.CTRL_TYPE_TOP;
}
- if (y > mHeight - mResizeHandleThickness) {
+ if (y > mTaskHeight) {
ctrlType |= TaskPositioner.CTRL_TYPE_BOTTOM;
}
return ctrlType;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 4ebd09f..bc5fd4d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -98,7 +98,6 @@
private final Binder mOwner = new Binder();
private final Rect mCaptionInsetsRect = new Rect();
- private final Rect mTaskSurfaceCrop = new Rect();
private final float[] mTmpColor = new float[3];
WindowDecoration(
@@ -218,21 +217,14 @@
final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
final Resources resources = mDecorWindowContext.getResources();
- outResult.mDecorContainerOffsetX = -loadDimensionPixelSize(resources, params.mOutsetLeftId);
- outResult.mDecorContainerOffsetY = -loadDimensionPixelSize(resources, params.mOutsetTopId);
- outResult.mWidth = taskBounds.width()
- + loadDimensionPixelSize(resources, params.mOutsetRightId)
- - outResult.mDecorContainerOffsetX;
- outResult.mHeight = taskBounds.height()
- + loadDimensionPixelSize(resources, params.mOutsetBottomId)
- - outResult.mDecorContainerOffsetY;
- startT.setPosition(
- mDecorationContainerSurface,
- outResult.mDecorContainerOffsetX, outResult.mDecorContainerOffsetY)
- .setWindowCrop(mDecorationContainerSurface,
- outResult.mWidth, outResult.mHeight)
+ outResult.mWidth = taskBounds.width();
+ outResult.mHeight = taskBounds.height();
+ startT.setWindowCrop(mDecorationContainerSurface, outResult.mWidth, outResult.mHeight)
.show(mDecorationContainerSurface);
+ // TODO(b/270202228): This surface can be removed. Instead, use
+ // |mDecorationContainerSurface| to set the background now that it no longer has outsets
+ // and its crop is set to the task bounds.
// TaskBackgroundSurface
if (mTaskBackgroundSurface == null) {
final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
@@ -250,8 +242,7 @@
mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f;
mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f;
mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f;
- startT.setWindowCrop(mTaskBackgroundSurface, taskBounds.width(),
- taskBounds.height())
+ startT.setWindowCrop(mTaskBackgroundSurface, taskBounds.width(), taskBounds.height())
.setShadowRadius(mTaskBackgroundSurface, shadowRadius)
.setColor(mTaskBackgroundSurface, mTmpColor)
.show(mTaskBackgroundSurface);
@@ -269,11 +260,7 @@
final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
final int captionWidth = taskBounds.width();
- startT.setPosition(
- mCaptionContainerSurface,
- -outResult.mDecorContainerOffsetX + params.mCaptionX,
- -outResult.mDecorContainerOffsetY + params.mCaptionY)
- .setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight)
+ startT.setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight)
.show(mCaptionContainerSurface);
if (mCaptionWindowManager == null) {
@@ -314,14 +301,9 @@
// Task surface itself
Point taskPosition = mTaskInfo.positionInParent;
- mTaskSurfaceCrop.set(
- outResult.mDecorContainerOffsetX,
- outResult.mDecorContainerOffsetY,
- outResult.mWidth + outResult.mDecorContainerOffsetX,
- outResult.mHeight + outResult.mDecorContainerOffsetY);
startT.show(mTaskSurface);
finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y)
- .setCrop(mTaskSurface, mTaskSurfaceCrop);
+ .setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
}
/**
@@ -447,37 +429,15 @@
int mCaptionWidthId;
int mShadowRadiusId;
- int mOutsetTopId;
- int mOutsetBottomId;
- int mOutsetLeftId;
- int mOutsetRightId;
-
int mCaptionX;
int mCaptionY;
- void setOutsets(int leftId, int topId, int rightId, int bottomId) {
- mOutsetLeftId = leftId;
- mOutsetTopId = topId;
- mOutsetRightId = rightId;
- mOutsetBottomId = bottomId;
- }
-
- void setCaptionPosition(int left, int top) {
- mCaptionX = left;
- mCaptionY = top;
- }
-
void reset() {
mLayoutResId = Resources.ID_NULL;
mCaptionHeightId = Resources.ID_NULL;
mCaptionWidthId = Resources.ID_NULL;
mShadowRadiusId = Resources.ID_NULL;
- mOutsetTopId = Resources.ID_NULL;
- mOutsetBottomId = Resources.ID_NULL;
- mOutsetLeftId = Resources.ID_NULL;
- mOutsetRightId = Resources.ID_NULL;
-
mCaptionX = 0;
mCaptionY = 0;
}
@@ -487,14 +447,10 @@
int mWidth;
int mHeight;
T mRootView;
- int mDecorContainerOffsetX;
- int mDecorContainerOffsetY;
void reset() {
mWidth = 0;
mHeight = 0;
- mDecorContainerOffsetX = 0;
- mDecorContainerOffsetY = 0;
mRootView = null;
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index dfa3c10..e8147ff 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -159,14 +159,8 @@
.setVisible(false)
.build();
taskInfo.isFocused = false;
- // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is
- // 64px.
+ // Density is 2. Shadow radius is 10px. Caption height is 64px.
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- mRelayoutParams.setOutsets(
- R.dimen.test_window_decor_left_outset,
- R.dimen.test_window_decor_top_outset,
- R.dimen.test_window_decor_right_outset,
- R.dimen.test_window_decor_bottom_outset);
final SurfaceControl taskSurface = mock(SurfaceControl.class);
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
@@ -213,14 +207,8 @@
.setVisible(true)
.build();
taskInfo.isFocused = true;
- // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is
- // 64px.
+ // Density is 2. Shadow radius is 10px. Caption height is 64px.
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- mRelayoutParams.setOutsets(
- R.dimen.test_window_decor_left_outset,
- R.dimen.test_window_decor_top_outset,
- R.dimen.test_window_decor_right_outset,
- R.dimen.test_window_decor_bottom_outset);
final SurfaceControl taskSurface = mock(SurfaceControl.class);
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
@@ -229,8 +217,7 @@
verify(decorContainerSurfaceBuilder).setParent(taskSurface);
verify(decorContainerSurfaceBuilder).setContainerLayer();
verify(mMockSurfaceControlStartT).setTrustedOverlay(decorContainerSurface, true);
- verify(mMockSurfaceControlStartT).setPosition(decorContainerSurface, -20, -40);
- verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 380, 220);
+ verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 300, 100);
verify(taskBackgroundSurfaceBuilder).setParent(taskSurface);
verify(taskBackgroundSurfaceBuilder).setEffectLayer();
@@ -244,7 +231,6 @@
verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
verify(captionContainerSurfaceBuilder).setContainerLayer();
- verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, 20, 40);
verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
verify(mMockSurfaceControlStartT).show(captionContainerSurface);
@@ -268,12 +254,12 @@
verify(mMockSurfaceControlFinishT)
.setPosition(taskSurface, TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y);
verify(mMockSurfaceControlFinishT)
- .setCrop(taskSurface, new Rect(-20, -40, 360, 180));
+ .setWindowCrop(taskSurface, 300, 100);
verify(mMockSurfaceControlStartT)
.show(taskSurface);
- assertEquals(380, mRelayoutResult.mWidth);
- assertEquals(220, mRelayoutResult.mHeight);
+ assertEquals(300, mRelayoutResult.mWidth);
+ assertEquals(100, mRelayoutResult.mHeight);
}
@Test
@@ -309,14 +295,8 @@
.setVisible(true)
.build();
taskInfo.isFocused = true;
- // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is
- // 64px.
+ // Density is 2. Shadow radius is 10px. Caption height is 64px.
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- mRelayoutParams.setOutsets(
- R.dimen.test_window_decor_left_outset,
- R.dimen.test_window_decor_top_outset,
- R.dimen.test_window_decor_right_outset,
- R.dimen.test_window_decor_bottom_outset);
final SurfaceControl taskSurface = mock(SurfaceControl.class);
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
@@ -419,11 +399,6 @@
.build();
taskInfo.isFocused = true;
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- mRelayoutParams.setOutsets(
- R.dimen.test_window_decor_left_outset,
- R.dimen.test_window_decor_top_outset,
- R.dimen.test_window_decor_right_outset,
- R.dimen.test_window_decor_bottom_outset);
final SurfaceControl taskSurface = mock(SurfaceControl.class);
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
windowDecor.relayout(taskInfo);
@@ -438,7 +413,7 @@
verify(additionalWindowSurfaceBuilder).setContainerLayer();
verify(additionalWindowSurfaceBuilder).setParent(decorContainerSurface);
verify(additionalWindowSurfaceBuilder).build();
- verify(mMockSurfaceControlAddWindowT).setPosition(additionalWindowSurface, 20, 40);
+ verify(mMockSurfaceControlAddWindowT).setPosition(additionalWindowSurface, 0, 0);
final int width = WindowDecoration.loadDimensionPixelSize(
mContext.getResources(), mCaptionMenuWidthId);
final int height = WindowDecoration.loadDimensionPixelSize(
@@ -496,11 +471,6 @@
.build();
taskInfo.isFocused = true;
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- mRelayoutParams.setOutsets(
- R.dimen.test_window_decor_left_outset,
- R.dimen.test_window_decor_top_outset,
- R.dimen.test_window_decor_right_outset,
- R.dimen.test_window_decor_bottom_outset);
final SurfaceControl taskSurface = mock(SurfaceControl.class);
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
@@ -508,7 +478,6 @@
verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
verify(captionContainerSurfaceBuilder).setContainerLayer();
- verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, 20, 40);
// Width of the captionContainerSurface should match the width of TASK_BOUNDS
verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
verify(mMockSurfaceControlStartT).show(captionContainerSurface);
@@ -584,9 +553,7 @@
String name = "Test Window";
WindowDecoration.AdditionalWindow additionalWindow =
addWindow(R.layout.desktop_mode_window_decor_handle_menu_app_info_pill, name,
- mMockSurfaceControlAddWindowT,
- x - mRelayoutResult.mDecorContainerOffsetX,
- y - mRelayoutResult.mDecorContainerOffsetY,
+ mMockSurfaceControlAddWindowT, x, y,
width, height, shadowRadius, cornerRadius);
return additionalWindow;
}
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index cc987bc..c4d3f5c 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -53,6 +53,8 @@
}
MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() {
+ bool wasSurfaceless = mEglManager.isCurrent(EGL_NO_SURFACE);
+
// In case the surface was destroyed (e.g. a previous trimMemory call) we
// need to recreate it here.
if (mHardwareBuffer) {
@@ -65,6 +67,37 @@
if (!mEglManager.makeCurrent(mEglSurface, &error)) {
return MakeCurrentResult::AlreadyCurrent;
}
+
+ // Make sure read/draw buffer state of default framebuffer is GL_BACK. Vendor implementations
+ // disagree on the draw/read buffer state if the default framebuffer transitions from a surface
+ // to EGL_NO_SURFACE and vice-versa. There was a related discussion within Khronos on this topic.
+ // See https://cvs.khronos.org/bugzilla/show_bug.cgi?id=13534.
+ // The discussion was not resolved with a clear consensus
+ if (error == 0 && wasSurfaceless && mEglSurface != EGL_NO_SURFACE) {
+ GLint curReadFB = 0;
+ GLint curDrawFB = 0;
+ glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &curReadFB);
+ glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &curDrawFB);
+
+ GLint buffer = GL_NONE;
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ glGetIntegerv(GL_DRAW_BUFFER0, &buffer);
+ if (buffer == GL_NONE) {
+ const GLenum drawBuffer = GL_BACK;
+ glDrawBuffers(1, &drawBuffer);
+ }
+
+ glGetIntegerv(GL_READ_BUFFER, &buffer);
+ if (buffer == GL_NONE) {
+ glReadBuffer(GL_BACK);
+ }
+
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, curReadFB);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, curDrawFB);
+
+ GL_CHECKPOINT(LOW);
+ }
+
return error ? MakeCurrentResult::Failed : MakeCurrentResult::Succeeded;
}
@@ -104,7 +137,8 @@
GrBackendRenderTarget backendRT(frame.width(), frame.height(), 0, STENCIL_BUFFER_SIZE, fboInfo);
- SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
+ SkSurfaceProps props(mColorMode == ColorMode::Default ? 0 : SkSurfaceProps::kAlwaysDither_Flag,
+ kUnknown_SkPixelGeometry);
SkASSERT(mRenderThread.getGrContext() != nullptr);
sk_sp<SkSurface> surface;
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 1f92968..b020e96 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -28,7 +28,6 @@
#include <SkMultiPictureDocument.h>
#include <SkOverdrawCanvas.h>
#include <SkOverdrawColorFilter.h>
-#include <SkPaintFilterCanvas.h>
#include <SkPicture.h>
#include <SkPictureRecorder.h>
#include <SkRect.h>
@@ -450,23 +449,6 @@
}
}
-class ForceDitherCanvas : public SkPaintFilterCanvas {
-public:
- ForceDitherCanvas(SkCanvas* canvas) : SkPaintFilterCanvas(canvas) {}
-
-protected:
- bool onFilter(SkPaint& paint) const override {
- paint.setDither(true);
- return true;
- }
-
- void onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) override {
- // We unroll the drawable using "this" canvas, so that draw calls contained inside will
- // get dithering applied
- drawable->draw(this, matrix);
- }
-};
-
void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& clip,
const std::vector<sp<RenderNode>>& nodes, bool opaque,
const Rect& contentDrawBounds, sk_sp<SkSurface> surface,
@@ -521,12 +503,6 @@
canvas->clear(SK_ColorTRANSPARENT);
}
- std::optional<ForceDitherCanvas> forceDitherCanvas;
- if (shouldForceDither()) {
- forceDitherCanvas.emplace(canvas);
- canvas = &forceDitherCanvas.value();
- }
-
if (1 == nodes.size()) {
if (!nodes[0]->nothingToDraw()) {
RenderNodeDrawable root(nodes[0].get(), canvas);
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index 0763b06..befee89 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -98,8 +98,6 @@
bool isCapturingSkp() const { return mCaptureMode != CaptureMode::None; }
- virtual bool shouldForceDither() const { return mColorMode != ColorMode::Default; }
-
private:
void renderFrameImpl(const SkRect& clip,
const std::vector<sp<RenderNode>>& nodes, bool opaque,
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index f22652f..86096d5 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -203,11 +203,6 @@
return nullptr;
}
-bool SkiaVulkanPipeline::shouldForceDither() const {
- if (mVkSurface && mVkSurface->isBeyond8Bit()) return false;
- return SkiaPipeline::shouldForceDither();
-}
-
void SkiaVulkanPipeline::onContextDestroyed() {
if (mVkSurface) {
vulkanManager().destroySurface(mVkSurface);
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
index cce5468e..284cde5 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
@@ -63,8 +63,6 @@
protected:
void onContextDestroyed() override;
- bool shouldForceDither() const override;
-
private:
renderthread::VulkanManager& vulkanManager();
renderthread::VulkanSurface* mVkSurface = nullptr;
diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp
index 2b7fa04..10f4567 100644
--- a/libs/hwui/renderthread/VulkanSurface.cpp
+++ b/libs/hwui/renderthread/VulkanSurface.cpp
@@ -453,9 +453,15 @@
VulkanSurface::NativeBufferInfo* bufferInfo = &mNativeBuffers[idx];
if (bufferInfo->skSurface.get() == nullptr) {
+ SkSurfaceProps surfaceProps;
+ if (mWindowInfo.colorMode != ColorMode::Default) {
+ surfaceProps = SkSurfaceProps(SkSurfaceProps::kAlwaysDither_Flag | surfaceProps.flags(),
+ surfaceProps.pixelGeometry());
+ }
bufferInfo->skSurface = SkSurface::MakeFromAHardwareBuffer(
mGrContext, ANativeWindowBuffer_getHardwareBuffer(bufferInfo->buffer.get()),
- kTopLeft_GrSurfaceOrigin, mWindowInfo.colorspace, nullptr, /*from_window=*/true);
+ kTopLeft_GrSurfaceOrigin, mWindowInfo.colorspace, &surfaceProps,
+ /*from_window=*/true);
if (bufferInfo->skSurface.get() == nullptr) {
ALOGE("SkSurface::MakeFromAHardwareBuffer failed");
mNativeWindow->cancelBuffer(mNativeWindow.get(), buffer,
@@ -545,16 +551,6 @@
}
}
-bool VulkanSurface::isBeyond8Bit() const {
- switch (mWindowInfo.bufferFormat) {
- case AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM:
- case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT:
- return true;
- default:
- return false;
- }
-}
-
} /* namespace renderthread */
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/renderthread/VulkanSurface.h b/libs/hwui/renderthread/VulkanSurface.h
index d3266af8..6f528010 100644
--- a/libs/hwui/renderthread/VulkanSurface.h
+++ b/libs/hwui/renderthread/VulkanSurface.h
@@ -49,8 +49,6 @@
void setColorSpace(sk_sp<SkColorSpace> colorSpace);
const SkM44& getPixelSnapMatrix() const { return mWindowInfo.pixelSnapMatrix; }
- bool isBeyond8Bit() const;
-
private:
/*
* All structs/methods in this private section are specifically for use by the VulkanManager
diff --git a/media/java/android/media/soundtrigger/SoundTriggerInstrumentation.java b/media/java/android/media/soundtrigger/SoundTriggerInstrumentation.java
new file mode 100644
index 0000000..80bc5c0
--- /dev/null
+++ b/media/java/android/media/soundtrigger/SoundTriggerInstrumentation.java
@@ -0,0 +1,630 @@
+/**
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.soundtrigger;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.TestApi;
+import android.hardware.soundtrigger.ConversionUtil;
+import android.hardware.soundtrigger.SoundTrigger;
+import android.media.soundtrigger_middleware.IAcknowledgeEvent;
+import android.media.soundtrigger_middleware.IInjectGlobalEvent;
+import android.media.soundtrigger_middleware.IInjectModelEvent;
+import android.media.soundtrigger_middleware.IInjectRecognitionEvent;
+import android.media.soundtrigger_middleware.ISoundTriggerInjection;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.ISoundTriggerService;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Used to inject/observe events when using a fake SoundTrigger HAL for test purposes.
+ * Created by {@link SoundTriggerManager#getInjection(Executor, GlobalCallback)}.
+ * Only one instance of this class is valid at any given time, old instances will be delivered
+ * {@link GlobalCallback#onPreempted()}.
+ * @hide
+ */
+@TestApi
+public final class SoundTriggerInstrumentation {
+
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private IInjectGlobalEvent mInjectGlobalEvent = null;
+
+ @GuardedBy("mLock")
+ private Map<IBinder, ModelSession> mModelSessionMap = new HashMap<>();
+ @GuardedBy("mLock")
+ private Map<IBinder, RecognitionSession> mRecognitionSessionMap = new HashMap<>();
+ @GuardedBy("mLock")
+ private IBinder mClientToken = null;
+
+ private final GlobalCallback mClientCallback;
+ private final Executor mGlobalCallbackExecutor;
+
+ /**
+ * Callback interface for un-sessioned events observed from the fake STHAL.
+ * Registered upon construction of {@link SoundTriggerInstrumentation}
+ * @hide
+ */
+ @TestApi
+ public interface GlobalCallback {
+ /**
+ * Called when the created {@link SoundTriggerInstrumentation} object is invalidated
+ * by another client creating an {@link SoundTriggerInstrumentation} to instrument the
+ * fake STHAL. Only one client may inject at a time.
+ * All sessions are invalidated, no further events will be received, and no
+ * injected events will be delivered.
+ */
+ default void onPreempted() {}
+ /**
+ * Called when the STHAL has been restarted by the framework, due to unexpected
+ * error conditions.
+ * Not called when {@link SoundTriggerInstrumentation#triggerRestart()} is injected.
+ */
+ default void onRestarted() {}
+ /**
+ * Called when the framework detaches from the fake HAL.
+ * This is not transmitted to real HALs, but it indicates that the
+ * framework has flushed its global state.
+ */
+ default void onFrameworkDetached() {}
+ /**
+ * Called when a client application attaches to the framework.
+ * This is not transmitted to real HALs, but it represents the state of
+ * the framework.
+ */
+ default void onClientAttached() {}
+ /**
+ * Called when a client application detaches from the framework.
+ * This is not transmitted to real HALs, but it represents the state of
+ * the framework.
+ */
+ default void onClientDetached() {}
+ /**
+ * Called when the fake HAL receives a model load from the framework.
+ * @param modelSession - A session which exposes additional injection
+ * functionality associated with the newly loaded
+ * model. See {@link ModelSession}.
+ */
+ void onModelLoaded(@NonNull ModelSession modelSession);
+ }
+
+ /**
+ * Callback for HAL events related to a loaded model. Register with
+ * {@link ModelSession#setModelCallback(Executor, ModelCallback)}
+ * Note, callbacks will not be delivered for events triggered by the injection.
+ * @hide
+ */
+ @TestApi
+ public interface ModelCallback {
+ /**
+ * Called when the model associated with the {@link ModelSession} this callback
+ * was registered for was unloaded by the framework.
+ */
+ default void onModelUnloaded() {}
+ /**
+ * Called when the model associated with the {@link ModelSession} this callback
+ * was registered for receives a set parameter call from the framework.
+ * @param param - Parameter being set.
+ * See {@link SoundTrigger.ModelParamTypes}
+ * @param value - Value the model parameter was set to.
+ */
+ default void onParamSet(@SoundTrigger.ModelParamTypes int param, int value) {}
+ /**
+ * Called when the model associated with the {@link ModelSession} this callback
+ * was registered for receives a recognition start request.
+ * @param recognitionSession - A session which exposes additional injection
+ * functionality associated with the newly started
+ * recognition. See {@link RecognitionSession}
+ */
+ void onRecognitionStarted(@NonNull RecognitionSession recognitionSession);
+ }
+
+ /**
+ * Callback for HAL events related to a started recognition. Register with
+ * {@link RecognitionSession#setRecognitionCallback(Executor, RecognitionCallback)}
+ * Note, callbacks will not be delivered for events triggered by the injection.
+ * @hide
+ */
+ @TestApi
+ public interface RecognitionCallback {
+ /**
+ * Called when the recognition associated with the {@link RecognitionSession} this
+ * callback was registered for was stopped by the framework.
+ */
+ void onRecognitionStopped();
+ }
+
+ /**
+ * Session associated with a loaded model in the fake STHAL.
+ * Can be used to query details about the loaded model, register a callback for future
+ * model events, or trigger HAL events associated with a loaded model.
+ * This session is invalid once the model is unloaded, caused by a
+ * {@link ModelSession#triggerUnloadModel()},
+ * the client unloading recognition, or if a {@link GlobalCallback#onRestarted()} is
+ * received.
+ * Further injections on an invalidated session will not be respected, and no future
+ * callbacks will be delivered.
+ * @hide
+ */
+ @TestApi
+ public class ModelSession {
+
+ /**
+ * Trigger the HAL to preemptively unload the model associated with this session.
+ * Typically occurs when a higher priority model is loaded which utilizes the same
+ * resources.
+ */
+ public void triggerUnloadModel() {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ try {
+ mInjectModelEvent.triggerUnloadModel();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ mModelSessionMap.remove(mInjectModelEvent.asBinder());
+ }
+ }
+
+ /**
+ * Get the {@link SoundTriggerManager.Model} associated with this session.
+ * @return - The model associated with this session.
+ */
+ public @NonNull SoundTriggerManager.Model getSoundModel() {
+ return mModel;
+ }
+
+ /**
+ * Get the list of {@link SoundTrigger.Keyphrase} associated with this session.
+ * @return - The keyphrases associated with this session.
+ */
+ public @NonNull List<SoundTrigger.Keyphrase> getPhrases() {
+ if (mPhrases == null) {
+ return new ArrayList<>();
+ } else {
+ return new ArrayList<>(Arrays.asList(mPhrases));
+ }
+ }
+
+ /**
+ * Get whether this model is of keyphrase type.
+ * @return - true if the model is a keyphrase model, false otherwise
+ */
+ public boolean isKeyphrase() {
+ return (mPhrases != null);
+ }
+
+ /**
+ * Registers the model callback associated with this session. Events associated
+ * with this model session will be reported via this callback.
+ * See {@link ModelCallback}
+ * @param executor - Executor which the callback is dispatched on
+ * @param callback - Model callback for reporting model session events.
+ */
+ public void setModelCallback(@NonNull @CallbackExecutor Executor executor, @NonNull
+ ModelCallback callback) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ mModelCallback = Objects.requireNonNull(callback);
+ mModelExecutor = Objects.requireNonNull(executor);
+ }
+ }
+
+ /**
+ * Clear the model callback associated with this session, if any has been
+ * set by {@link #setModelCallback(Executor, ModelCallback)}.
+ */
+ public void clearModelCallback() {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ mModelCallback = null;
+ mModelExecutor = null;
+ }
+ }
+
+ private ModelSession(SoundModel model, Phrase[] phrases,
+ IInjectModelEvent injection) {
+ mModel = SoundTriggerManager.Model.create(UUID.fromString(model.uuid),
+ UUID.fromString(model.vendorUuid),
+ ConversionUtil.sharedMemoryToByteArray(model.data, model.dataSize));
+ if (phrases != null) {
+ mPhrases = new SoundTrigger.Keyphrase[phrases.length];
+ int i = 0;
+ for (var phrase : phrases) {
+ mPhrases[i++] = ConversionUtil.aidl2apiPhrase(phrase);
+ }
+ } else {
+ mPhrases = null;
+ }
+ mInjectModelEvent = injection;
+ }
+
+ private void wrap(Consumer<ModelCallback> consumer) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ if (mModelCallback != null && mModelExecutor != null) {
+ final ModelCallback callback = mModelCallback;
+ mModelExecutor.execute(() -> consumer.accept(callback));
+ }
+ }
+ }
+
+ private final SoundTriggerManager.Model mModel;
+ private final SoundTrigger.Keyphrase[] mPhrases;
+ private final IInjectModelEvent mInjectModelEvent;
+
+ @GuardedBy("SoundTriggerInstrumentation.this.mLock")
+ private ModelCallback mModelCallback = null;
+ @GuardedBy("SoundTriggerInstrumentation.this.mLock")
+ private Executor mModelExecutor = null;
+ }
+
+ /**
+ * Session associated with a recognition start in the fake STHAL.
+ * Can be used to get information about the started recognition, register a callback
+ * for future events associated with this recognition, and triggering
+ * recognition events or aborts.
+ * This session is invalid once the recognition is stopped, caused by a
+ * {@link RecognitionSession#triggerAbortRecognition()},
+ * {@link RecognitionSession#triggerRecognitionEvent(byte[], List)},
+ * the client stopping recognition, or any operation which invalidates the
+ * {@link ModelSession} which the session was created from.
+ * Further injections on an invalidated session will not be respected, and no future
+ * callbacks will be delivered.
+ * @hide
+ */
+ @TestApi
+ public class RecognitionSession {
+
+ /**
+ * Get an integer token representing the audio session associated with this
+ * recognition in the STHAL.
+ * @return - The session token.
+ */
+ public int getAudioSession() {
+ return mAudioSession;
+ }
+
+ /**
+ * Get the recognition config used to start this recognition.
+ * @return - The config passed to the HAL for startRecognition.
+ */
+ public @NonNull SoundTrigger.RecognitionConfig getRecognitionConfig() {
+ return mRecognitionConfig;
+ }
+
+ /**
+ * Trigger a recognition in the fake STHAL.
+ * @param data - The opaque data buffer included in the recognition event.
+ * @param phraseExtras - Keyphrase metadata included in the event. The
+ * event must include metadata for the keyphrase id
+ * associated with this model to be received by the
+ * client application.
+ */
+ public void triggerRecognitionEvent(@NonNull byte[] data, @Nullable
+ List<SoundTrigger.KeyphraseRecognitionExtra> phraseExtras) {
+ PhraseRecognitionExtra[] converted = null;
+ if (phraseExtras != null) {
+ converted = new PhraseRecognitionExtra[phraseExtras.size()];
+ int i = 0;
+ for (var phraseExtra : phraseExtras) {
+ converted[i++] = ConversionUtil.api2aidlPhraseRecognitionExtra(phraseExtra);
+ }
+ }
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ mRecognitionSessionMap.remove(mInjectRecognitionEvent.asBinder());
+ try {
+ mInjectRecognitionEvent.triggerRecognitionEvent(data, converted);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Trigger an abort recognition event in the fake HAL. This represents a
+ * preemptive ending of the recognition session by the HAL, despite no
+ * recognition detection. Typically occurs during contention for microphone
+ * usage, or if model limits are hit.
+ * See {@link SoundTriggerInstrumentation#setResourceContention(boolean)} to block
+ * subsequent downward calls for contention reasons.
+ */
+ public void triggerAbortRecognition() {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ mRecognitionSessionMap.remove(mInjectRecognitionEvent.asBinder());
+ try {
+ mInjectRecognitionEvent.triggerAbortRecognition();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Registers the recognition callback associated with this session. Events associated
+ * with this recognition session will be reported via this callback.
+ * See {@link RecognitionCallback}
+ * @param executor - Executor which the callback is dispatched on
+ * @param callback - Recognition callback for reporting recognition session events.
+ */
+ public void setRecognitionCallback(@NonNull @CallbackExecutor Executor executor,
+ @NonNull RecognitionCallback callback) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ mRecognitionCallback = callback;
+ mRecognitionExecutor = executor;
+ }
+ }
+
+ /**
+ * Clear the recognition callback associated with this session, if any has been
+ * set by {@link #setRecognitionCallback(Executor, RecognitionCallback)}.
+ */
+ public void clearRecognitionCallback() {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ mRecognitionCallback = null;
+ mRecognitionExecutor = null;
+ }
+ }
+
+ private RecognitionSession(int audioSession,
+ RecognitionConfig recognitionConfig,
+ IInjectRecognitionEvent injectRecognitionEvent) {
+ mAudioSession = audioSession;
+ mRecognitionConfig = ConversionUtil.aidl2apiRecognitionConfig(recognitionConfig);
+ mInjectRecognitionEvent = injectRecognitionEvent;
+ }
+
+ private void wrap(Consumer<RecognitionCallback> consumer) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ if (mRecognitionCallback != null && mRecognitionExecutor != null) {
+ final RecognitionCallback callback = mRecognitionCallback;
+ mRecognitionExecutor.execute(() -> consumer.accept(callback));
+ }
+ }
+ }
+
+ private final int mAudioSession;
+ private final SoundTrigger.RecognitionConfig mRecognitionConfig;
+ private final IInjectRecognitionEvent mInjectRecognitionEvent;
+
+ @GuardedBy("SoundTriggerInstrumentation.this.mLock")
+ private Executor mRecognitionExecutor = null;
+ @GuardedBy("SoundTriggerInstrumentation.this.mLock")
+ private RecognitionCallback mRecognitionCallback = null;
+ }
+
+ // Implementation of injection interface passed to the HAL.
+ // This class will re-associate events received on this callback interface
+ // with sessions, to avoid staleness issues.
+ private class Injection extends ISoundTriggerInjection.Stub {
+ @Override
+ public void registerGlobalEventInjection(IInjectGlobalEvent globalInjection) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ mInjectGlobalEvent = globalInjection;
+ }
+ }
+
+ @Override
+ public void onSoundModelLoaded(SoundModel model, @Nullable Phrase[] phrases,
+ IInjectModelEvent modelInjection, IInjectGlobalEvent globalSession) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ if (globalSession.asBinder() != mInjectGlobalEvent.asBinder()) return;
+ ModelSession modelSession = new ModelSession(model, phrases, modelInjection);
+ mModelSessionMap.put(modelInjection.asBinder(), modelSession);
+ mGlobalCallbackExecutor.execute(() -> mClientCallback.onModelLoaded(modelSession));
+ }
+ }
+
+ @Override
+ public void onSoundModelUnloaded(IInjectModelEvent modelSession) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ ModelSession clientModelSession = mModelSessionMap.remove(modelSession.asBinder());
+ if (clientModelSession == null) return;
+ clientModelSession.wrap((ModelCallback cb) -> cb.onModelUnloaded());
+ }
+ }
+
+ @Override
+ public void onRecognitionStarted(int audioSessionHandle, RecognitionConfig config,
+ IInjectRecognitionEvent recognitionInjection, IInjectModelEvent modelSession) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ ModelSession clientModelSession = mModelSessionMap.get(modelSession.asBinder());
+ if (clientModelSession == null) return;
+ RecognitionSession recogSession = new RecognitionSession(
+ audioSessionHandle, config, recognitionInjection);
+ mRecognitionSessionMap.put(recognitionInjection.asBinder(), recogSession);
+ clientModelSession.wrap((ModelCallback cb) ->
+ cb.onRecognitionStarted(recogSession));
+ }
+ }
+
+ @Override
+ public void onRecognitionStopped(IInjectRecognitionEvent recognitionSession) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ RecognitionSession clientRecognitionSession =
+ mRecognitionSessionMap.remove(recognitionSession.asBinder());
+ if (clientRecognitionSession == null) return;
+ clientRecognitionSession.wrap((RecognitionCallback cb)
+ -> cb.onRecognitionStopped());
+ }
+ }
+
+ @Override
+ public void onParamSet(int modelParam, int value, IInjectModelEvent modelSession) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ ModelSession clientModelSession = mModelSessionMap.get(modelSession.asBinder());
+ if (clientModelSession == null) return;
+ clientModelSession.wrap((ModelCallback cb) -> cb.onParamSet(modelParam, value));
+ }
+ }
+
+
+ @Override
+ public void onRestarted(IInjectGlobalEvent globalSession) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ if (globalSession.asBinder() != mInjectGlobalEvent.asBinder()) return;
+ mRecognitionSessionMap.clear();
+ mModelSessionMap.clear();
+ mGlobalCallbackExecutor.execute(() -> mClientCallback.onRestarted());
+ }
+ }
+
+ @Override
+ public void onFrameworkDetached(IInjectGlobalEvent globalSession) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ if (globalSession.asBinder() != mInjectGlobalEvent.asBinder()) return;
+ mGlobalCallbackExecutor.execute(() -> mClientCallback.onFrameworkDetached());
+ }
+ }
+
+ @Override
+ public void onClientAttached(IBinder token, IInjectGlobalEvent globalSession) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ if (globalSession.asBinder() != mInjectGlobalEvent.asBinder()) return;
+ mClientToken = token;
+ mGlobalCallbackExecutor.execute(() -> mClientCallback.onClientAttached());
+ }
+ }
+
+ @Override
+ public void onClientDetached(IBinder token) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ if (token != mClientToken) return;
+ mClientToken = null;
+ mGlobalCallbackExecutor.execute(() -> mClientCallback.onClientDetached());
+ }
+ }
+
+ @Override
+ public void onPreempted() {
+ // This is always valid, independent of session
+ mGlobalCallbackExecutor.execute(() -> mClientCallback.onPreempted());
+ // Callbacks will no longer be delivered, and injection will be silently dropped.
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
+ public SoundTriggerInstrumentation(ISoundTriggerService service,
+ @CallbackExecutor @NonNull Executor executor,
+ @NonNull GlobalCallback callback) {
+ mClientCallback = Objects.requireNonNull(callback);
+ mGlobalCallbackExecutor = Objects.requireNonNull(executor);
+ try {
+ service.attachInjection(new Injection());
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Simulate a HAL restart, typically caused by the framework on an unexpected error,
+ * or a restart of the core audio HAL.
+ * Application sessions will be detached, and all state will be cleared. The framework
+ * will re-attach to the HAL following restart.
+ * @hide
+ */
+ @TestApi
+ public void triggerRestart() {
+ synchronized (mLock) {
+ if (mInjectGlobalEvent == null) {
+ throw new IllegalStateException(
+ "Attempted to trigger HAL restart before registration");
+ }
+ try {
+ mInjectGlobalEvent.triggerRestart();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Trigger a resource available callback from the fake SoundTrigger HAL to the framework.
+ * This callback notifies the framework that methods which previously failed due to
+ * resource contention may now succeed.
+ * @hide
+ */
+ @TestApi
+ public void triggerOnResourcesAvailable() {
+ synchronized (mLock) {
+ if (mInjectGlobalEvent == null) {
+ throw new IllegalStateException(
+ "Attempted to trigger HAL resources available before registration");
+ }
+ try {
+ mInjectGlobalEvent.triggerOnResourcesAvailable();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Simulate resource contention, similar to when HAL which does not
+ * support concurrent capture opens a capture stream, or when a HAL
+ * has reached its maximum number of models.
+ * Subsequent model loads and recognition starts will gracefully error.
+ * Since this call does not trigger a callback through the framework, the
+ * call will block until the fake HAL has acknowledged the state change.
+ * @param isResourceContended - true to enable contention, false to return
+ * to normal functioning.
+ * @hide
+ */
+ @TestApi
+ public void setResourceContention(boolean isResourceContended) {
+ synchronized (mLock) {
+ if (mInjectGlobalEvent == null) {
+ throw new IllegalStateException("Injection interface not set up");
+ }
+ IInjectGlobalEvent current = mInjectGlobalEvent;
+ final CountDownLatch signal = new CountDownLatch(1);
+ try {
+ current.setResourceContention(isResourceContended, new IAcknowledgeEvent.Stub() {
+ @Override
+ public void eventReceived() {
+ signal.countDown();
+ }
+ });
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ // Block until we get a callback from the service that our request was serviced.
+ try {
+ // Rely on test timeout if we don't get a response.
+ signal.await();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+}
+
diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java
index ae8121a..c41bd1b 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerManager.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java
@@ -18,11 +18,13 @@
import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR;
+import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.app.ActivityThread;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
@@ -45,6 +47,7 @@
import android.os.IBinder;
import android.os.ParcelUuid;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.provider.Settings;
import android.util.Slog;
@@ -53,9 +56,9 @@
import com.android.internal.util.Preconditions;
import java.util.HashMap;
-import java.util.List;
import java.util.Objects;
import java.util.UUID;
+import java.util.concurrent.Executor;
/**
* This class provides management of non-voice (general sound trigger) based sound recognition
@@ -609,4 +612,24 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Create a {@link SoundTriggerInstrumentation} for test purposes, which instruments a fake
+ * STHAL. Clients must attach to the appropriate underlying ST module.
+ * @param executor - Executor to dispatch global callbacks on
+ * @param callback - Callback for unsessioned events received by the fake STHAL
+ * @return - A {@link SoundTriggerInstrumentation} for observation/injection.
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
+ @NonNull
+ public static SoundTriggerInstrumentation attachInstrumentation(
+ @CallbackExecutor @NonNull Executor executor,
+ @NonNull SoundTriggerInstrumentation.GlobalCallback callback) {
+ ISoundTriggerService service = ISoundTriggerService.Stub.asInterface(
+ ServiceManager.getService(Context.SOUND_TRIGGER_SERVICE));
+ return new SoundTriggerInstrumentation(service, executor, callback);
+ }
+
}
diff --git a/media/java/android/media/voice/KeyphraseModelManager.java b/media/java/android/media/voice/KeyphraseModelManager.java
index 8ec8967..5a690a5 100644
--- a/media/java/android/media/voice/KeyphraseModelManager.java
+++ b/media/java/android/media/voice/KeyphraseModelManager.java
@@ -21,7 +21,9 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.hardware.soundtrigger.SoundTrigger;
+import android.os.Binder;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.util.Slog;
@@ -154,4 +156,23 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Override the persistent enrolled model database with an in-memory
+ * fake for testing purposes.
+ *
+ * @param enabled - {@code true} if the model enrollment database should be overridden with an
+ * in-memory fake. {@code false} if the real, persistent model enrollment database should be
+ * used.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES)
+ @TestApi
+ public void setModelDatabaseForTestEnabled(boolean enabled) {
+ try {
+ mVoiceInteractionManagerService.setModelDatabaseForTestEnabled(enabled, new Binder());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index e9b2e10..3e65251 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -117,6 +117,8 @@
<string name="get_dialog_sign_in_type_username_separator" translatable="false">" • "</string>
<!-- This text is followed by a list of one or more options. [CHAR LIMIT=80] -->
<string name="get_dialog_title_sign_in_options">Sign-in options</string>
+ <!-- Button label for viewing the full information about an account. [CHAR LIMIT=80] -->
+ <string name="button_label_view_more">View more</string>
<!-- Column heading for displaying sign-ins for a specific username. [CHAR LIMIT=80] -->
<string name="get_dialog_heading_for_username">For <xliff:g id="username" example="becket@gmail.com">%1$s</xliff:g></string>
<!-- Column heading for displaying locked (that is, the user needs to first authenticate via pin, fingerprint, faceId, etc.) sign-ins. [CHAR LIMIT=80] -->
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index ca89129..64addf2 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -252,13 +252,6 @@
))
}
is PublicKeyCredentialEntry -> {
- val passkeyUsername = credentialEntry.username.toString()
- val passkeyDisplayName = credentialEntry.displayName?.toString() ?: ""
- val (username, displayName) = userAndDisplayNameForPasskey(
- passkeyUsername = passkeyUsername,
- passkeyDisplayName = passkeyDisplayName,
- )
-
result.add(CredentialEntryInfo(
providerId = providerId,
providerDisplayName = providerLabel,
@@ -268,8 +261,8 @@
fillInIntent = it.frameworkExtrasIntent,
credentialType = CredentialType.PASSKEY,
credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(),
- userName = username,
- displayName = displayName,
+ userName = credentialEntry.username.toString(),
+ displayName = credentialEntry.displayName?.toString(),
icon = credentialEntry.icon.loadDrawable(context),
shouldTintIcon = credentialEntry.isDefaultIcon,
lastUsedTimeMillis = credentialEntry.lastUsedTime,
@@ -707,7 +700,7 @@
* 2) username on top if display-name is not available.
* 3) don't show username on second line if username == display-name
*/
-private fun userAndDisplayNameForPasskey(
+fun userAndDisplayNameForPasskey(
passkeyUsername: String,
passkeyDisplayName: String,
): Pair<String, String> {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
index 0623ff6..2dba2ab 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
@@ -51,6 +51,7 @@
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
@@ -79,6 +80,7 @@
/** If true, draws a trailing lock icon. */
isLockedAuthEntry: Boolean = false,
enforceOneLine: Boolean = false,
+ onTextLayout: (TextLayoutResult) -> Unit = {},
) {
val iconPadding = Modifier.wrapContentSize().padding(
// Horizontal padding should be 16dp, but the suggestion chip itself
@@ -103,7 +105,11 @@
) {
// Apply weight so that the trailing icon can always show.
Column(modifier = Modifier.wrapContentHeight().fillMaxWidth().weight(1f)) {
- SmallTitleText(text = entryHeadlineText, enforceOneLine = enforceOneLine)
+ SmallTitleText(
+ text = entryHeadlineText,
+ enforceOneLine = enforceOneLine,
+ onTextLayout = onTextLayout,
+ )
if (passwordValue != null) {
Row(
modifier = Modifier.fillMaxWidth().padding(top = 4.dp),
@@ -141,10 +147,18 @@
)
}
} else if (entrySecondLineText != null) {
- BodySmallText(text = entrySecondLineText, enforceOneLine = enforceOneLine)
+ BodySmallText(
+ text = entrySecondLineText,
+ enforceOneLine = enforceOneLine,
+ onTextLayout = onTextLayout,
+ )
}
if (entryThirdLineText != null) {
- BodySmallText(text = entryThirdLineText, enforceOneLine = enforceOneLine)
+ BodySmallText(
+ text = entryThirdLineText,
+ enforceOneLine = enforceOneLine,
+ onTextLayout = onTextLayout,
+ )
}
}
if (isLockedAuthEntry) {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt
index 61c03b4..6b46636 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt
@@ -22,6 +22,7 @@
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
@@ -59,14 +60,20 @@
* Body-small typography; on-surface-variant color.
*/
@Composable
-fun BodySmallText(text: String, modifier: Modifier = Modifier, enforceOneLine: Boolean = false) {
+fun BodySmallText(
+ text: String,
+ modifier: Modifier = Modifier,
+ enforceOneLine: Boolean = false,
+ onTextLayout: (TextLayoutResult) -> Unit = {},
+) {
Text(
modifier = modifier.wrapContentSize(),
text = text,
color = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
style = MaterialTheme.typography.bodySmall,
overflow = TextOverflow.Ellipsis,
- maxLines = if (enforceOneLine) 1 else Int.MAX_VALUE
+ maxLines = if (enforceOneLine) 1 else Int.MAX_VALUE,
+ onTextLayout = onTextLayout,
)
}
@@ -87,14 +94,20 @@
* Title-small typography; on-surface color.
*/
@Composable
-fun SmallTitleText(text: String, modifier: Modifier = Modifier, enforceOneLine: Boolean = false) {
+fun SmallTitleText(
+ text: String,
+ modifier: Modifier = Modifier,
+ enforceOneLine: Boolean = false,
+ onTextLayout: (TextLayoutResult) -> Unit = {},
+) {
Text(
modifier = modifier.wrapContentSize(),
text = text,
color = LocalAndroidColorScheme.current.colorOnSurface,
style = MaterialTheme.typography.titleSmall,
overflow = TextOverflow.Ellipsis,
- maxLines = if (enforceOneLine) 1 else Int.MAX_VALUE
+ maxLines = if (enforceOneLine) 1 else Int.MAX_VALUE,
+ onTextLayout = onTextLayout,
)
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 6d7ecd7..b8b7ac3 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -34,11 +34,15 @@
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.unit.dp
import androidx.core.graphics.drawable.toBitmap
import com.android.credentialmanager.CredentialSelectorViewModel
@@ -62,6 +66,7 @@
import com.android.credentialmanager.common.ui.LargeLabelTextOnSurfaceVariant
import com.android.credentialmanager.common.ui.Snackbar
import com.android.credentialmanager.logging.GetCredentialEvent
+import com.android.credentialmanager.userAndDisplayNameForPasskey
import com.android.internal.logging.UiEventLogger.UiEventEnum
@Composable
@@ -158,6 +163,7 @@
onMoreOptionSelected: () -> Unit,
onLog: @Composable (UiEventEnum) -> Unit,
) {
+ val showMoreForTruncatedEntry = remember { mutableStateOf(false) }
val sortedUserNameToCredentialEntryList =
providerDisplayInfo.sortedUserNameToCredentialEntryList
val authenticationEntryList = providerDisplayInfo.authenticationEntryList
@@ -209,6 +215,8 @@
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
val usernameForCredentialSize = sortedUserNameToCredentialEntryList.size
val authenticationEntrySize = authenticationEntryList.size
+ // If true, render a view more button for the single truncated entry on the
+ // front page.
// Show max 4 entries in this primary page
if (usernameForCredentialSize + authenticationEntrySize <= 4) {
sortedUserNameToCredentialEntryList.forEach {
@@ -216,6 +224,9 @@
credentialEntryInfo = it.sortedCredentialEntryList.first(),
onEntrySelected = onEntrySelected,
enforceOneLine = true,
+ onTextLayout = {
+ showMoreForTruncatedEntry.value = it.hasVisualOverflow
+ }
)
}
authenticationEntryList.forEach {
@@ -269,6 +280,13 @@
onMoreOptionSelected
)
}
+ } else if (showMoreForTruncatedEntry.value) {
+ {
+ ActionButton(
+ stringResource(R.string.button_label_view_more),
+ onMoreOptionSelected
+ )
+ }
} else null,
rightButton = if (activeEntry != null) { // Only one sign-in options exist
{
@@ -438,7 +456,12 @@
credentialEntryInfo: CredentialEntryInfo,
onEntrySelected: (BaseEntry) -> Unit,
enforceOneLine: Boolean = false,
+ onTextLayout: (TextLayoutResult) -> Unit = {},
) {
+ val (username, displayName) = if (credentialEntryInfo.credentialType == CredentialType.PASSKEY)
+ userAndDisplayNameForPasskey(
+ credentialEntryInfo.userName, credentialEntryInfo.displayName ?: "")
+ else Pair(credentialEntryInfo.userName, credentialEntryInfo.displayName)
Entry(
onClick = { onEntrySelected(credentialEntryInfo) },
iconImageBitmap = credentialEntryInfo.icon?.toBitmap()?.asImageBitmap(),
@@ -447,13 +470,13 @@
iconPainter =
if (credentialEntryInfo.icon == null) painterResource(R.drawable.ic_other_sign_in_24)
else null,
- entryHeadlineText = credentialEntryInfo.userName,
+ entryHeadlineText = username,
entrySecondLineText = if (
credentialEntryInfo.credentialType == CredentialType.PASSWORD) {
"••••••••••••"
} else {
val itemsToDisplay = listOf(
- credentialEntryInfo.displayName,
+ displayName,
credentialEntryInfo.credentialTypeDisplayName,
credentialEntryInfo.providerDisplayName
).filterNot(TextUtils::isEmpty)
@@ -463,6 +486,7 @@
)
},
enforceOneLine = enforceOneLine,
+ onTextLayout = onTextLayout,
)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
index 47ac2df..e07a629 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
@@ -69,6 +69,7 @@
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.unit.dp
@@ -460,6 +461,9 @@
ProvideTextStyle(value = titleTextStyle) {
CompositionLocalProvider(
LocalContentColor provides titleContentColor,
+ // Disable the title font scaling by only passing the density but not the
+ // font scale.
+ LocalDensity provides Density(density = LocalDensity.current.density),
content = title
)
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
index a3db6d7..e28ada4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
@@ -120,9 +120,9 @@
* @return true if the request succeeded.
*/
public static synchronized boolean setPowerSaveMode(Context context,
- boolean enable, boolean needFirstTimeWarning) {
+ boolean enable, boolean needFirstTimeWarning, @SaverManualEnabledReason int reason) {
if (DEBUG) {
- Log.d(TAG, "Battery saver turning " + (enable ? "ON" : "OFF"));
+ Log.d(TAG, "Battery saver turning " + (enable ? "ON" : "OFF") + ", reason: " + reason);
}
final ContentResolver cr = context.getContentResolver();
@@ -152,6 +152,7 @@
sendSystemUiBroadcast(context, ACTION_SHOW_AUTO_SAVER_SUGGESTION,
confirmationExtras);
}
+ recordBatterySaverEnabledReason(context, reason);
}
return true;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java
index ad022a6..cb386fb 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java
@@ -16,6 +16,7 @@
package com.android.settingslib.fuelgauge;
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_UNKNOWN;
import static com.android.settingslib.fuelgauge.BatterySaverUtils.KEY_NO_SCHEDULE;
import static com.android.settingslib.fuelgauge.BatterySaverUtils.KEY_PERCENTAGE;
@@ -72,7 +73,8 @@
Secure.putString(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, "null");
Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
- assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true)).isFalse();
+ assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true,
+ SAVER_ENABLED_UNKNOWN)).isFalse();
verify(mMockContext, times(1)).sendBroadcast(any(Intent.class));
verify(mMockPowerManager, times(0)).setPowerSaveModeEnabled(anyBoolean());
@@ -92,7 +94,8 @@
Secure.putInt(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, 1);
Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
- assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true)).isTrue();
+ assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true,
+ SAVER_ENABLED_UNKNOWN)).isTrue();
verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(true));
@@ -111,7 +114,8 @@
Secure.putInt(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, 1);
Secure.putInt(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, 1);
- assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true)).isTrue();
+ assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true,
+ SAVER_ENABLED_UNKNOWN)).isTrue();
verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(true));
@@ -129,7 +133,8 @@
Secure.putString(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, "null");
Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
- assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, false)).isTrue();
+ assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, false,
+ SAVER_ENABLED_UNKNOWN)).isTrue();
verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(true));
@@ -147,7 +152,8 @@
Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
// When disabling, needFirstTimeWarning doesn't matter.
- assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, false)).isTrue();
+ assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, false,
+ SAVER_ENABLED_UNKNOWN)).isTrue();
verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(false));
@@ -166,7 +172,8 @@
Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
// When disabling, needFirstTimeWarning doesn't matter.
- assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, true)).isTrue();
+ assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, true,
+ SAVER_ENABLED_UNKNOWN)).isTrue();
verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(false));
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 36a0b5d..dabb578 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -347,15 +347,6 @@
<uses-permission android:name="android.permission.MONITOR_KEYBOARD_BACKLIGHT" />
- <!-- Intent Chooser -->
- <permission
- android:name="android.permission.ADD_CHOOSER_PINS"
- android:protectionLevel="signature" />
- <uses-permission android:name="android.permission.ADD_CHOOSER_PINS" />
- <permission
- android:name="android.permission.RECEIVE_CHOOSER_PIN_MIGRATION"
- android:protectionLevel="signature" />
-
<protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" />
<protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" />
<protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" />
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt
index 52dfc55..f71c137 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt
@@ -4,7 +4,7 @@
import androidx.annotation.VisibleForTesting
class WeatherData
-private constructor(
+constructor(
val description: String,
val state: WeatherStateIcon,
val useCelsius: Boolean,
diff --git a/packages/SystemUI/res-keyguard/drawable/fp_to_locked.xml b/packages/SystemUI/res-keyguard/drawable/fp_to_locked.xml
new file mode 100644
index 0000000..61a1cb5
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/fp_to_locked.xml
@@ -0,0 +1,165 @@
+<?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
+ -->
+<animated-vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <aapt:attr name="android:drawable">
+ <vector android:height="65dp" android:width="46dp" android:viewportHeight="65" android:viewportWidth="46">
+ <group android:name="_R_G">
+ <group android:name="_R_G_L_1_G" android:translateX="3.75" android:translateY="8.25">
+ <path android:name="_R_G_L_1_G_D_0_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 "/>
+ <path android:name="_R_G_L_1_G_D_1_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 "/>
+ <path android:name="_R_G_L_1_G_D_2_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 "/>
+ <path android:name="_R_G_L_1_G_D_3_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 "/>
+ </group>
+ <group android:name="_R_G_L_0_G" android:translateX="20.357" android:translateY="35.75" android:pivotX="2.75" android:pivotY="2.75" android:scaleX="1.41866" android:scaleY="1.41866">
+ <path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#FF000000" android:fillAlpha="0" android:fillType="nonZero" android:pathData=" M2.75 5.25 C4.13,5.25 5.25,4.13 5.25,2.75 C5.25,1.37 4.13,0.25 2.75,0.25 C1.37,0.25 0.25,1.37 0.25,2.75 C0.25,4.13 1.37,5.25 2.75,5.25c "/>
+ </group>
+ </group>
+ <group android:name="time_group"/>
+ </vector>
+ </aapt:attr>
+ <target android:name="_R_G_L_1_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="0" android:valueFrom="M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 " android:valueTo="M30.81 32.26 C30.56,32.49 30.27,38.76 29.96,38.9 C29.77,39.46 29.13,39.94 28.57,40.26 C28.15,40.51 26.93,40.65 26.4,40.65 C26.18,40.65 11.91,40.62 11.71,40.58 C10.68,40.53 9.06,39.79 8.89,38.88 C8.6,38.74 8.34,32.48 8.1,32.27 " android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="pathData" android:duration="143" android:startOffset="107" android:valueFrom="M30.81 32.26 C30.56,32.49 30.27,38.76 29.96,38.9 C29.77,39.46 29.13,39.94 28.57,40.26 C28.15,40.51 26.93,40.65 26.4,40.65 C26.18,40.65 11.91,40.62 11.71,40.58 C10.68,40.53 9.06,39.79 8.89,38.88 C8.6,38.74 8.34,32.48 8.1,32.27 " android:valueTo="M30.64 30.14 C30.64,30.14 30.64,38.14 30.64,38.14 C30.64,38.77 30.36,39.32 29.91,39.69 C29.57,39.97 29.12,40.14 28.64,40.14 C28.64,40.14 10.14,40.14 10.14,40.14 C9.04,40.14 8.14,39.25 8.14,38.14 C8.14,38.14 8.14,30.14 8.14,30.14 " android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.331,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_1_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="strokeAlpha" android:duration="140" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="strokeAlpha" android:duration="50" android:startOffset="140" android:valueFrom="1" android:valueTo="0" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_1_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="0" android:valueFrom="M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 " android:valueTo="M18.93 32.18 C17.11,32.68 16.62,30.26 16.62,30.26 C16.62,28.78 17.81,27.59 19.39,27.59 C20.96,27.59 22.15,28.78 22.15,30.26 C22.15,30.26 22.15,30.34 22.15,30.34 C22.15,30.89 21.11,32.54 19.57,32.19 C19.19,32.1 20.48,31.09 20.34,30.71 C20.34,30.71 20.02,29.88 20.02,29.88 C19.88,29.5 19.53,29.25 19.15,29.25 C18.63,29.25 18,29.67 18,30.22 C18,30.57 18.06,31.08 18.32,31.51 C18.49,31.8 19.02,32.25 19.79,32.04 C20.41,31.7 20.38,31.36 20.38,31.36 " android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="107" android:valueFrom="M18.93 32.18 C17.11,32.68 16.62,30.26 16.62,30.26 C16.62,28.78 17.81,27.59 19.39,27.59 C20.96,27.59 22.15,28.78 22.15,30.26 C22.15,30.26 22.15,30.34 22.15,30.34 C22.15,30.89 21.11,32.54 19.57,32.19 C19.19,32.1 20.48,31.09 20.34,30.71 C20.34,30.71 20.02,29.88 20.02,29.88 C19.88,29.5 19.53,29.25 19.15,29.25 C18.63,29.25 18,29.67 18,30.22 C18,30.57 18.06,31.08 18.32,31.51 C18.49,31.8 19.02,32.25 19.79,32.04 C20.41,31.7 20.38,31.36 20.38,31.36 " android:valueTo="M19.42 31.53 C18.15,31.52 18.11,30.33 18.11,30.33 C18.11,29.59 18.66,28.98 19.4,28.98 C20.13,28.98 20.69,29.59 20.69,30.33 C20.69,30.33 20.69,30.37 20.69,30.37 C20.69,30.64 20.49,30.87 20.25,30.87 C20.07,30.87 19.91,30.74 19.84,30.55 C19.84,30.55 19.69,30.14 19.69,30.14 C19.63,29.94 19.46,29.82 19.28,29.82 C19.04,29.82 18.61,30.02 18.61,30.29 C18.61,30.43 18.6,30.75 18.76,31.03 C18.87,31.21 19.21,31.77 19.96,31.41 C20.69,31.01 20.69,30.34 20.69,30.34 " android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_2_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="pathData" android:duration="250" android:startOffset="0" android:valueFrom="M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 " android:valueTo="M8.14 30.22 C8.14,30.22 8.14,22.22 8.14,22.22 C8.14,21.71 8.33,21.25 8.64,20.9 C9,20.48 9.54,20.22 10.14,20.22 C10.14,20.22 28.64,20.22 28.64,20.22 C29.75,20.22 30.64,21.11 30.64,22.22 C30.64,22.22 30.64,30.14 30.64,30.14 " android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.189,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_3_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="pathData" android:duration="95" android:startOffset="0" android:valueFrom="M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 " android:valueTo="M11.47 14.84 C11.47,14.84 12.21,11.43 13.54,9.84 C14.84,8.28 16.68,7.22 19.35,7.22 C22.01,7.22 23.98,8.4 25.19,10.18 C26.39,11.96 27.25,14.84 27.25,14.84 " android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="pathData" android:duration="24" android:startOffset="95" android:valueFrom="M11.47 14.84 C11.47,14.84 12.21,11.43 13.54,9.84 C14.84,8.28 16.68,7.22 19.35,7.22 C22.01,7.22 23.98,8.4 25.19,10.18 C26.39,11.96 27.25,14.84 27.25,14.84 " android:valueTo="M12.11 16.85 C12.11,16.85 12.82,12.71 13.37,11.5 C14.17,9.24 16.38,7.53 19.35,7.53 C22.32,7.53 24.61,9.32 25.35,11.72 C25.61,12.64 26.62,16.85 26.62,16.85 " android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.833,0.767 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="pathData" android:duration="81" android:startOffset="119" android:valueFrom="M12.11 16.85 C12.11,16.85 12.82,12.71 13.37,11.5 C14.17,9.24 16.38,7.53 19.35,7.53 C22.32,7.53 24.61,9.32 25.35,11.72 C25.61,12.64 26.62,16.85 26.62,16.85 " android:valueTo="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.261,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="fillAlpha" android:duration="120" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="fillAlpha" android:duration="20" android:startOffset="120" android:valueFrom="0" android:valueTo="1" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="scaleX" android:duration="120" android:startOffset="0" android:valueFrom="1.4186600000000003" android:valueTo="1.4186600000000003" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="scaleY" android:duration="120" android:startOffset="0" android:valueFrom="1.4186600000000003" android:valueTo="1.4186600000000003" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="scaleX" android:duration="130" android:startOffset="120" android:valueFrom="1.4186600000000003" android:valueTo="1" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="scaleY" android:duration="130" android:startOffset="120" android:valueFrom="1.4186600000000003" android:valueTo="1" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="translateX" android:duration="517" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
+ </set>
+ </aapt:attr>
+ </target>
+</animated-vector>
diff --git a/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml
index 951d6fe..f3325ec 100644
--- a/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml
+++ b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml
@@ -104,4 +104,9 @@
android:fromId="@id/unlocked"
android:toId="@id/locked"
android:drawable="@drawable/unlocked_to_locked" />
+
+ <transition
+ android:fromId="@id/locked_fp"
+ android:toId="@id/locked"
+ android:drawable="@drawable/fp_to_locked" />
</animated-selector>
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index 2143fc4..edd3047 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -21,19 +21,19 @@
<!-- Instructions telling the user to enter their PIN password to unlock the keyguard [CHAR LIMIT=30] -->
<string name="keyguard_enter_your_pin">Enter your PIN</string>
- <!-- Instructions telling the user to enter their PIN password to unlock the keyguard [CHAR LIMIT=26] -->
+ <!-- Instructions telling the user to enter their PIN password to unlock the keyguard [CHAR LIMIT=48] -->
<string name="keyguard_enter_pin">Enter PIN</string>
<!-- Instructions telling the user to enter their pattern to unlock the keyguard [CHAR LIMIT=30] -->
<string name="keyguard_enter_your_pattern">Enter your pattern</string>
- <!-- Instructions telling the user to enter their pattern to unlock the keyguard [CHAR LIMIT=26] -->
+ <!-- Instructions telling the user to enter their pattern to unlock the keyguard [CHAR LIMIT=48] -->
<string name="keyguard_enter_pattern">Draw pattern</string>
<!-- Instructions telling the user to enter their text password to unlock the keyguard [CHAR LIMIT=30] -->
<string name="keyguard_enter_your_password">Enter your password</string>
- <!-- Instructions telling the user to enter their text password to unlock the keyguard [CHAR LIMIT=26] -->
+ <!-- Instructions telling the user to enter their text password to unlock the keyguard [CHAR LIMIT=48] -->
<string name="keyguard_enter_password">Enter password</string>
<!-- Shown in the lock screen when there is SIM card IO error. -->
@@ -128,103 +128,103 @@
<!-- Message shown when user enters wrong pattern -->
<string name="kg_wrong_pattern">Wrong pattern</string>
- <!-- Message shown when user enters wrong pattern [CHAR LIMIT=26] -->
+ <!-- Message shown when user enters wrong pattern [CHAR LIMIT=48] -->
<string name="kg_wrong_pattern_try_again">Wrong pattern. Try again.</string>
<!-- Message shown when user enters wrong password -->
<string name="kg_wrong_password">Wrong password</string>
- <!-- Message shown when user enters wrong pattern [CHAR LIMIT=26] -->
+ <!-- Message shown when user enters wrong pattern [CHAR LIMIT=48] -->
<string name="kg_wrong_password_try_again">Wrong password. Try again.</string>
<!-- Message shown when user enters wrong PIN -->
<string name="kg_wrong_pin">Wrong PIN</string>
- <!-- Message shown when user enters wrong PIN [CHAR LIMIT=26] -->
+ <!-- Message shown when user enters wrong PIN [CHAR LIMIT=48] -->
<string name="kg_wrong_pin_try_again">Wrong PIN. Try again.</string>
- <!-- Message shown when user enters wrong PIN/password/pattern below the main message, for ex: "Wrong PIN. Try again" in line 1 and the following text in line 2. [CHAR LIMIT=52] -->
+ <!-- Message shown when user enters wrong PIN/password/pattern below the main message, for ex: "Wrong PIN. Try again" in line 1 and the following text in line 2. [CHAR LIMIT=70] -->
<string name="kg_wrong_input_try_fp_suggestion">Or unlock with fingerprint</string>
- <!-- Message shown when user fingerprint is not recognized [CHAR LIMIT=26] -->
+ <!-- Message shown when user fingerprint is not recognized [CHAR LIMIT=48] -->
<string name="kg_fp_not_recognized">Fingerprint not recognized</string>
- <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=26] -->
+ <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=48] -->
<string name="bouncer_face_not_recognized">Face not recognized</string>
- <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=52] -->
+ <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=70] -->
<string name="kg_bio_try_again_or_pin">Try again or enter PIN</string>
- <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=52] -->
+ <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=70] -->
<string name="kg_bio_try_again_or_password">Try again or enter password</string>
- <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=52] -->
+ <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=70] -->
<string name="kg_bio_try_again_or_pattern">Try again or draw pattern</string>
- <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint [CHAR LIMIT=52] -->
+ <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint [CHAR LIMIT=70] -->
<string name="kg_bio_too_many_attempts_pin">PIN is required after too many attempts</string>
- <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint [CHAR LIMIT=52] -->
+ <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint [CHAR LIMIT=70] -->
<string name="kg_bio_too_many_attempts_password">Password is required after too many attempts</string>
- <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint [CHAR LIMIT=52] -->
+ <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint [CHAR LIMIT=70] -->
<string name="kg_bio_too_many_attempts_pattern">Pattern is required after too many attempts</string>
- <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=26] -->
+ <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=48] -->
<string name="kg_unlock_with_pin_or_fp">Unlock with PIN or fingerprint</string>
- <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=26] -->
+ <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=48] -->
<string name="kg_unlock_with_password_or_fp">Unlock with password or fingerprint</string>
- <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=26] -->
+ <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=48] -->
<string name="kg_unlock_with_pattern_or_fp">Unlock with pattern or fingerprint</string>
- <!-- Message shown when we are on bouncer after Device admin requested lockdown. [CHAR LIMIT=52] -->
+ <!-- Message shown when we are on bouncer after Device admin requested lockdown. [CHAR LIMIT=70] -->
<string name="kg_prompt_after_dpm_lock">For added security, device was locked by work policy</string>
- <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown. [CHAR LIMIT=52] -->
+ <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown. [CHAR LIMIT=70] -->
<string name="kg_prompt_after_user_lockdown_pin">PIN is required after lockdown</string>
- <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown. [CHAR LIMIT=52] -->
+ <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown. [CHAR LIMIT=70] -->
<string name="kg_prompt_after_user_lockdown_password">Password is required after lockdown</string>
- <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown. [CHAR LIMIT=52] -->
+ <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown. [CHAR LIMIT=70] -->
<string name="kg_prompt_after_user_lockdown_pattern">Pattern is required after lockdown</string>
- <!-- Message shown to prepare for an unattended update (OTA). Also known as an over-the-air (OTA) update. [CHAR LIMIT=52] -->
+ <!-- Message shown to prepare for an unattended update (OTA). Also known as an over-the-air (OTA) update. [CHAR LIMIT=70] -->
<string name="kg_prompt_unattended_update">Update will install during inactive hours</string>
- <!-- Message shown when primary authentication hasn't been used for some time. [CHAR LIMIT=52] -->
+ <!-- Message shown when primary authentication hasn't been used for some time. [CHAR LIMIT=70] -->
<string name="kg_prompt_pin_auth_timeout">Added security required. PIN not used for a while.</string>
- <!-- Message shown when primary authentication hasn't been used for some time. [CHAR LIMIT=52] -->
+ <!-- Message shown when primary authentication hasn't been used for some time. [CHAR LIMIT=70] -->
<string name="kg_prompt_password_auth_timeout">Added security required. Password not used for a while.</string>
- <!-- Message shown when primary authentication hasn't been used for some time. [CHAR LIMIT=52] -->
+ <!-- Message shown when primary authentication hasn't been used for some time. [CHAR LIMIT=76] -->
<string name="kg_prompt_pattern_auth_timeout">Added security required. Pattern not used for a while.</string>
- <!-- Message shown when device hasn't been unlocked for a while. [CHAR LIMIT=52] -->
+ <!-- Message shown when device hasn't been unlocked for a while. [CHAR LIMIT=82] -->
<string name="kg_prompt_auth_timeout">Added security required. Device wasn\u2019t unlocked for a while.</string>
- <!-- Message shown when face unlock is not available after too many failed face authentication attempts. [CHAR LIMIT=52] -->
+ <!-- Message shown when face unlock is not available after too many failed face authentication attempts. [CHAR LIMIT=70] -->
<string name="kg_face_locked_out">Can\u2019t unlock with face. Too many attempts.</string>
- <!-- Message shown when fingerprint unlock isn't available after too many failed fingerprint authentication attempts. [CHAR LIMIT=52] -->
+ <!-- Message shown when fingerprint unlock isn't available after too many failed fingerprint authentication attempts. [CHAR LIMIT=75] -->
<string name="kg_fp_locked_out">Can\u2019t unlock with fingerprint. Too many attempts.</string>
- <!-- Message shown when Trust Agent is disabled. [CHAR LIMIT=52] -->
+ <!-- Message shown when Trust Agent is disabled. [CHAR LIMIT=70] -->
<string name="kg_trust_agent_disabled">Trust agent is unavailable</string>
- <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=52] -->
+ <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=70] -->
<string name="kg_primary_auth_locked_out_pin">Too many attempts with incorrect PIN</string>
- <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=52] -->
+ <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=70] -->
<string name="kg_primary_auth_locked_out_pattern">Too many attempts with incorrect pattern</string>
- <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=52] -->
+ <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=70] -->
<string name="kg_primary_auth_locked_out_password">Too many attempts with incorrect password</string>
- <!-- Countdown message shown after too many failed unlock attempts [CHAR LIMIT=26]-->
+ <!-- Countdown message shown after too many failed unlock attempts [CHAR LIMIT=48]-->
<string name="kg_too_many_failed_attempts_countdown">{count, plural,
=1 {Try again in # second.}
other {Try again in # seconds.}
@@ -296,13 +296,13 @@
<!-- Description of airplane mode -->
<string name="airplane_mode">Airplane mode</string>
- <!-- An explanation text that the pattern needs to be solved since the device has just been restarted. [CHAR LIMIT=52] -->
+ <!-- An explanation text that the pattern needs to be solved since the device has just been restarted. [CHAR LIMIT=70] -->
<string name="kg_prompt_reason_restart_pattern">Pattern is required after device restarts</string>
- <!-- An explanation text that the pin needs to be entered since the device has just been restarted. [CHAR LIMIT=52] -->
+ <!-- An explanation text that the pin needs to be entered since the device has just been restarted. [CHAR LIMIT=70] -->
<string name="kg_prompt_reason_restart_pin">PIN is required after device restarts</string>
- <!-- An explanation text that the password needs to be entered since the device has just been restarted. [CHAR LIMIT=52] -->
+ <!-- An explanation text that the password needs to be entered since the device has just been restarted. [CHAR LIMIT=70] -->
<string name="kg_prompt_reason_restart_password">Password is required after device restarts</string>
<!-- An explanation text that the pattern needs to be solved since the user hasn't used strong authentication since quite some time. [CHAR LIMIT=80] -->
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index c0f7029..e9acf3f 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -64,20 +64,20 @@
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_gravity="bottom"
+ android:layout_marginHorizontal="@dimen/keyguard_affordance_horizontal_offset"
+ android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
+ android:gravity="bottom"
>
<com.android.systemui.animation.view.LaunchableImageView
android:id="@+id/start_button"
android:layout_height="@dimen/keyguard_affordance_fixed_height"
android:layout_width="@dimen/keyguard_affordance_fixed_width"
- android:layout_gravity="bottom|start"
android:scaleType="fitCenter"
android:padding="@dimen/keyguard_affordance_fixed_padding"
android:tint="?android:attr/textColorPrimary"
android:background="@drawable/keyguard_bottom_affordance_bg"
android:foreground="@drawable/keyguard_bottom_affordance_selected_border"
- android:layout_marginStart="@dimen/keyguard_affordance_horizontal_offset"
- android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
android:visibility="invisible" />
<FrameLayout
@@ -90,7 +90,7 @@
android:id="@+id/keyguard_settings_button"
layout="@layout/keyguard_settings_popup_menu"
android:layout_width="wrap_content"
- android:layout_height="@dimen/keyguard_affordance_fixed_height"
+ android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone"
/>
@@ -100,14 +100,11 @@
android:id="@+id/end_button"
android:layout_height="@dimen/keyguard_affordance_fixed_height"
android:layout_width="@dimen/keyguard_affordance_fixed_width"
- android:layout_gravity="bottom|end"
android:scaleType="fitCenter"
android:padding="@dimen/keyguard_affordance_fixed_padding"
android:tint="?android:attr/textColorPrimary"
android:background="@drawable/keyguard_bottom_affordance_bg"
android:foreground="@drawable/keyguard_bottom_affordance_selected_border"
- android:layout_marginEnd="@dimen/keyguard_affordance_horizontal_offset"
- android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
android:visibility="invisible" />
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/keyguard_settings_popup_menu.xml b/packages/SystemUI/res/layout/keyguard_settings_popup_menu.xml
index 9d0d783..65ee8b3 100644
--- a/packages/SystemUI/res/layout/keyguard_settings_popup_menu.xml
+++ b/packages/SystemUI/res/layout/keyguard_settings_popup_menu.xml
@@ -20,7 +20,8 @@
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
- android:layout_height="48dp"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/keyguard_affordance_fixed_height"
android:orientation="horizontal"
android:gravity="center_vertical"
android:background="@drawable/keyguard_settings_popup_menu_background"
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index f1fca76..d710676 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -152,10 +152,4 @@
<include
layout="@layout/keyguard_bottom_area"
android:visibility="gone" />
-
- <FrameLayout
- android:id="@+id/preview_container"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
- </FrameLayout>
</com.android.systemui.shade.NotificationPanelView>
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
index 7971e84..b153785 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
@@ -21,7 +21,7 @@
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.res.Resources;
-import android.net.wifi.WifiManager;
+import android.os.Trace;
import android.telephony.ServiceState;
import android.telephony.SubscriptionInfo;
import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener;
@@ -37,6 +37,7 @@
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository;
import com.android.systemui.telephony.TelephonyListenerManager;
import java.util.List;
@@ -50,7 +51,10 @@
* Controller that generates text including the carrier names and/or the status of all the SIM
* interfaces in the device. Through a callback, the updates can be retrieved either as a list or
* separated by a given separator {@link CharSequence}.
+ *
+ * @deprecated use {@link com.android.systemui.statusbar.pipeline.wifi} instead
*/
+@Deprecated
public class CarrierTextManager {
private static final boolean DEBUG = KeyguardConstants.DEBUG;
private static final String TAG = "CarrierTextController";
@@ -64,7 +68,7 @@
private final AtomicBoolean mNetworkSupported = new AtomicBoolean();
@VisibleForTesting
protected KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- private final WifiManager mWifiManager;
+ private final WifiRepository mWifiRepository;
private final boolean[] mSimErrorState;
private final int mSimSlotsNumber;
@Nullable // Check for nullability before dispatching
@@ -165,7 +169,7 @@
CharSequence separator,
boolean showAirplaneMode,
boolean showMissingSim,
- @Nullable WifiManager wifiManager,
+ WifiRepository wifiRepository,
TelephonyManager telephonyManager,
TelephonyListenerManager telephonyListenerManager,
WakefulnessLifecycle wakefulnessLifecycle,
@@ -177,8 +181,7 @@
mShowAirplaneMode = showAirplaneMode;
mShowMissingSim = showMissingSim;
-
- mWifiManager = wifiManager;
+ mWifiRepository = wifiRepository;
mTelephonyManager = telephonyManager;
mSeparator = separator;
mTelephonyListenerManager = telephonyListenerManager;
@@ -297,6 +300,7 @@
}
protected void updateCarrierText() {
+ Trace.beginSection("CarrierTextManager#updateCarrierText");
boolean allSimsMissing = true;
boolean anySimReadyAndInService = false;
CharSequence displayText = null;
@@ -329,20 +333,20 @@
carrierNames[i] = carrierTextForSimState;
}
if (simState == TelephonyManager.SIM_STATE_READY) {
+ Trace.beginSection("WFC check");
ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId);
if (ss != null && ss.getDataRegistrationState() == ServiceState.STATE_IN_SERVICE) {
// hack for WFC (IWLAN) not turning off immediately once
// Wi-Fi is disassociated or disabled
if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
- || (mWifiManager != null && mWifiManager.isWifiEnabled()
- && mWifiManager.getConnectionInfo() != null
- && mWifiManager.getConnectionInfo().getBSSID() != null)) {
+ || mWifiRepository.isWifiConnectedWithValidSsid()) {
if (DEBUG) {
Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss);
}
anySimReadyAndInService = true;
}
}
+ Trace.endSection();
}
}
// Only create "No SIM card" if no cards with CarrierName && no wifi when some sim is READY
@@ -406,6 +410,7 @@
subsIds,
airplaneMode);
postToCallback(info);
+ Trace.endSection();
}
@VisibleForTesting
@@ -633,7 +638,7 @@
public static class Builder {
private final Context mContext;
private final String mSeparator;
- private final WifiManager mWifiManager;
+ private final WifiRepository mWifiRepository;
private final TelephonyManager mTelephonyManager;
private final TelephonyListenerManager mTelephonyListenerManager;
private final WakefulnessLifecycle mWakefulnessLifecycle;
@@ -647,7 +652,7 @@
public Builder(
Context context,
@Main Resources resources,
- @Nullable WifiManager wifiManager,
+ @Nullable WifiRepository wifiRepository,
TelephonyManager telephonyManager,
TelephonyListenerManager telephonyListenerManager,
WakefulnessLifecycle wakefulnessLifecycle,
@@ -657,7 +662,7 @@
mContext = context;
mSeparator = resources.getString(
com.android.internal.R.string.kg_text_message_separator);
- mWifiManager = wifiManager;
+ mWifiRepository = wifiRepository;
mTelephonyManager = telephonyManager;
mTelephonyListenerManager = telephonyListenerManager;
mWakefulnessLifecycle = wakefulnessLifecycle;
@@ -681,7 +686,7 @@
/** Create a CarrierTextManager. */
public CarrierTextManager build() {
return new CarrierTextManager(
- mContext, mSeparator, mShowAirplaneMode, mShowMissingSim, mWifiManager,
+ mContext, mSeparator, mShowAirplaneMode, mShowMissingSim, mWifiRepository,
mTelephonyManager, mTelephonyListenerManager, mWakefulnessLifecycle,
mMainExecutor, mBgExecutor, mKeyguardUpdateMonitor);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 9290220..25d1792 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -109,7 +109,7 @@
private final ContentObserver mShowWeatherObserver = new ContentObserver(null) {
@Override
public void onChange(boolean change) {
- setDateWeatherVisibility();
+ setWeatherVisibility();
}
};
@@ -236,6 +236,7 @@
updateDoubleLineClock();
setDateWeatherVisibility();
+ setWeatherVisibility();
mKeyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(
mKeyguardUnlockAnimationListener);
@@ -266,6 +267,8 @@
mStatusArea.removeView(mDateWeatherView);
addDateWeatherView(index);
}
+ setDateWeatherVisibility();
+ setWeatherVisibility();
}
int index = mStatusArea.indexOfChild(mSmartspaceView);
if (index >= 0) {
@@ -487,16 +490,19 @@
}
private void setDateWeatherVisibility() {
- if (mDateWeatherView != null || mWeatherView != null) {
+ if (mDateWeatherView != null) {
mUiExecutor.execute(() -> {
- if (mDateWeatherView != null) {
- mDateWeatherView.setVisibility(
- clockHasCustomWeatherDataDisplay() ? View.GONE : View.VISIBLE);
- }
- if (mWeatherView != null) {
- mWeatherView.setVisibility(
- mSmartspaceController.isWeatherEnabled() ? View.VISIBLE : View.GONE);
- }
+ mDateWeatherView.setVisibility(
+ clockHasCustomWeatherDataDisplay() ? View.GONE : View.VISIBLE);
+ });
+ }
+ }
+
+ private void setWeatherVisibility() {
+ if (mWeatherView != null) {
+ mUiExecutor.execute(() -> {
+ mWeatherView.setVisibility(
+ mSmartspaceController.isWeatherEnabled() ? View.VISIBLE : View.GONE);
});
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index c4df836..6f54988 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -16,12 +16,37 @@
package com.android.keyguard;
+import static androidx.constraintlayout.widget.ConstraintSet.END;
+import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
+
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION;
+
+import android.animation.Animator;
+import android.animation.ValueAnimator;
import android.annotation.Nullable;
import android.graphics.Rect;
+import android.transition.ChangeBounds;
+import android.transition.Transition;
+import android.transition.TransitionListenerAdapter;
+import android.transition.TransitionManager;
+import android.transition.TransitionSet;
+import android.transition.TransitionValues;
import android.util.Slog;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import androidx.annotation.VisibleForTesting;
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.constraintlayout.widget.ConstraintSet;
+
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.keyguard.KeyguardClockSwitch.ClockSize;
import com.android.keyguard.logging.KeyguardLogger;
+import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.ClockController;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
@@ -42,6 +67,12 @@
private static final boolean DEBUG = KeyguardConstants.DEBUG;
private static final String TAG = "KeyguardStatusViewController";
+ /**
+ * Duration to use for the animator when the keyguard status view alignment changes, and a
+ * custom clock animation is in use.
+ */
+ private static final int KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION = 1000;
+
private static final AnimationProperties CLOCK_ANIMATION_PROPERTIES =
new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
@@ -50,8 +81,25 @@
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final ConfigurationController mConfigurationController;
private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
+ private final FeatureFlags mFeatureFlags;
+ private final InteractionJankMonitor mInteractionJankMonitor;
private final Rect mClipBounds = new Rect();
+ private Boolean mStatusViewCentered = true;
+
+ private final TransitionListenerAdapter mKeyguardStatusAlignmentTransitionListener =
+ new TransitionListenerAdapter() {
+ @Override
+ public void onTransitionCancel(Transition transition) {
+ mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION);
+ }
+
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ mInteractionJankMonitor.end(CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION);
+ }
+ };
+
@Inject
public KeyguardStatusViewController(
KeyguardStatusView keyguardStatusView,
@@ -62,7 +110,9 @@
ConfigurationController configurationController,
DozeParameters dozeParameters,
ScreenOffAnimationController screenOffAnimationController,
- KeyguardLogger logger) {
+ KeyguardLogger logger,
+ FeatureFlags featureFlags,
+ InteractionJankMonitor interactionJankMonitor) {
super(keyguardStatusView);
mKeyguardSliceViewController = keyguardSliceViewController;
mKeyguardClockSwitchController = keyguardClockSwitchController;
@@ -71,6 +121,8 @@
mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController,
dozeParameters, screenOffAnimationController, /* animateYPos= */ true,
logger.getBuffer());
+ mInteractionJankMonitor = interactionJankMonitor;
+ mFeatureFlags = featureFlags;
}
@Override
@@ -242,9 +294,141 @@
}
}
- /** Gets the current clock controller. */
- @Nullable
- public ClockController getClockController() {
- return mKeyguardClockSwitchController.getClock();
+ /**
+ * Updates the alignment of the KeyguardStatusView and animates the transition if requested.
+ */
+ public void updateAlignment(
+ ConstraintLayout notifContainerParent,
+ boolean splitShadeEnabled,
+ boolean shouldBeCentered,
+ boolean animate) {
+ if (mStatusViewCentered == shouldBeCentered) {
+ return;
+ }
+
+ mStatusViewCentered = shouldBeCentered;
+ if (notifContainerParent == null) {
+ return;
+ }
+
+ ConstraintSet constraintSet = new ConstraintSet();
+ constraintSet.clone(notifContainerParent);
+ int statusConstraint = shouldBeCentered ? PARENT_ID : R.id.qs_edge_guideline;
+ constraintSet.connect(R.id.keyguard_status_view, END, statusConstraint, END);
+ if (!animate) {
+ constraintSet.applyTo(notifContainerParent);
+ return;
+ }
+
+ mInteractionJankMonitor.begin(mView, CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION);
+ ChangeBounds transition = new ChangeBounds();
+ if (splitShadeEnabled) {
+ // Excluding media from the transition on split-shade, as it doesn't transition
+ // horizontally properly.
+ transition.excludeTarget(R.id.status_view_media_container, true);
+ }
+
+ transition.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+ transition.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+
+ ClockController clock = mKeyguardClockSwitchController.getClock();
+ boolean customClockAnimation = clock != null
+ && clock.getConfig().getHasCustomPositionUpdatedAnimation();
+
+ if (mFeatureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION) && customClockAnimation) {
+ // Find the clock, so we can exclude it from this transition.
+ FrameLayout clockContainerView = mView.findViewById(R.id.lockscreen_clock_view_large);
+
+ // The clock container can sometimes be null. If it is, just fall back to the
+ // old animation rather than setting up the custom animations.
+ if (clockContainerView == null || clockContainerView.getChildCount() == 0) {
+ transition.addListener(mKeyguardStatusAlignmentTransitionListener);
+ TransitionManager.beginDelayedTransition(notifContainerParent, transition);
+ } else {
+ View clockView = clockContainerView.getChildAt(0);
+
+ transition.excludeTarget(clockView, /* exclude= */ true);
+
+ TransitionSet set = new TransitionSet();
+ set.addTransition(transition);
+
+ SplitShadeTransitionAdapter adapter =
+ new SplitShadeTransitionAdapter(mKeyguardClockSwitchController);
+
+ // Use linear here, so the actual clock can pick its own interpolator.
+ adapter.setInterpolator(Interpolators.LINEAR);
+ adapter.setDuration(KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION);
+ adapter.addTarget(clockView);
+ set.addTransition(adapter);
+ set.addListener(mKeyguardStatusAlignmentTransitionListener);
+ TransitionManager.beginDelayedTransition(notifContainerParent, set);
+ }
+ } else {
+ transition.addListener(mKeyguardStatusAlignmentTransitionListener);
+ TransitionManager.beginDelayedTransition(notifContainerParent, transition);
+ }
+
+ constraintSet.applyTo(notifContainerParent);
+ }
+
+ @VisibleForTesting
+ static class SplitShadeTransitionAdapter extends Transition {
+ private static final String PROP_BOUNDS = "splitShadeTransitionAdapter:bounds";
+ private static final String[] TRANSITION_PROPERTIES = { PROP_BOUNDS };
+
+ private final KeyguardClockSwitchController mController;
+
+ @VisibleForTesting
+ SplitShadeTransitionAdapter(KeyguardClockSwitchController controller) {
+ mController = controller;
+ }
+
+ private void captureValues(TransitionValues transitionValues) {
+ Rect boundsRect = new Rect();
+ boundsRect.left = transitionValues.view.getLeft();
+ boundsRect.top = transitionValues.view.getTop();
+ boundsRect.right = transitionValues.view.getRight();
+ boundsRect.bottom = transitionValues.view.getBottom();
+ transitionValues.values.put(PROP_BOUNDS, boundsRect);
+ }
+
+ @Override
+ public void captureEndValues(TransitionValues transitionValues) {
+ captureValues(transitionValues);
+ }
+
+ @Override
+ public void captureStartValues(TransitionValues transitionValues) {
+ captureValues(transitionValues);
+ }
+
+ @Nullable
+ @Override
+ public Animator createAnimator(ViewGroup sceneRoot, @Nullable TransitionValues startValues,
+ @Nullable TransitionValues endValues) {
+ if (startValues == null || endValues == null) {
+ return null;
+ }
+ ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+
+ Rect from = (Rect) startValues.values.get(PROP_BOUNDS);
+ Rect to = (Rect) endValues.values.get(PROP_BOUNDS);
+
+ anim.addUpdateListener(animation -> {
+ ClockController clock = mController.getClock();
+ if (clock == null) {
+ return;
+ }
+
+ clock.getAnimations().onPositionUpdated(from, to, animation.getAnimatedFraction());
+ });
+
+ return anim;
+ }
+
+ @Override
+ public String[] getTransitionProperties() {
+ return TRANSITION_PROPERTIES;
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index e1bca89..350c4ed 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -96,6 +96,7 @@
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
import android.hardware.biometrics.SensorProperties;
+import android.hardware.biometrics.SensorPropertiesInternal;
import android.hardware.face.FaceAuthenticateOptions;
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorPropertiesInternal;
@@ -1278,6 +1279,9 @@
if (msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT) {
lockedOutStateChanged = !mFaceLockedOutPermanent;
mFaceLockedOutPermanent = true;
+ if (isFaceClass3()) {
+ updateFingerprintListeningState(BIOMETRIC_ACTION_STOP);
+ }
}
if (isHwUnavailable && cameraPrivacyEnabled) {
@@ -1487,8 +1491,10 @@
// STRONG_AUTH_REQUIRED_AFTER_LOCKOUT which is the same as mFingerprintLockedOutPermanent;
// however the strong auth tracker does not include the temporary lockout
// mFingerprintLockedOut.
+ // Class 3 biometric lockout will lockout ALL biometrics
return mStrongAuthTracker.isUnlockingWithBiometricAllowed(isStrongBiometric)
- && !mFingerprintLockedOut;
+ && (!isFingerprintClass3() || !isFingerprintLockedOut())
+ && (!isFaceClass3() || !mFaceLockedOutPermanent);
}
/**
@@ -1506,9 +1512,9 @@
@NonNull BiometricSourceType biometricSourceType) {
switch (biometricSourceType) {
case FINGERPRINT:
- return isUnlockingWithBiometricAllowed(true);
+ return isUnlockingWithBiometricAllowed(isFingerprintClass3());
case FACE:
- return isUnlockingWithBiometricAllowed(false);
+ return isUnlockingWithBiometricAllowed(isFaceClass3());
default:
return false;
}
@@ -2473,7 +2479,7 @@
}
private void updateFaceEnrolled(int userId) {
- Boolean isFaceEnrolled = mFaceManager != null && !mFaceSensorProperties.isEmpty()
+ final Boolean isFaceEnrolled = isFaceSupported()
&& mBiometricEnabledForUser.get(userId)
&& mAuthController.isFaceAuthEnrolled(userId);
if (mIsFaceEnrolled != isFaceEnrolled) {
@@ -2482,10 +2488,14 @@
mIsFaceEnrolled = isFaceEnrolled;
}
- public boolean isFaceSupported() {
+ private boolean isFaceSupported() {
return mFaceManager != null && !mFaceSensorProperties.isEmpty();
}
+ private boolean isFingerprintSupported() {
+ return mFpm != null && !mFingerprintSensorProperties.isEmpty();
+ }
+
/**
* @return true if there's at least one udfps enrolled for the current user.
*/
@@ -2792,10 +2802,10 @@
|| !mLockPatternUtils.isSecure(user);
// Don't trigger active unlock if fp is locked out
- final boolean fpLockedOut = mFingerprintLockedOut || mFingerprintLockedOutPermanent;
+ final boolean fpLockedOut = isFingerprintLockedOut();
// Don't trigger active unlock if primary auth is required
- final boolean primaryAuthRequired = !isUnlockingWithBiometricAllowed(true);
+ final boolean primaryAuthRequired = !isUnlockingWithTrustAgentAllowed();
final boolean shouldTriggerActiveUnlock =
(mAuthInterruptActive || triggerActiveUnlockForAssistant || awakeKeyguard)
@@ -2857,7 +2867,7 @@
|| mGoingToSleep
|| shouldListenForFingerprintAssistant
|| (mKeyguardOccluded && mIsDreaming)
- || (mKeyguardOccluded && userDoesNotHaveTrust
+ || (mKeyguardOccluded && userDoesNotHaveTrust && mKeyguardShowing
&& (mOccludingAppRequestingFp || isUdfps || mAlternateBouncerShowing));
// Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an
@@ -2949,7 +2959,7 @@
// allow face detection to happen even if stronger auth is required. When face is detected,
// we show the bouncer. However, if the user manually locked down the device themselves,
// never attempt to detect face.
- final boolean supportsDetect = !mFaceSensorProperties.isEmpty()
+ final boolean supportsDetect = isFaceSupported()
&& mFaceSensorProperties.get(0).supportsFaceDetection
&& canBypass && !mPrimaryBouncerIsOrWillBeShowing
&& !isUserInLockdown(user);
@@ -3104,7 +3114,7 @@
: WAKE_REASON_UNKNOWN
).toFaceAuthenticateOptions();
// This would need to be updated for multi-sensor devices
- final boolean supportsFaceDetection = !mFaceSensorProperties.isEmpty()
+ final boolean supportsFaceDetection = isFaceSupported()
&& mFaceSensorProperties.get(0).supportsFaceDetection;
if (!isUnlockingWithBiometricAllowed(FACE)) {
final boolean udfpsFingerprintAuthRunning = isUdfpsSupported()
@@ -3166,21 +3176,15 @@
* @return {@code true} if possible.
*/
public boolean isUnlockingWithNonStrongBiometricsPossible(int userId) {
- // This assumes that there is at most one face and at most one fingerprint sensor
- return (mFaceManager != null && !mFaceSensorProperties.isEmpty()
- && (mFaceSensorProperties.get(0).sensorStrength != SensorProperties.STRENGTH_STRONG)
- && isUnlockWithFacePossible(userId))
- || (mFpm != null && !mFingerprintSensorProperties.isEmpty()
- && (mFingerprintSensorProperties.get(0).sensorStrength
- != SensorProperties.STRENGTH_STRONG) && isUnlockWithFingerprintPossible(userId));
+ return (!isFaceClass3() && isUnlockWithFacePossible(userId))
+ || (isFingerprintClass3() && isUnlockWithFingerprintPossible(userId));
}
@SuppressLint("MissingPermission")
@VisibleForTesting
boolean isUnlockWithFingerprintPossible(int userId) {
// TODO (b/242022358), make this rely on onEnrollmentChanged event and update it only once.
- boolean newFpEnrolled = mFpm != null
- && !mFingerprintSensorProperties.isEmpty()
+ boolean newFpEnrolled = isFingerprintSupported()
&& !isFingerprintDisabled(userId) && mFpm.hasEnrolledTemplates(userId);
Boolean oldFpEnrolled = mIsUnlockWithFingerprintPossible.getOrDefault(userId, false);
if (oldFpEnrolled != newFpEnrolled) {
@@ -3330,12 +3334,12 @@
// Immediately stop previous biometric listening states.
// Resetting lockout states updates the biometric listening states.
- if (mFaceManager != null && !mFaceSensorProperties.isEmpty()) {
+ if (isFaceSupported()) {
stopListeningForFace(FACE_AUTH_UPDATED_USER_SWITCHING);
handleFaceLockoutReset(mFaceManager.getLockoutModeForUser(
mFaceSensorProperties.get(0).sensorId, userId));
}
- if (mFpm != null && !mFingerprintSensorProperties.isEmpty()) {
+ if (isFingerprintSupported()) {
stopListeningForFingerprint();
handleFingerprintLockoutReset(mFpm.getLockoutModeForUser(
mFingerprintSensorProperties.get(0).sensorId, userId));
@@ -4071,6 +4075,22 @@
return BIOMETRIC_LOCKOUT_RESET_DELAY_MS;
}
+ @VisibleForTesting
+ protected boolean isFingerprintClass3() {
+ // This assumes that there is at most one fingerprint sensor property
+ return isFingerprintSupported() && isClass3Biometric(mFingerprintSensorProperties.get(0));
+ }
+
+ @VisibleForTesting
+ protected boolean isFaceClass3() {
+ // This assumes that there is at most one face sensor property
+ return isFaceSupported() && isClass3Biometric(mFaceSensorProperties.get(0));
+ }
+
+ private boolean isClass3Biometric(SensorPropertiesInternal sensorProperties) {
+ return sensorProperties.sensorStrength == SensorProperties.STRENGTH_STRONG;
+ }
+
/**
* Unregister all listeners.
*/
@@ -4122,11 +4142,12 @@
for (int subId : mServiceStates.keySet()) {
pw.println(" " + subId + "=" + mServiceStates.get(subId));
}
- if (mFpm != null && !mFingerprintSensorProperties.isEmpty()) {
+ if (isFingerprintSupported()) {
final int userId = mUserTracker.getUserId();
final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId);
BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(userId);
pw.println(" Fingerprint state (user=" + userId + ")");
+ pw.println(" isFingerprintClass3=" + isFingerprintClass3());
pw.println(" areAllFpAuthenticatorsRegistered="
+ mAuthController.areAllFingerprintAuthenticatorsRegistered());
pw.println(" allowed="
@@ -4184,11 +4205,12 @@
mFingerprintListenBuffer.toList()
).printTableData(pw);
}
- if (mFaceManager != null && !mFaceSensorProperties.isEmpty()) {
+ if (isFaceSupported()) {
final int userId = mUserTracker.getUserId();
final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId);
BiometricAuthenticated face = mUserFaceAuthenticated.get(userId);
pw.println(" Face authentication state (user=" + userId + ")");
+ pw.println(" isFaceClass3=" + isFaceClass3());
pw.println(" allowed="
+ (face != null && isUnlockingWithBiometricAllowed(face.mIsStrongBiometric)));
pw.println(" auth'd="
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 235a8bc..5f2afe8 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -294,6 +294,11 @@
final CharSequence prevContentDescription = mView.getContentDescription();
if (mShowLockIcon) {
+ if (wasShowingFpIcon) {
+ // fp icon was shown by UdfpsView, and now we still want to animate the transition
+ // in this drawable
+ mView.updateIcon(ICON_FINGERPRINT, false);
+ }
mView.updateIcon(ICON_LOCK, false);
mView.setContentDescription(mLockedLabel);
mView.setVisibility(View.VISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/ChooserPinMigration.kt b/packages/SystemUI/src/com/android/systemui/ChooserPinMigration.kt
deleted file mode 100644
index 2f03259..0000000
--- a/packages/SystemUI/src/com/android/systemui/ChooserPinMigration.kt
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui
-
-import android.content.ComponentName
-import android.content.Context
-import android.content.Context.MODE_PRIVATE
-import android.content.Intent
-import android.content.SharedPreferences
-import android.os.Bundle
-import android.os.Environment
-import android.os.storage.StorageManager
-import android.util.Log
-import androidx.core.util.Supplier
-import com.android.internal.R
-import com.android.systemui.broadcast.BroadcastSender
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import java.io.File
-import javax.inject.Inject
-
-/**
- * Performs a migration of pinned targets to the unbundled chooser if legacy data exists.
- *
- * Sends an explicit broadcast with the contents of the legacy pin preferences. The broadcast is
- * protected by the RECEIVE_CHOOSER_PIN_MIGRATION permission. This class requires the
- * ADD_CHOOSER_PINS permission in order to be able to send this broadcast.
- */
-class ChooserPinMigration
-@Inject
-constructor(
- private val context: Context,
- private val featureFlags: FeatureFlags,
- private val broadcastSender: BroadcastSender,
- legacyPinPrefsFileSupplier: LegacyPinPrefsFileSupplier,
-) : CoreStartable {
-
- private val legacyPinPrefsFile = legacyPinPrefsFileSupplier.get()
- private val chooserComponent =
- ComponentName.unflattenFromString(
- context.resources.getString(R.string.config_chooserActivity)
- )
-
- override fun start() {
- if (migrationIsRequired()) {
- doMigration()
- }
- }
-
- private fun migrationIsRequired(): Boolean {
- return featureFlags.isEnabled(Flags.CHOOSER_MIGRATION_ENABLED) &&
- legacyPinPrefsFile.exists() &&
- chooserComponent?.packageName != null
- }
-
- private fun doMigration() {
- Log.i(TAG, "Beginning migration")
-
- val legacyPinPrefs = context.getSharedPreferences(legacyPinPrefsFile, MODE_PRIVATE)
-
- if (legacyPinPrefs.all.isEmpty()) {
- Log.i(TAG, "No data to migrate, deleting legacy file")
- } else {
- sendSharedPreferences(legacyPinPrefs)
- Log.i(TAG, "Legacy data sent, deleting legacy preferences")
-
- val legacyPinPrefsEditor = legacyPinPrefs.edit()
- legacyPinPrefsEditor.clear()
- if (!legacyPinPrefsEditor.commit()) {
- Log.e(TAG, "Failed to delete legacy preferences")
- return
- }
- }
-
- if (!legacyPinPrefsFile.delete()) {
- Log.e(TAG, "Legacy preferences deleted, but failed to delete legacy preferences file")
- return
- }
-
- Log.i(TAG, "Legacy preference deletion complete")
- }
-
- private fun sendSharedPreferences(sharedPreferences: SharedPreferences) {
- val bundle = Bundle()
-
- sharedPreferences.all.entries.forEach { (key, value) ->
- when (value) {
- is Boolean -> bundle.putBoolean(key, value)
- else -> Log.e(TAG, "Unsupported preference type for $key: ${value?.javaClass}")
- }
- }
-
- sendBundle(bundle)
- }
-
- private fun sendBundle(bundle: Bundle) {
- val intent =
- Intent().apply {
- `package` = chooserComponent?.packageName!!
- action = BROADCAST_ACTION
- putExtras(bundle)
- }
- broadcastSender.sendBroadcast(intent, BROADCAST_PERMISSION)
- }
-
- companion object {
- private const val TAG = "PinnedShareTargetMigration"
- private const val BROADCAST_ACTION = "android.intent.action.CHOOSER_PIN_MIGRATION"
- private const val BROADCAST_PERMISSION = "android.permission.RECEIVE_CHOOSER_PIN_MIGRATION"
-
- class LegacyPinPrefsFileSupplier @Inject constructor(private val context: Context) :
- Supplier<File> {
-
- override fun get(): File {
- val packageDirectory =
- Environment.getDataUserCePackageDirectory(
- StorageManager.UUID_PRIVATE_INTERNAL,
- context.userId,
- context.packageName,
- )
- val sharedPrefsDirectory = File(packageDirectory, "shared_prefs")
- return File(sharedPrefsDirectory, "chooser_pin_settings.xml")
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
index 179eb39..a3e7d71 100644
--- a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
@@ -35,6 +35,7 @@
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.settingslib.Utils
import com.android.systemui.animation.Interpolators
+import com.android.systemui.biometrics.AuthController
import com.android.systemui.log.ScreenDecorationsLogger
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.util.asIndenting
@@ -52,6 +53,7 @@
val keyguardUpdateMonitor: KeyguardUpdateMonitor,
val mainExecutor: Executor,
val logger: ScreenDecorationsLogger,
+ val authController: AuthController,
) : ScreenDecorations.DisplayCutoutView(context, pos) {
private var showScanningAnim = false
private val rimPaint = Paint()
@@ -102,7 +104,9 @@
}
override fun enableShowProtection(show: Boolean) {
- val showScanningAnimNow = keyguardUpdateMonitor.isFaceDetectionRunning && show
+ val animationRequired =
+ keyguardUpdateMonitor.isFaceDetectionRunning || authController.isShowing
+ val showScanningAnimNow = animationRequired && show
if (showScanningAnimNow == showScanningAnim) {
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 92344db..0999229 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -1005,9 +1005,11 @@
* not enrolled sfps. This may be false if called before onAllAuthenticatorsRegistered.
*/
public boolean isRearFpsSupported() {
- for (FingerprintSensorPropertiesInternal prop: mFpProps) {
- if (prop.sensorType == TYPE_REAR) {
- return true;
+ if (mFpProps != null) {
+ for (FingerprintSensorPropertiesInternal prop: mFpProps) {
+ if (prop.sensorType == TYPE_REAR) {
+ return true;
+ }
}
}
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/MotionEventExt.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/MotionEventExt.kt
index 26fc36d..81ed076 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/MotionEventExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/MotionEventExt.kt
@@ -19,10 +19,18 @@
import android.util.MathUtils
import android.view.MotionEvent
-/** Returns the distance from the position of this [MotionEvent] and the given coordinates. */
-fun MotionEvent.distanceFrom(
- x: Float,
- y: Float,
+/**
+ * Returns the distance from the raw position of this [MotionEvent] and the given coordinates.
+ * Because this is all expected to be in the coordinate space of the display and not the view,
+ * applying mutations to the view (such as scaling animations) does not affect the distance
+ * measured.
+ * @param xOnDisplay the x coordinate relative to the display
+ * @param yOnDisplay the y coordinate relative to the display
+ * @return distance from the raw position of this [MotionEvent] and the given coordinates
+ */
+fun MotionEvent.rawDistanceFrom(
+ xOnDisplay: Float,
+ yOnDisplay: Float,
): Float {
- return MathUtils.dist(this.x, this.y, x, y)
+ return MathUtils.dist(this.rawX, this.rawY, xOnDisplay, yOnDisplay)
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index df236e7..9bf6b2a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -17,7 +17,6 @@
package com.android.systemui.dagger
import com.android.keyguard.KeyguardBiometricLockoutLogger
-import com.android.systemui.ChooserPinMigration
import com.android.systemui.ChooserSelector
import com.android.systemui.CoreStartable
import com.android.systemui.LatencyTester
@@ -76,13 +75,6 @@
@ClassKey(AuthController::class)
abstract fun bindAuthController(service: AuthController): CoreStartable
- /** Inject into ChooserPinMigration. */
- @Binds
- @IntoMap
- @ClassKey(ChooserPinMigration::class)
- @PerUser
- abstract fun bindChooserPinMigration(sysui: ChooserPinMigration): CoreStartable
-
/** Inject into ChooserCoreStartable. */
@Binds
@IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
index 88c0c50..4e62104 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
@@ -98,7 +98,8 @@
}
fun shouldShowFaceScanningAnim(): Boolean {
- return canShowFaceScanningAnim() && keyguardUpdateMonitor.isFaceDetectionRunning
+ return canShowFaceScanningAnim() &&
+ (keyguardUpdateMonitor.isFaceDetectionRunning || authController.isShowing)
}
}
@@ -142,6 +143,7 @@
keyguardUpdateMonitor,
mainExecutor,
logger,
+ authController,
)
view.id = viewId
view.setColor(tintColor)
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index b44499f..012c8cf 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -65,7 +65,7 @@
val FSI_ON_DND_UPDATE = releasedFlag(259130119, "fsi_on_dnd_update")
// TODO(b/254512538): Tracking Bug
- val INSTANT_VOICE_REPLY = releasedFlag(111, "instant_voice_reply")
+ val INSTANT_VOICE_REPLY = unreleasedFlag(111, "instant_voice_reply")
// TODO(b/254512425): Tracking Bug
val NOTIFICATION_MEMORY_MONITOR_ENABLED =
@@ -103,6 +103,11 @@
val FILTER_UNSEEN_NOTIFS_ON_KEYGUARD =
releasedFlag(254647461, "filter_unseen_notifs_on_keyguard")
+ // TODO(b/277338665): Tracking Bug
+ @JvmField
+ val NOTIFICATION_SHELF_REFACTOR =
+ unreleasedFlag(271161129, "notification_shelf_refactor")
+
// TODO(b/263414400): Tracking Bug
@JvmField
val NOTIFICATION_ANIMATE_BIG_PICTURE =
@@ -406,7 +411,7 @@
val MEDIA_RETAIN_RECOMMENDATIONS = releasedFlag(916, "media_retain_recommendations")
// TODO(b/270437894): Tracking Bug
- val MEDIA_REMOTE_RESUME = unreleasedFlag(917, "media_remote_resume")
+ val MEDIA_REMOTE_RESUME = unreleasedFlag(917, "media_remote_resume", teamfood = true)
// 1000 - dock
val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging")
@@ -414,9 +419,6 @@
// TODO(b/254512758): Tracking Bug
@JvmField val ROUNDED_BOX_RIPPLE = releasedFlag(1002, "rounded_box_ripple")
- // TODO(b/270882464): Tracking Bug
- val ENABLE_DOCK_SETUP_V2 = releasedFlag(1005, "enable_dock_setup_v2")
-
// TODO(b/265045965): Tracking Bug
val SHOW_LOWLIGHT_ON_DIRECT_BOOT = releasedFlag(1003, "show_lowlight_on_direct_boot")
@@ -616,9 +618,6 @@
val SHARESHEET_SCROLLABLE_IMAGE_PREVIEW =
releasedFlag(1504, "sharesheet_scrollable_image_preview")
- // TODO(b/274137694) Tracking Bug
- val CHOOSER_MIGRATION_ENABLED = unreleasedFlag(1505, "chooser_migration_enabled")
-
// 1700 - clipboard
@JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag(1701, "clipboard_remote_behavior")
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 56e7398..0abce82 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
@@ -92,6 +92,9 @@
/** Current state of whether face authentication is running. */
val isAuthRunning: Flow<Boolean>
+ /** Whether bypass is currently enabled */
+ val isBypassEnabled: Flow<Boolean>
+
/**
* Trigger face authentication.
*
@@ -166,7 +169,7 @@
override val isAuthenticated: Flow<Boolean>
get() = _isAuthenticated
- private val bypassEnabled: Flow<Boolean> =
+ override val isBypassEnabled: Flow<Boolean> =
keyguardBypassController?.let {
conflatedCallbackFlow {
val callback =
@@ -222,7 +225,7 @@
// & detection is supported & biometric unlock is not allowed.
listOf(
canFaceAuthOrDetectRun(),
- logAndObserve(bypassEnabled, "bypassEnabled"),
+ logAndObserve(isBypassEnabled, "isBypassEnabled"),
logAndObserve(
biometricSettingsRepository.isNonStrongBiometricAllowed.isFalse(),
"nonStrongBiometricIsNotAllowed"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt
index 779095c..5745d6a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt
@@ -26,7 +26,7 @@
import androidx.core.animation.ObjectAnimator
import com.android.systemui.R
import com.android.systemui.animation.Expandable
-import com.android.systemui.common.ui.view.distanceFrom
+import com.android.systemui.common.ui.view.rawDistanceFrom
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.VibratorHelper
@@ -41,14 +41,14 @@
private val longPressDurationMs = ViewConfiguration.getLongPressTimeout().toLong()
private var longPressAnimator: ViewPropertyAnimator? = null
- private val down: PointF by lazy { PointF() }
+ private val downDisplayCoords: PointF by lazy { PointF() }
@SuppressLint("ClickableViewAccessibility")
override fun onTouch(v: View, event: MotionEvent): Boolean {
return when (event.actionMasked) {
MotionEvent.ACTION_DOWN ->
if (viewModel.configKey != null) {
- down.set(event.x, event.y)
+ downDisplayCoords.set(event.rawX, event.rawY)
if (isUsingAccurateTool(event)) {
// For accurate tool types (stylus, mouse, etc.), we don't require a
// long-press.
@@ -81,7 +81,13 @@
if (!isUsingAccurateTool(event)) {
// Moving too far while performing a long-press gesture cancels that
// gesture.
- if (event.distanceFrom(down.x, down.y) > ViewConfiguration.getTouchSlop()) {
+ if (
+ event
+ .rawDistanceFrom(
+ downDisplayCoords.x,
+ downDisplayCoords.y,
+ ) > ViewConfiguration.getTouchSlop()
+ ) {
cancel()
}
}
@@ -94,7 +100,7 @@
// the pointer performs a click.
if (
viewModel.configKey != null &&
- event.distanceFrom(down.x, down.y) <=
+ event.rawDistanceFrom(downDisplayCoords.x, downDisplayCoords.y) <=
ViewConfiguration.getTouchSlop() &&
falsingManager?.isFalseTap(FalsingManager.NO_PENALTY) == false
) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt
index ad3fb63..c54203c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt
@@ -21,7 +21,7 @@
import android.view.View
import android.view.ViewConfiguration
import com.android.systemui.animation.view.LaunchableLinearLayout
-import com.android.systemui.common.ui.view.distanceFrom
+import com.android.systemui.common.ui.view.rawDistanceFrom
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsMenuViewModel
class KeyguardSettingsButtonOnTouchListener(
@@ -29,18 +29,20 @@
private val viewModel: KeyguardSettingsMenuViewModel,
) : View.OnTouchListener {
- private val downPosition = PointF()
+ private val downPositionDisplayCoords = PointF()
override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
when (motionEvent.actionMasked) {
MotionEvent.ACTION_DOWN -> {
view.isPressed = true
- downPosition.set(motionEvent.x, motionEvent.y)
+ downPositionDisplayCoords.set(motionEvent.rawX, motionEvent.rawY)
viewModel.onTouchGestureStarted()
}
MotionEvent.ACTION_UP -> {
view.isPressed = false
- val distanceMoved = motionEvent.distanceFrom(downPosition.x, downPosition.y)
+ val distanceMoved =
+ motionEvent
+ .rawDistanceFrom(downPositionDisplayCoords.x, downPositionDisplayCoords.y)
val isClick = distanceMoved < ViewConfiguration.getTouchSlop()
viewModel.onTouchGestureEnded(isClick)
if (isClick) {
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index c2c1306..a765702 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -18,6 +18,10 @@
import static android.app.PendingIntent.FLAG_IMMUTABLE;
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_CONFIRMATION;
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_LOW_WARNING;
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SaverManualEnabledReason;
+
import android.app.Dialog;
import android.app.KeyguardManager;
import android.app.Notification;
@@ -691,7 +695,7 @@
d.setTitle(R.string.battery_saver_confirmation_title);
d.setPositiveButton(R.string.battery_saver_confirmation_ok,
(dialog, which) -> {
- setSaverMode(true, false);
+ setSaverMode(true, false, SAVER_ENABLED_CONFIRMATION);
logEvent(BatteryWarningEvents.LowBatteryWarningEvent.SAVER_CONFIRM_OK);
});
d.setNegativeButton(android.R.string.cancel, (dialog, which) ->
@@ -790,8 +794,9 @@
return builder;
}
- private void setSaverMode(boolean mode, boolean needFirstTimeWarning) {
- BatterySaverUtils.setPowerSaveMode(mContext, mode, needFirstTimeWarning);
+ private void setSaverMode(boolean mode, boolean needFirstTimeWarning,
+ @SaverManualEnabledReason int reason) {
+ BatterySaverUtils.setPowerSaveMode(mContext, mode, needFirstTimeWarning, reason);
}
private void startBatterySaverSchedulePage() {
@@ -839,7 +844,7 @@
} else if (action.equals(ACTION_START_SAVER)) {
logEvent(BatteryWarningEvents
.LowBatteryWarningEvent.LOW_BATTERY_NOTIFICATION_TURN_ON);
- setSaverMode(true, true);
+ setSaverMode(true, true, SAVER_ENABLED_LOW_WARNING);
dismissLowBatteryNotification();
} else if (action.equals(ACTION_SHOW_START_SAVER_CONFIRMATION)) {
dismissLowBatteryNotification();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
index ce690e2..57b479e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
@@ -26,8 +26,6 @@
import com.android.systemui.plugins.qs.QSFactory;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTileView;
-import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository;
-import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
import com.android.systemui.util.leak.GarbageMonitor;
import java.util.ArrayList;
@@ -35,7 +33,7 @@
import java.util.Collection;
import java.util.List;
-public interface QSHost extends PanelInteractor, CustomTileAddedRepository {
+public interface QSHost {
String TILES_SETTING = Settings.Secure.QS_TILES;
int POSITION_AT_END = -1;
@@ -75,7 +73,11 @@
* @see QSFactory#createTileView
*/
QSTileView createTileView(Context themedContext, QSTile tile, boolean collapsedView);
- /** Create a {@link QSTile} of a {@code tileSpec} type. */
+ /** Create a {@link QSTile} of a {@code tileSpec} type.
+ *
+ * This should only be called by classes that need to create one-off instances of tiles.
+ * Do not use to create {@code custom} tiles without explicitly taking care of its lifecycle.
+ */
QSTile createTile(String tileSpec);
/**
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt
new file mode 100644
index 0000000..14acb4b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs
+
+import android.content.ComponentName
+import android.content.Context
+import androidx.annotation.GuardedBy
+import com.android.internal.logging.InstanceId
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.plugins.qs.QSTileView
+import com.android.systemui.qs.external.TileServiceRequestController
+import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository.Companion.POSITION_AT_END
+import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+
+/**
+ * Adapter to determine what real class to use for classes that depend on [QSHost].
+ *
+ * * When [Flags.QS_PIPELINE_NEW_HOST] is off, all calls will be routed to [QSTileHost].
+ * * When [Flags.QS_PIPELINE_NEW_HOST] is on, calls regarding the current set of tiles will be
+ * routed to [CurrentTilesInteractor]. Other calls (like [warn]) will still be routed to
+ * [QSTileHost].
+ *
+ * This routing also includes dumps.
+ */
+@SysUISingleton
+class QSHostAdapter
+@Inject
+constructor(
+ private val qsTileHost: QSTileHost,
+ private val interactor: CurrentTilesInteractor,
+ private val context: Context,
+ private val tileServiceRequestControllerBuilder: TileServiceRequestController.Builder,
+ @Application private val scope: CoroutineScope,
+ private val featureFlags: FeatureFlags,
+ dumpManager: DumpManager,
+) : QSHost {
+
+ companion object {
+ private const val TAG = "QSTileHost"
+ }
+
+ private val useNewHost = featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)
+
+ @GuardedBy("callbacksMap") private val callbacksMap = mutableMapOf<QSHost.Callback, Job>()
+
+ init {
+ scope.launch { tileServiceRequestControllerBuilder.create(this@QSHostAdapter).init() }
+ // Redirect dump to the correct host (needed for CTS tests)
+ dumpManager.registerCriticalDumpable(
+ TAG,
+ if (useNewHost) interactor else qsTileHost
+ )
+ }
+
+ override fun getTiles(): Collection<QSTile> {
+ return if (useNewHost) {
+ interactor.currentQSTiles
+ } else {
+ qsTileHost.getTiles()
+ }
+ }
+
+ override fun getSpecs(): List<String> {
+ return if (useNewHost) {
+ interactor.currentTilesSpecs.map { it.spec }
+ } else {
+ qsTileHost.getSpecs()
+ }
+ }
+
+ override fun removeTile(spec: String) {
+ if (useNewHost) {
+ interactor.removeTiles(listOf(TileSpec.create(spec)))
+ } else {
+ qsTileHost.removeTile(spec)
+ }
+ }
+
+ override fun addCallback(callback: QSHost.Callback) {
+ if (useNewHost) {
+ val job =
+ scope.launch {
+ interactor.currentTiles.collect { callback.onTilesChanged() }
+ }
+ synchronized(callbacksMap) { callbacksMap.put(callback, job) }
+ } else {
+ qsTileHost.addCallback(callback)
+ }
+ }
+
+ override fun removeCallback(callback: QSHost.Callback) {
+ if (useNewHost) {
+ synchronized(callbacksMap) { callbacksMap.get(callback)?.cancel() }
+ } else {
+ qsTileHost.removeCallback(callback)
+ }
+ }
+
+ override fun removeTiles(specs: Collection<String>) {
+ if (useNewHost) {
+ interactor.removeTiles(specs.map(TileSpec::create))
+ } else {
+ qsTileHost.removeTiles(specs)
+ }
+ }
+
+ override fun removeTileByUser(component: ComponentName) {
+ if (useNewHost) {
+ interactor.removeTiles(listOf(TileSpec.create(component)))
+ } else {
+ qsTileHost.removeTileByUser(component)
+ }
+ }
+
+ override fun addTile(spec: String, position: Int) {
+ if (useNewHost) {
+ interactor.addTile(TileSpec.create(spec), position)
+ } else {
+ qsTileHost.addTile(spec, position)
+ }
+ }
+
+ override fun addTile(component: ComponentName, end: Boolean) {
+ if (useNewHost) {
+ interactor.addTile(
+ TileSpec.create(component),
+ if (end) POSITION_AT_END else 0
+ )
+ } else {
+ qsTileHost.addTile(component, end)
+ }
+ }
+
+ override fun changeTilesByUser(previousTiles: List<String>, newTiles: List<String>) {
+ if (useNewHost) {
+ interactor.setTiles(newTiles.map(TileSpec::create))
+ } else {
+ qsTileHost.changeTilesByUser(previousTiles, newTiles)
+ }
+ }
+
+ override fun warn(message: String?, t: Throwable?) {
+ qsTileHost.warn(message, t)
+ }
+
+ override fun getContext(): Context {
+ return if (useNewHost) {
+ context
+ } else {
+ qsTileHost.context
+ }
+ }
+
+ override fun getUserContext(): Context {
+ return if (useNewHost) {
+ interactor.userContext.value
+ } else {
+ qsTileHost.userContext
+ }
+ }
+
+ override fun getUserId(): Int {
+ return if (useNewHost) {
+ interactor.userId.value
+ } else {
+ qsTileHost.userId
+ }
+ }
+
+ override fun getUiEventLogger(): UiEventLogger {
+ return qsTileHost.uiEventLogger
+ }
+
+ override fun createTileView(
+ themedContext: Context?,
+ tile: QSTile?,
+ collapsedView: Boolean
+ ): QSTileView {
+ return qsTileHost.createTileView(themedContext, tile, collapsedView)
+ }
+
+ override fun createTile(tileSpec: String): QSTile? {
+ return qsTileHost.createTile(tileSpec)
+ }
+
+ override fun addTile(spec: String) {
+ return addTile(spec, QSHost.POSITION_AT_END)
+ }
+
+ override fun addTile(tile: ComponentName) {
+ return addTile(tile, false)
+ }
+
+ override fun indexOf(tileSpec: String): Int {
+ return specs.indexOf(tileSpec)
+ }
+
+ override fun getNewInstanceId(): InstanceId {
+ return qsTileHost.newInstanceId
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 8bbdeed..0ca8973 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -37,8 +37,9 @@
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dump.DumpManager;
import com.android.systemui.dump.nano.SystemUIProtoDump;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.qs.QSFactory;
@@ -48,9 +49,10 @@
import com.android.systemui.qs.external.CustomTileStatePersister;
import com.android.systemui.qs.external.TileLifecycleManager;
import com.android.systemui.qs.external.TileServiceKey;
-import com.android.systemui.qs.external.TileServiceRequestController;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.nano.QsTileState;
+import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository;
+import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
import com.android.systemui.settings.UserFileManager;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.AutoTileManager;
@@ -85,7 +87,8 @@
* This class also provides the interface for adding/removing/changing tiles.
*/
@SysUISingleton
-public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, ProtoDumpable {
+public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, ProtoDumpable,
+ PanelInteractor, CustomTileAddedRepository {
private static final String TAG = "QSTileHost";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final int MAX_QS_INSTANCE_ID = 1 << 20;
@@ -99,7 +102,6 @@
private final ArrayList<String> mTileSpecs = new ArrayList<>();
private final TunerService mTunerService;
private final PluginManager mPluginManager;
- private final DumpManager mDumpManager;
private final QSLogger mQSLogger;
private final UiEventLogger mUiEventLogger;
private final InstanceIdSequence mInstanceIdSequence;
@@ -122,9 +124,10 @@
// This is enforced by only cleaning the flag at the end of a successful run of #onTuningChanged
private boolean mTilesListDirty = true;
- private final TileServiceRequestController mTileServiceRequestController;
private TileLifecycleManager.Factory mTileLifeCycleManagerFactory;
+ private final FeatureFlags mFeatureFlags;
+
@Inject
public QSTileHost(Context context,
QSFactory defaultFactory,
@@ -132,35 +135,32 @@
PluginManager pluginManager,
TunerService tunerService,
Provider<AutoTileManager> autoTiles,
- DumpManager dumpManager,
Optional<CentralSurfaces> centralSurfacesOptional,
QSLogger qsLogger,
UiEventLogger uiEventLogger,
UserTracker userTracker,
SecureSettings secureSettings,
CustomTileStatePersister customTileStatePersister,
- TileServiceRequestController.Builder tileServiceRequestControllerBuilder,
TileLifecycleManager.Factory tileLifecycleManagerFactory,
- UserFileManager userFileManager
+ UserFileManager userFileManager,
+ FeatureFlags featureFlags
) {
mContext = context;
mUserContext = context;
mTunerService = tunerService;
mPluginManager = pluginManager;
- mDumpManager = dumpManager;
mQSLogger = qsLogger;
mUiEventLogger = uiEventLogger;
mMainExecutor = mainExecutor;
- mTileServiceRequestController = tileServiceRequestControllerBuilder.create(this);
mTileLifeCycleManagerFactory = tileLifecycleManagerFactory;
mUserFileManager = userFileManager;
+ mFeatureFlags = featureFlags;
mInstanceIdSequence = new InstanceIdSequence(MAX_QS_INSTANCE_ID);
mCentralSurfacesOptional = centralSurfacesOptional;
mQsFactories.add(defaultFactory);
pluginManager.addPluginListener(this, QSFactory.class, true);
- mDumpManager.registerDumpable(TAG, this);
mUserTracker = userTracker;
mSecureSettings = secureSettings;
mCustomTileStatePersister = customTileStatePersister;
@@ -172,7 +172,6 @@
tunerService.addTunable(this, TILES_SETTING);
// AutoTileManager can modify mTiles so make sure mTiles has already been initialized.
mAutoTiles = autoTiles.get();
- mTileServiceRequestController.init();
});
}
@@ -186,8 +185,6 @@
mAutoTiles.destroy();
mTunerService.removeTunable(this);
mPluginManager.removePluginListener(this);
- mDumpManager.unregisterDumpable(TAG);
- mTileServiceRequestController.destroy();
}
@Override
@@ -300,6 +297,10 @@
if (!TILES_SETTING.equals(key)) {
return;
}
+ // Do not process tiles if the flag is enabled.
+ if (mFeatureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) {
+ return;
+ }
if (newValue == null && UserManager.isDeviceInDemoMode(mContext)) {
newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt
index 964fe71..3ddd9f1c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt
@@ -19,6 +19,7 @@
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QSHostAdapter
import com.android.systemui.qs.QSTileHost
import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository
import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedSharedPrefsRepository
@@ -31,7 +32,7 @@
@Module
interface QSHostModule {
- @Binds fun provideQsHost(controllerImpl: QSTileHost): QSHost
+ @Binds fun provideQsHost(controllerImpl: QSHostAdapter): QSHost
@Module
companion object {
@@ -39,7 +40,7 @@
@JvmStatic
fun providePanelInteractor(
featureFlags: FeatureFlags,
- qsHost: QSHost,
+ qsHost: QSTileHost,
panelInteractorImpl: PanelInteractorImpl
): PanelInteractor {
return if (featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) {
@@ -53,7 +54,7 @@
@JvmStatic
fun provideCustomTileAddedRepository(
featureFlags: FeatureFlags,
- qsHost: QSHost,
+ qsHost: QSTileHost,
customTileAddedRepository: CustomTileAddedSharedPrefsRepository
): CustomTileAddedRepository {
return if (featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
index 00f0a67..e212bc4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
@@ -22,6 +22,8 @@
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
import com.android.systemui.qs.pipeline.data.repository.TileSpecSettingsRepository
+import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
+import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractorImpl
import com.android.systemui.qs.pipeline.prototyping.PrototypeCoreStartable
import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
import dagger.Binds
@@ -38,6 +40,11 @@
abstract fun provideTileSpecRepository(impl: TileSpecSettingsRepository): TileSpecRepository
@Binds
+ abstract fun bindCurrentTilesInteractor(
+ impl: CurrentTilesInteractorImpl
+ ): CurrentTilesInteractor
+
+ @Binds
@IntoMap
@ClassKey(PrototypeCoreStartable::class)
abstract fun providePrototypeCoreStartable(startable: PrototypeCoreStartable): CoreStartable
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
index d254e1b..595b29a9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
@@ -32,6 +32,7 @@
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
@@ -53,6 +54,8 @@
* at the end of the list.
*
* Passing [TileSpec.Invalid] is a noop.
+ *
+ * Trying to add a tile beyond the end of the list will add it at the end.
*/
suspend fun addTile(@UserIdInt userId: Int, tile: TileSpec, position: Int = POSITION_AT_END)
@@ -61,7 +64,7 @@
*
* Passing [TileSpec.Invalid] or a non present tile is a noop.
*/
- suspend fun removeTile(@UserIdInt userId: Int, tile: TileSpec)
+ suspend fun removeTiles(@UserIdInt userId: Int, tiles: Collection<TileSpec>)
/**
* Sets the list of current [tiles] for a given [userId].
@@ -106,6 +109,7 @@
}
.onStart { emit(Unit) }
.map { secureSettings.getStringForUser(SETTING, userId) ?: "" }
+ .distinctUntilChanged()
.onEach { logger.logTilesChangedInSettings(it, userId) }
.map { parseTileSpecs(it, userId) }
.flowOn(backgroundDispatcher)
@@ -117,7 +121,7 @@
}
val tilesList = loadTiles(userId).toMutableList()
if (tile !in tilesList) {
- if (position < 0) {
+ if (position < 0 || position >= tilesList.size) {
tilesList.add(tile)
} else {
tilesList.add(position, tile)
@@ -126,12 +130,12 @@
}
}
- override suspend fun removeTile(userId: Int, tile: TileSpec) {
- if (tile == TileSpec.Invalid) {
+ override suspend fun removeTiles(userId: Int, tiles: Collection<TileSpec>) {
+ if (tiles.all { it == TileSpec.Invalid }) {
return
}
val tilesList = loadTiles(userId).toMutableList()
- if (tilesList.remove(tile)) {
+ if (tilesList.removeAll(tiles)) {
storeTiles(userId, tilesList.toList())
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
new file mode 100644
index 0000000..91c6e8b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.interactor
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.os.UserHandle
+import com.android.systemui.Dumpable
+import com.android.systemui.ProtoDumpable
+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.dump.nano.SystemUIProtoDump
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.plugins.qs.QSFactory
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.external.CustomTile
+import com.android.systemui.qs.external.CustomTileStatePersister
+import com.android.systemui.qs.external.TileLifecycleManager
+import com.android.systemui.qs.external.TileServiceKey
+import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository
+import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
+import com.android.systemui.qs.pipeline.domain.model.TileModel
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.qs.toProto
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.kotlin.pairwise
+import java.io.PrintWriter
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+/**
+ * Interactor for retrieving the list of current QS tiles, as well as making changes to this list
+ *
+ * It is [ProtoDumpable] as it needs to be able to dump state for CTS tests.
+ */
+interface CurrentTilesInteractor : ProtoDumpable {
+ /** Current list of tiles with their corresponding spec. */
+ val currentTiles: StateFlow<List<TileModel>>
+
+ /** User for the [currentTiles]. */
+ val userId: StateFlow<Int>
+
+ /** [Context] corresponding to [userId] */
+ val userContext: StateFlow<Context>
+
+ /** List of specs corresponding to the last value of [currentTiles] */
+ val currentTilesSpecs: List<TileSpec>
+ get() = currentTiles.value.map(TileModel::spec)
+
+ /** List of tiles corresponding to the last value of [currentTiles] */
+ val currentQSTiles: List<QSTile>
+ get() = currentTiles.value.map(TileModel::tile)
+
+ /**
+ * Requests that a tile be added in the list of tiles for the current user.
+ *
+ * @see TileSpecRepository.addTile
+ */
+ fun addTile(spec: TileSpec, position: Int = TileSpecRepository.POSITION_AT_END)
+
+ /**
+ * Requests that tiles be removed from the list of tiles for the current user
+ *
+ * If tiles with [TileSpec.CustomTileSpec] are removed, their lifecycle will be terminated and
+ * marked as removed.
+ *
+ * @see TileSpecRepository.removeTiles
+ */
+ fun removeTiles(specs: Collection<TileSpec>)
+
+ /**
+ * Requests that the list of tiles for the current user is changed to [specs].
+ *
+ * If tiles with [TileSpec.CustomTileSpec] are removed, their lifecycle will be terminated and
+ * marked as removed.
+ *
+ * @see TileSpecRepository.setTiles
+ */
+ fun setTiles(specs: List<TileSpec>)
+}
+
+/**
+ * This implementation of [CurrentTilesInteractor] will try to re-use existing [QSTile] objects when
+ * possible, in particular:
+ * * It will only destroy tiles when they are not part of the list of tiles anymore
+ * * Platform tiles will be kept between users, with a call to [QSTile.userSwitch]
+ * * [CustomTile]s will only be destroyed if the user changes.
+ */
+@SysUISingleton
+class CurrentTilesInteractorImpl
+@Inject
+constructor(
+ private val tileSpecRepository: TileSpecRepository,
+ private val userRepository: UserRepository,
+ private val customTileStatePersister: CustomTileStatePersister,
+ private val tileFactory: QSFactory,
+ private val customTileAddedRepository: CustomTileAddedRepository,
+ private val tileLifecycleManagerFactory: TileLifecycleManager.Factory,
+ private val userTracker: UserTracker,
+ @Main private val mainDispatcher: CoroutineDispatcher,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ @Application private val scope: CoroutineScope,
+ private val logger: QSPipelineLogger,
+ featureFlags: FeatureFlags,
+) : CurrentTilesInteractor {
+
+ private val _currentSpecsAndTiles: MutableStateFlow<List<TileModel>> =
+ MutableStateFlow(emptyList())
+
+ override val currentTiles: StateFlow<List<TileModel>> = _currentSpecsAndTiles.asStateFlow()
+
+ // This variable should only be accessed inside the collect of `startTileCollection`.
+ private val specsToTiles = mutableMapOf<TileSpec, QSTile>()
+
+ private val currentUser = MutableStateFlow(userTracker.userId)
+ override val userId = currentUser.asStateFlow()
+
+ private val _userContext = MutableStateFlow(userTracker.userContext)
+ override val userContext = _userContext.asStateFlow()
+
+ init {
+ if (featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) {
+ startTileCollection()
+ }
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ private fun startTileCollection() {
+ scope.launch {
+ userRepository.selectedUserInfo
+ .flatMapLatest { user ->
+ currentUser.value = user.id
+ _userContext.value = userTracker.userContext
+ tileSpecRepository.tilesSpecs(user.id).map { user.id to it }
+ }
+ .distinctUntilChanged()
+ .pairwise(-1 to emptyList())
+ .flowOn(backgroundDispatcher)
+ .collect { (old, new) ->
+ val newTileList = new.second
+ val userChanged = old.first != new.first
+ val newUser = new.first
+
+ // Destroy all tiles that are not in the new set
+ specsToTiles
+ .filter { it.key !in newTileList }
+ .forEach { entry ->
+ logger.logTileDestroyed(
+ entry.key,
+ if (userChanged) {
+ QSPipelineLogger.TileDestroyedReason
+ .TILE_NOT_PRESENT_IN_NEW_USER
+ } else {
+ QSPipelineLogger.TileDestroyedReason.TILE_REMOVED
+ }
+ )
+ entry.value.destroy()
+ }
+ // MutableMap will keep the insertion order
+ val newTileMap = mutableMapOf<TileSpec, QSTile>()
+
+ newTileList.forEach { tileSpec ->
+ if (tileSpec !in newTileMap) {
+ val newTile =
+ if (tileSpec in specsToTiles) {
+ processExistingTile(
+ tileSpec,
+ specsToTiles.getValue(tileSpec),
+ userChanged,
+ newUser
+ )
+ ?: createTile(tileSpec)
+ } else {
+ createTile(tileSpec)
+ }
+ if (newTile != null) {
+ newTileMap[tileSpec] = newTile
+ }
+ }
+ }
+
+ val resolvedSpecs = newTileMap.keys.toList()
+ specsToTiles.clear()
+ specsToTiles.putAll(newTileMap)
+ _currentSpecsAndTiles.value = newTileMap.map { TileModel(it.key, it.value) }
+ if (resolvedSpecs != newTileList) {
+ // There were some tiles that couldn't be created. Change the value in the
+ // repository
+ launch { tileSpecRepository.setTiles(currentUser.value, resolvedSpecs) }
+ }
+ }
+ }
+ }
+
+ override fun addTile(spec: TileSpec, position: Int) {
+ scope.launch {
+ tileSpecRepository.addTile(userRepository.getSelectedUserInfo().id, spec, position)
+ }
+ }
+
+ override fun removeTiles(specs: Collection<TileSpec>) {
+ val currentSpecsCopy = currentTilesSpecs.toSet()
+ val user = currentUser.value
+ // intersect: tiles that are there and are being removed
+ val toFree = currentSpecsCopy.intersect(specs).filterIsInstance<TileSpec.CustomTileSpec>()
+ toFree.forEach { onCustomTileRemoved(it.componentName, user) }
+ if (currentSpecsCopy.intersect(specs).isNotEmpty()) {
+ // We don't want to do the call to set in case getCurrentTileSpecs is not the most
+ // up to date for this user.
+ scope.launch { tileSpecRepository.removeTiles(user, specs) }
+ }
+ }
+
+ override fun setTiles(specs: List<TileSpec>) {
+ val currentSpecsCopy = currentTilesSpecs
+ val user = currentUser.value
+ if (currentSpecsCopy != specs) {
+ // minus: tiles that were there but are not there anymore
+ val toFree = currentSpecsCopy.minus(specs).filterIsInstance<TileSpec.CustomTileSpec>()
+ toFree.forEach { onCustomTileRemoved(it.componentName, user) }
+ scope.launch { tileSpecRepository.setTiles(user, specs) }
+ }
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("CurrentTileInteractorImpl:")
+ pw.println("User: ${userId.value}")
+ currentTiles.value
+ .map { it.tile }
+ .filterIsInstance<Dumpable>()
+ .forEach { it.dump(pw, args) }
+ }
+
+ override fun dumpProto(systemUIProtoDump: SystemUIProtoDump, args: Array<String>) {
+ val data =
+ currentTiles.value.map { it.tile.state }.mapNotNull { it.toProto() }.toTypedArray()
+ systemUIProtoDump.tiles = data
+ }
+
+ private fun onCustomTileRemoved(componentName: ComponentName, userId: Int) {
+ val intent = Intent().setComponent(componentName)
+ val lifecycleManager = tileLifecycleManagerFactory.create(intent, UserHandle.of(userId))
+ lifecycleManager.onStopListening()
+ lifecycleManager.onTileRemoved()
+ customTileStatePersister.removeState(TileServiceKey(componentName, userId))
+ customTileAddedRepository.setTileAdded(componentName, userId, false)
+ lifecycleManager.flushMessagesAndUnbind()
+ }
+
+ private suspend fun createTile(spec: TileSpec): QSTile? {
+ val tile = withContext(mainDispatcher) { tileFactory.createTile(spec.spec) }
+ if (tile == null) {
+ logger.logTileNotFoundInFactory(spec)
+ return null
+ } else {
+ tile.tileSpec = spec.spec
+ return if (!tile.isAvailable) {
+ logger.logTileDestroyed(
+ spec,
+ QSPipelineLogger.TileDestroyedReason.NEW_TILE_NOT_AVAILABLE,
+ )
+ tile.destroy()
+ null
+ } else {
+ logger.logTileCreated(spec)
+ tile
+ }
+ }
+ }
+
+ private fun processExistingTile(
+ tileSpec: TileSpec,
+ qsTile: QSTile,
+ userChanged: Boolean,
+ user: Int,
+ ): QSTile? {
+ return when {
+ !qsTile.isAvailable -> {
+ logger.logTileDestroyed(
+ tileSpec,
+ QSPipelineLogger.TileDestroyedReason.EXISTING_TILE_NOT_AVAILABLE
+ )
+ qsTile.destroy()
+ null
+ }
+ // Tile is in the current list of tiles and available.
+ // We have a handful of different cases
+ qsTile !is CustomTile -> {
+ // The tile is not a custom tile. Make sure they are reset to the correct user
+ qsTile.removeCallbacks()
+ if (userChanged) {
+ qsTile.userSwitch(user)
+ logger.logTileUserChanged(tileSpec, user)
+ }
+ qsTile
+ }
+ qsTile.user == user -> {
+ // The tile is a custom tile for the same user, just return it
+ qsTile.removeCallbacks()
+ qsTile
+ }
+ else -> {
+ // The tile is a custom tile and the user has changed. Destroy it
+ qsTile.destroy()
+ logger.logTileDestroyed(
+ tileSpec,
+ QSPipelineLogger.TileDestroyedReason.CUSTOM_TILE_USER_CHANGED
+ )
+ null
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/TileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/TileModel.kt
new file mode 100644
index 0000000..e2381ec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/TileModel.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.model
+
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.pipeline.shared.TileSpec
+
+/**
+ * Container for a [tile] and its [spec]. The following must be true:
+ * ```
+ * spec.spec == tile.tileSpec
+ * ```
+ */
+data class TileModel(val spec: TileSpec, val tile: QSTile) {
+ init {
+ check(spec.spec == tile.tileSpec)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt
index 69d8248..8940800 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt
@@ -93,7 +93,7 @@
private fun performRemove(args: List<String>, spec: TileSpec) {
val user = args.getOrNull(2)?.toInt() ?: userRepository.getSelectedUserInfo().id
- scope.launch { tileSpecRepository.removeTile(user, spec) }
+ scope.launch { tileSpecRepository.removeTiles(user, listOf(spec)) }
}
override fun help(pw: PrintWriter) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
index c691c2f..af1cd09 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
@@ -66,6 +66,10 @@
}
}
+ fun create(component: ComponentName): CustomTileSpec {
+ return CustomTileSpec(CustomTile.toSpec(component), component)
+ }
+
private val String.isCustomTileSpec: Boolean
get() = startsWith(CustomTile.PREFIX)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
index 200f743..767ce91 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
@@ -73,4 +73,59 @@
{ "Tiles changed in settings for user $int1: $str1" }
)
}
+
+ /** Log when a tile is destroyed and its reason for destroying. */
+ fun logTileDestroyed(spec: TileSpec, reason: TileDestroyedReason) {
+ tileListLogBuffer.log(
+ TILE_LIST_TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = spec.toString()
+ str2 = reason.readable
+ },
+ { "Tile $str1 destroyed. Reason: $str2" }
+ )
+ }
+
+ /** Log when a tile is created. */
+ fun logTileCreated(spec: TileSpec) {
+ tileListLogBuffer.log(
+ TILE_LIST_TAG,
+ LogLevel.DEBUG,
+ { str1 = spec.toString() },
+ { "Tile $str1 created" }
+ )
+ }
+
+ /** Ĺog when trying to create a tile, but it's not found in the factory. */
+ fun logTileNotFoundInFactory(spec: TileSpec) {
+ tileListLogBuffer.log(
+ TILE_LIST_TAG,
+ LogLevel.VERBOSE,
+ { str1 = spec.toString() },
+ { "Tile $str1 not found in factory" }
+ )
+ }
+
+ /** Log when the user is changed for a platform tile. */
+ fun logTileUserChanged(spec: TileSpec, user: Int) {
+ tileListLogBuffer.log(
+ TILE_LIST_TAG,
+ LogLevel.VERBOSE,
+ {
+ str1 = spec.toString()
+ int1 = user
+ },
+ { "User changed to $int1 for tile $str1" }
+ )
+ }
+
+ /** Reasons for destroying an existing tile. */
+ enum class TileDestroyedReason(val readable: String) {
+ TILE_REMOVED("Tile removed from current set"),
+ CUSTOM_TILE_USER_CHANGED("User changed for custom tile"),
+ NEW_TILE_NOT_AVAILABLE("New tile not available"),
+ EXISTING_TILE_NOT_AVAILABLE("Existing tile not available"),
+ TILE_NOT_PRESENT_IN_NEW_USER("Tile not present in new user"),
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 5e65cdd..aedd976 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -22,10 +22,6 @@
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
-import static androidx.constraintlayout.widget.ConstraintSet.END;
-import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
-
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION;
import static com.android.keyguard.KeyguardClockSwitch.LARGE;
import static com.android.keyguard.KeyguardClockSwitch.SMALL;
import static com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE;
@@ -73,12 +69,6 @@
import android.os.UserManager;
import android.os.VibrationEffect;
import android.provider.Settings;
-import android.transition.ChangeBounds;
-import android.transition.Transition;
-import android.transition.TransitionListenerAdapter;
-import android.transition.TransitionManager;
-import android.transition.TransitionSet;
-import android.transition.TransitionValues;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.MathUtils;
@@ -100,8 +90,6 @@
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
-import androidx.constraintlayout.widget.ConstraintSet;
-
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
@@ -163,8 +151,6 @@
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.ClockAnimations;
-import com.android.systemui.plugins.ClockController;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.FalsingManager.FalsingTapListener;
import com.android.systemui.plugins.qs.QS;
@@ -301,11 +287,6 @@
private static final Rect M_DUMMY_DIRTY_RECT = new Rect(0, 0, 1, 1);
private static final Rect EMPTY_RECT = new Rect();
/**
- * Duration to use for the animator when the keyguard status view alignment changes, and a
- * custom clock animation is in use.
- */
- private static final int KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION = 1000;
- /**
* Whether the Shade should animate to reflect Back gesture progress.
* To minimize latency at runtime, we cache this, else we'd be reading it every time
* updateQsExpansion() is called... and it's called very often.
@@ -552,8 +533,6 @@
private final KeyguardMediaController mKeyguardMediaController;
- private boolean mStatusViewCentered = true;
-
private final Optional<KeyguardUnfoldTransition> mKeyguardUnfoldTransition;
private final Optional<NotificationPanelUnfoldAnimationController>
mNotificationPanelUnfoldAnimationController;
@@ -684,18 +663,6 @@
step.getTransitionState() == TransitionState.RUNNING;
};
- private final TransitionListenerAdapter mKeyguardStatusAlignmentTransitionListener =
- new TransitionListenerAdapter() {
- @Override
- public void onTransitionCancel(Transition transition) {
- mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION);
- }
-
- @Override
- public void onTransitionEnd(Transition transition) {
- mInteractionJankMonitor.end(CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION);
- }
- };
private final ActivityStarter mActivityStarter;
@Inject
@@ -1323,9 +1290,6 @@
keyguardStatusView = (KeyguardStatusView) mLayoutInflater.inflate(
R.layout.keyguard_status_view, mNotificationContainerParent, false);
mNotificationContainerParent.addView(keyguardStatusView, statusIndex);
- // When it's reinflated, this is centered by default. If it shouldn't be, this will update
- // below when resources are updated.
- mStatusViewCentered = true;
attachSplitShadeMediaPlayerContainer(
keyguardStatusView.findViewById(R.id.status_view_media_container));
@@ -1620,68 +1584,9 @@
private void updateKeyguardStatusViewAlignment(boolean animate) {
boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered();
- if (mStatusViewCentered != shouldBeCentered) {
- mStatusViewCentered = shouldBeCentered;
- ConstraintSet constraintSet = new ConstraintSet();
- constraintSet.clone(mNotificationContainerParent);
- int statusConstraint = shouldBeCentered ? PARENT_ID : R.id.qs_edge_guideline;
- constraintSet.connect(R.id.keyguard_status_view, END, statusConstraint, END);
- if (animate) {
- mInteractionJankMonitor.begin(mView, CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION);
- ChangeBounds transition = new ChangeBounds();
- if (mSplitShadeEnabled) {
- // Excluding media from the transition on split-shade, as it doesn't transition
- // horizontally properly.
- transition.excludeTarget(R.id.status_view_media_container, true);
- }
-
- transition.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- transition.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
-
- ClockController clock = mKeyguardStatusViewController.getClockController();
- boolean customClockAnimation = clock != null
- && clock.getConfig().getHasCustomPositionUpdatedAnimation();
-
- if (mFeatureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION) && customClockAnimation) {
- // Find the clock, so we can exclude it from this transition.
- FrameLayout clockContainerView =
- mView.findViewById(R.id.lockscreen_clock_view_large);
-
- // The clock container can sometimes be null. If it is, just fall back to the
- // old animation rather than setting up the custom animations.
- if (clockContainerView == null || clockContainerView.getChildCount() == 0) {
- transition.addListener(mKeyguardStatusAlignmentTransitionListener);
- TransitionManager.beginDelayedTransition(
- mNotificationContainerParent, transition);
- } else {
- View clockView = clockContainerView.getChildAt(0);
-
- transition.excludeTarget(clockView, /* exclude= */ true);
-
- TransitionSet set = new TransitionSet();
- set.addTransition(transition);
-
- SplitShadeTransitionAdapter adapter =
- new SplitShadeTransitionAdapter(mKeyguardStatusViewController);
-
- // Use linear here, so the actual clock can pick its own interpolator.
- adapter.setInterpolator(Interpolators.LINEAR);
- adapter.setDuration(KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION);
- adapter.addTarget(clockView);
- set.addTransition(adapter);
- set.addListener(mKeyguardStatusAlignmentTransitionListener);
- TransitionManager.beginDelayedTransition(mNotificationContainerParent, set);
- }
- } else {
- transition.addListener(mKeyguardStatusAlignmentTransitionListener);
- TransitionManager.beginDelayedTransition(
- mNotificationContainerParent, transition);
- }
- }
-
- constraintSet.applyTo(mNotificationContainerParent);
- }
- mKeyguardUnfoldTransition.ifPresent(t -> t.setStatusViewCentered(mStatusViewCentered));
+ mKeyguardStatusViewController.updateAlignment(
+ mNotificationContainerParent, mSplitShadeEnabled, shouldBeCentered, animate);
+ mKeyguardUnfoldTransition.ifPresent(t -> t.setStatusViewCentered(shouldBeCentered));
}
private boolean shouldKeyguardStatusViewBeCentered() {
@@ -3335,7 +3240,6 @@
ipw.print("mIsGestureNavigation="); ipw.println(mIsGestureNavigation);
ipw.print("mOldLayoutDirection="); ipw.println(mOldLayoutDirection);
ipw.print("mMinFraction="); ipw.println(mMinFraction);
- ipw.print("mStatusViewCentered="); ipw.println(mStatusViewCentered);
ipw.print("mSplitShadeFullTransitionDistance=");
ipw.println(mSplitShadeFullTransitionDistance);
ipw.print("mSplitShadeScrimTransitionDistance=");
@@ -5130,65 +5034,6 @@
}
}
- static class SplitShadeTransitionAdapter extends Transition {
- private static final String PROP_BOUNDS = "splitShadeTransitionAdapter:bounds";
- private static final String[] TRANSITION_PROPERTIES = { PROP_BOUNDS };
-
- private final KeyguardStatusViewController mController;
-
- SplitShadeTransitionAdapter(KeyguardStatusViewController controller) {
- mController = controller;
- }
-
- private void captureValues(TransitionValues transitionValues) {
- Rect boundsRect = new Rect();
- boundsRect.left = transitionValues.view.getLeft();
- boundsRect.top = transitionValues.view.getTop();
- boundsRect.right = transitionValues.view.getRight();
- boundsRect.bottom = transitionValues.view.getBottom();
- transitionValues.values.put(PROP_BOUNDS, boundsRect);
- }
-
- @Override
- public void captureEndValues(TransitionValues transitionValues) {
- captureValues(transitionValues);
- }
-
- @Override
- public void captureStartValues(TransitionValues transitionValues) {
- captureValues(transitionValues);
- }
-
- @Nullable
- @Override
- public Animator createAnimator(ViewGroup sceneRoot, @Nullable TransitionValues startValues,
- @Nullable TransitionValues endValues) {
- if (startValues == null || endValues == null) {
- return null;
- }
- ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
-
- Rect from = (Rect) startValues.values.get(PROP_BOUNDS);
- Rect to = (Rect) endValues.values.get(PROP_BOUNDS);
-
- anim.addUpdateListener(animation -> {
- ClockController clock = mController.getClockController();
- if (clock == null) {
- return;
- }
-
- clock.getAnimations().onPositionUpdated(from, to, animation.getAnimatedFraction());
- });
-
- return anim;
- }
-
- @Override
- public String[] getTransitionProperties() {
- return TRANSITION_PROPERTIES;
- }
- }
-
private final class HeadsUpNotificationViewControllerImpl implements
HeadsUpTouchHelper.HeadsUpNotificationViewController {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index b31ec33..7cb1cbe 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -17,6 +17,8 @@
package com.android.systemui.shade;
+import static android.view.WindowInsets.Type.ime;
+
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE;
import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
import static com.android.systemui.shade.NotificationPanelViewController.COUNTER_PANEL_OPEN_QS;
@@ -463,9 +465,17 @@
return (mQs != null ? mQs.getHeader().getHeight() : 0) + mPeekHeight;
}
+ private boolean isRemoteInputActiveWithKeyboardUp() {
+ //TODO(b/227115380) remove the isVisible(ime()) check once isRemoteInputActive is fixed.
+ // The check for keyboard visibility is a temporary workaround that allows QS to expand
+ // even when isRemoteInputActive is mistakenly returning true.
+ return mRemoteInputManager.isRemoteInputActive()
+ && mPanelView.getRootWindowInsets().isVisible(ime());
+ }
+
public boolean isExpansionEnabled() {
return mExpansionEnabledPolicy && mExpansionEnabledAmbient
- && !mRemoteInputManager.isRemoteInputActive();
+ && !isRemoteInputActiveWithKeyboardUp();
}
public float getTransitioningToFullShadeProgress() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
index c84894f..06f43f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
@@ -841,6 +841,19 @@
BottomSheetBehavior<FrameLayout> behavior = BottomSheetBehavior.from(bottomSheet);
behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
behavior.setSkipCollapsed(true);
+ behavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
+ @Override
+ public void onStateChanged(@NonNull View bottomSheet, int newState) {
+ if (newState == BottomSheetBehavior.STATE_DRAGGING) {
+ behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
+ }
+ }
+
+ @Override
+ public void onSlide(@NonNull View bottomSheet, float slideOffset) {
+ // Do nothing.
+ }
+ });
mKeyboardShortcutsBottomSheetDialog.setCanceledOnTouchOutside(true);
Window keyboardShortcutsWindow = mKeyboardShortcutsBottomSheetDialog.getWindow();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 765c93e..142689e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -1127,13 +1127,7 @@
final boolean faceAuthUnavailable = biometricSourceType == FACE
&& msgId == BIOMETRIC_HELP_FACE_NOT_AVAILABLE;
- // TODO(b/141025588): refactor to reduce repetition of code/comments
- // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
- // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to
- // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
- // check of whether non-strong biometric is allowed
- if (!mKeyguardUpdateMonitor
- .isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)
+ if (isPrimaryAuthRequired()
&& !faceAuthUnavailable) {
return;
}
@@ -1234,7 +1228,7 @@
private void onFaceAuthError(int msgId, String errString) {
CharSequence deferredFaceMessage = mFaceAcquiredMessageDeferral.getDeferredMessage();
mFaceAcquiredMessageDeferral.reset();
- if (shouldSuppressFaceError(msgId, mKeyguardUpdateMonitor)) {
+ if (shouldSuppressFaceError(msgId)) {
mKeyguardLogger.logBiometricMessage("suppressingFaceError", msgId, errString);
return;
}
@@ -1248,7 +1242,7 @@
}
private void onFingerprintAuthError(int msgId, String errString) {
- if (shouldSuppressFingerprintError(msgId, mKeyguardUpdateMonitor)) {
+ if (shouldSuppressFingerprintError(msgId)) {
mKeyguardLogger.logBiometricMessage("suppressingFingerprintError",
msgId,
errString);
@@ -1257,31 +1251,19 @@
}
}
- private boolean shouldSuppressFingerprintError(int msgId,
- KeyguardUpdateMonitor updateMonitor) {
- // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
- // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to
- // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
- // check of whether non-strong biometric is allowed
- return ((!updateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)
- && !isLockoutError(msgId))
+ private boolean shouldSuppressFingerprintError(int msgId) {
+ return ((isPrimaryAuthRequired() && !isLockoutError(msgId))
|| msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED
|| msgId == FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED
|| msgId == FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED);
}
- private boolean shouldSuppressFaceError(int msgId, KeyguardUpdateMonitor updateMonitor) {
- // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
- // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to
- // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
- // check of whether non-strong biometric is allowed
- return ((!updateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)
- && msgId != FaceManager.FACE_ERROR_LOCKOUT_PERMANENT)
+ private boolean shouldSuppressFaceError(int msgId) {
+ return ((isPrimaryAuthRequired() && msgId != FaceManager.FACE_ERROR_LOCKOUT_PERMANENT)
|| msgId == FaceManager.FACE_ERROR_CANCELED
|| msgId == FaceManager.FACE_ERROR_UNABLE_TO_PROCESS);
}
-
@Override
public void onTrustChanged(int userId) {
if (!isCurrentUser(userId)) return;
@@ -1355,6 +1337,16 @@
}
}
+ private boolean isPrimaryAuthRequired() {
+ // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
+ // as long as primary auth, i.e. PIN/pattern/password, is required), so it's ok to
+ // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
+ // check of whether non-strong biometric is allowed since strong biometrics can still be
+ // used.
+ return !mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ true /* isStrongBiometric */);
+ }
+
protected boolean isPluggedInAndCharging() {
return mPowerPluggedIn;
}
@@ -1431,7 +1423,7 @@
private boolean canUnlockWithFingerprint() {
return mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
- getCurrentUser());
+ getCurrentUser()) && mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed();
}
private void showErrorMessageNowOrLater(String errString, @Nullable String followUpMsg) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java b/packages/SystemUI/src/com/android/systemui/statusbar/LegacyNotificationShelfControllerImpl.java
similarity index 84%
rename from packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/LegacyNotificationShelfControllerImpl.java
index cb4ae28..f7d37e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LegacyNotificationShelfControllerImpl.java
@@ -25,7 +25,6 @@
import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
@@ -35,7 +34,7 @@
* Controller class for {@link NotificationShelf}.
*/
@NotificationRowScope
-public class NotificationShelfController {
+public class LegacyNotificationShelfControllerImpl implements NotificationShelfController {
private final NotificationShelf mView;
private final ActivatableNotificationViewController mActivatableNotificationViewController;
private final KeyguardBypassController mKeyguardBypassController;
@@ -44,7 +43,7 @@
private AmbientState mAmbientState;
@Inject
- public NotificationShelfController(
+ public LegacyNotificationShelfControllerImpl(
NotificationShelf notificationShelf,
ActivatableNotificationViewController activatableNotificationViewController,
KeyguardBypassController keyguardBypassController,
@@ -79,56 +78,42 @@
}
}
+ @Override
public NotificationShelf getView() {
return mView;
}
+ @Override
public boolean canModifyColorOfNotifications() {
return mAmbientState.isShadeExpanded()
&& !(mAmbientState.isOnKeyguard() && mKeyguardBypassController.getBypassEnabled());
}
+ @Override
public NotificationIconContainer getShelfIcons() {
return mView.getShelfIcons();
}
- public @View.Visibility int getVisibility() {
- return mView.getVisibility();
- }
-
- public void setCollapsedIcons(NotificationIconContainer notificationIcons) {
- mView.setCollapsedIcons(notificationIcons);
- }
-
+ @Override
public void bind(AmbientState ambientState,
NotificationStackScrollLayoutController notificationStackScrollLayoutController) {
mView.bind(ambientState, notificationStackScrollLayoutController);
mAmbientState = ambientState;
}
- public int getHeight() {
- return mView.getHeight();
- }
-
- public void updateState(StackScrollAlgorithm.StackScrollAlgorithmState algorithmState,
- AmbientState ambientState) {
- mAmbientState = ambientState;
- mView.updateState(algorithmState, ambientState);
- }
-
+ @Override
public int getIntrinsicHeight() {
return mView.getIntrinsicHeight();
}
+ @Override
public void setOnActivatedListener(ActivatableNotificationView.OnActivatedListener listener) {
mView.setOnActivatedListener(listener);
}
+ @Override
public void setOnClickListener(View.OnClickListener onClickListener) {
mView.setOnClickListener(onClickListener);
}
- public int getNotGoneIndex() {
- return mView.getNotGoneIndex();
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 4873c9d..d1c6aef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -24,6 +24,7 @@
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.IndentingPrintWriter;
+import android.util.Log;
import android.util.MathUtils;
import android.view.View;
import android.view.ViewGroup;
@@ -64,8 +65,7 @@
* A notification shelf view that is placed inside the notification scroller. It manages the
* overflow icons that don't fit into the regular list anymore.
*/
-public class NotificationShelf extends ActivatableNotificationView implements
- View.OnLayoutChangeListener, StateListener {
+public class NotificationShelf extends ActivatableNotificationView implements StateListener {
private static final int TAG_CONTINUOUS_CLIPPING = R.id.continuous_clipping_tag;
private static final String TAG = "NotificationShelf";
@@ -78,7 +78,6 @@
private static final SourceType SHELF_SCROLL = SourceType.from("ShelfScroll");
private NotificationIconContainer mShelfIcons;
- private int[] mTmp = new int[2];
private boolean mHideBackground;
private int mStatusBarHeight;
private boolean mEnableNotificationClipping;
@@ -87,7 +86,6 @@
private int mPaddingBetweenElements;
private int mNotGoneIndex;
private boolean mHasItemsInStableShelf;
- private NotificationIconContainer mCollapsedIcons;
private int mScrollFastThreshold;
private int mStatusBarState;
private boolean mInteractive;
@@ -99,6 +97,8 @@
private NotificationShelfController mController;
private float mActualWidth = -1;
private boolean mSensitiveRevealAnimEndabled;
+ private boolean mShelfRefactorFlagEnabled;
+ private boolean mCanModifyColorOfNotifications;
public NotificationShelf(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -428,7 +428,7 @@
transitionAmount = inShelfAmount;
}
// We don't want to modify the color if the notification is hun'd
- if (isLastChild && mController.canModifyColorOfNotifications()) {
+ if (isLastChild && canModifyColorOfNotifications()) {
if (colorOfViewBeforeLast == NO_COLOR) {
colorOfViewBeforeLast = ownColorUntinted;
}
@@ -493,6 +493,14 @@
}
}
+ private boolean canModifyColorOfNotifications() {
+ if (mShelfRefactorFlagEnabled) {
+ return mCanModifyColorOfNotifications && mAmbientState.isShadeExpanded();
+ } else {
+ return mController.canModifyColorOfNotifications();
+ }
+ }
+
private void updateCornerRoundnessOnScroll(
ActivatableNotificationView anv,
float viewStart,
@@ -868,10 +876,6 @@
return mShelfIcons.getIconState(icon);
}
- private float getFullyClosedTranslation() {
- return -(getIntrinsicHeight() - mStatusBarHeight) / 2;
- }
-
@Override
public boolean hasNoContentHeight() {
return true;
@@ -893,7 +897,6 @@
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
- updateRelativeOffset();
// we always want to clip to our sides, such that nothing can draw outside of these bounds
int height = getResources().getDisplayMetrics().heightPixels;
@@ -903,13 +906,6 @@
}
}
- private void updateRelativeOffset() {
- if (mCollapsedIcons != null) {
- mCollapsedIcons.getLocationOnScreen(mTmp);
- }
- getLocationOnScreen(mTmp);
- }
-
/**
* @return the index of the notification at which the shelf visually resides
*/
@@ -924,19 +920,6 @@
}
}
- /**
- * @return whether the shelf has any icons in it when a potential animation has finished, i.e
- * if the current state would be applied right now
- */
- public boolean hasItemsInStableShelf() {
- return mHasItemsInStableShelf;
- }
-
- public void setCollapsedIcons(NotificationIconContainer collapsedIcons) {
- mCollapsedIcons = collapsedIcons;
- mCollapsedIcons.addOnLayoutChangeListener(this);
- }
-
@Override
public void onStateChanged(int newState) {
mStatusBarState = newState;
@@ -983,20 +966,35 @@
}
@Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
- int oldTop, int oldRight, int oldBottom) {
- updateRelativeOffset();
- }
-
- @Override
public boolean needsClippingToShelf() {
return false;
}
+ private void assertRefactorFlagDisabled() {
+ if (mShelfRefactorFlagEnabled) {
+ throw new IllegalStateException(
+ "Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is enabled.");
+ }
+ }
+
+ private boolean checkRefactorFlagEnabled() {
+ if (!mShelfRefactorFlagEnabled) {
+ Log.wtf(TAG,
+ "Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is disabled.");
+ }
+ return mShelfRefactorFlagEnabled;
+ }
+
public void setController(NotificationShelfController notificationShelfController) {
+ assertRefactorFlagDisabled();
mController = notificationShelfController;
}
+ public void setCanModifyColorOfNotifications(boolean canModifyColorOfNotifications) {
+ if (!checkRefactorFlagEnabled()) return;
+ mCanModifyColorOfNotifications = canModifyColorOfNotifications;
+ }
+
public void setIndexOfFirstViewInShelf(ExpandableView firstViewInShelf) {
mIndexOfFirstViewInShelf = mHostLayoutController.indexOfChild(firstViewInShelf);
}
@@ -1009,6 +1007,10 @@
mSensitiveRevealAnimEndabled = enabled;
}
+ public void setRefactorFlagEnabled(boolean enabled) {
+ mShelfRefactorFlagEnabled = enabled;
+ }
+
/**
* This method resets the OnScroll roundness of a view to 0f
* <p>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt
new file mode 100644
index 0000000..bf3d47c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar
+
+import android.view.View
+import android.view.View.OnClickListener
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationView
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationView.OnActivatedListener
+import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.statusbar.notification.stack.AmbientState
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.phone.NotificationIconContainer
+
+/** Controller interface for [NotificationShelf]. */
+interface NotificationShelfController {
+ /** The [NotificationShelf] controlled by this Controller. */
+ val view: NotificationShelf
+
+ /** @see ExpandableView.getIntrinsicHeight */
+ val intrinsicHeight: Int
+
+ /** Container view for icons displayed in the shelf. */
+ val shelfIcons: NotificationIconContainer
+
+ /** Whether or not the shelf can modify the color of notifications in the shade. */
+ fun canModifyColorOfNotifications(): Boolean
+
+ /** @see ActivatableNotificationView.setOnActivatedListener */
+ fun setOnActivatedListener(listener: OnActivatedListener)
+
+ /** Binds the shelf to the host [NotificationStackScrollLayout], via its Controller. */
+ fun bind(
+ ambientState: AmbientState,
+ notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
+ )
+
+ /** @see View.setOnClickListener */
+ fun setOnClickListener(listener: OnClickListener)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationShelfComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationShelfComponent.java
index af8d6ec..98cd84d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationShelfComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationShelfComponent.java
@@ -16,8 +16,8 @@
package com.android.systemui.statusbar.notification.row.dagger;
+import com.android.systemui.statusbar.LegacyNotificationShelfControllerImpl;
import com.android.systemui.statusbar.NotificationShelf;
-import com.android.systemui.statusbar.NotificationShelfController;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import dagger.Binds;
@@ -46,7 +46,8 @@
* Creates a NotificationShelfController.
*/
@NotificationRowScope
- NotificationShelfController getNotificationShelfController();
+ LegacyNotificationShelfControllerImpl getNotificationShelfController();
+
/**
* Dagger Module that extracts interesting properties from a NotificationShelf.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt
new file mode 100644
index 0000000..db550c0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.shelf.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.statusbar.NotificationShelf
+import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+
+/** Interactor for the [NotificationShelf] */
+@CentralSurfacesComponent.CentralSurfacesScope
+class NotificationShelfInteractor
+@Inject
+constructor(
+ private val keyguardRepository: KeyguardRepository,
+ private val deviceEntryFaceAuthRepository: DeviceEntryFaceAuthRepository,
+) {
+ /** Is the system in a state where the shelf is just a static display of notification icons? */
+ val isShelfStatic: Flow<Boolean>
+ get() =
+ combine(
+ keyguardRepository.isKeyguardShowing,
+ deviceEntryFaceAuthRepository.isBypassEnabled,
+ ) { isKeyguardShowing, isBypassEnabled ->
+ isKeyguardShowing && isBypassEnabled
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
new file mode 100644
index 0000000..bd531ca
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.shelf.ui.viewbinder
+
+import android.view.View
+import android.view.View.OnAttachStateChangeListener
+import android.view.accessibility.AccessibilityManager
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.LegacyNotificationShelfControllerImpl
+import com.android.systemui.statusbar.NotificationShelf
+import com.android.systemui.statusbar.NotificationShelfController
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationView
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController
+import com.android.systemui.statusbar.notification.row.ExpandableOutlineViewController
+import com.android.systemui.statusbar.notification.row.ExpandableViewController
+import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel
+import com.android.systemui.statusbar.notification.stack.AmbientState
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.phone.NotificationIconContainer
+import com.android.systemui.statusbar.phone.NotificationTapHelper
+import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import javax.inject.Inject
+
+/**
+ * Controller class for [NotificationShelf]. This implementation serves as a temporary wrapper
+ * around a [NotificationShelfViewBinder], so that external code can continue to depend on the
+ * [NotificationShelfController] interface. Once the [LegacyNotificationShelfControllerImpl] is
+ * removed, this class can go away and the ViewBinder can be used directly.
+ */
+@CentralSurfacesScope
+class NotificationShelfViewBinderWrapperControllerImpl
+@Inject
+constructor(
+ private val shelf: NotificationShelf,
+ private val viewModel: NotificationShelfViewModel,
+ featureFlags: FeatureFlags,
+ private val notifTapHelperFactory: NotificationTapHelper.Factory,
+ private val a11yManager: AccessibilityManager,
+ private val falsingManager: FalsingManager,
+ private val falsingCollector: FalsingCollector,
+ private val statusBarStateController: SysuiStatusBarStateController,
+) : NotificationShelfController {
+
+ override val view: NotificationShelf
+ get() = shelf
+
+ init {
+ shelf.apply {
+ setRefactorFlagEnabled(featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR))
+ useRoundnessSourceTypes(featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES))
+ setSensitiveRevealAnimEndabled(featureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM))
+ }
+ }
+
+ fun init() {
+ NotificationShelfViewBinder.bind(viewModel, shelf)
+
+ ActivatableNotificationViewController(
+ shelf,
+ notifTapHelperFactory,
+ ExpandableOutlineViewController(shelf, ExpandableViewController(shelf)),
+ a11yManager,
+ falsingManager,
+ falsingCollector,
+ )
+ .init()
+ val onAttachStateListener =
+ object : OnAttachStateChangeListener {
+ override fun onViewAttachedToWindow(v: View) {
+ statusBarStateController.addCallback(
+ shelf,
+ SysuiStatusBarStateController.RANK_SHELF,
+ )
+ }
+
+ override fun onViewDetachedFromWindow(v: View) {
+ statusBarStateController.removeCallback(shelf)
+ }
+ }
+ shelf.addOnAttachStateChangeListener(onAttachStateListener)
+ if (shelf.isAttachedToWindow) {
+ onAttachStateListener.onViewAttachedToWindow(shelf)
+ }
+ }
+
+ override val intrinsicHeight: Int
+ get() = shelf.intrinsicHeight
+
+ override val shelfIcons: NotificationIconContainer
+ get() = shelf.shelfIcons
+
+ override fun canModifyColorOfNotifications(): Boolean = unsupported
+
+ override fun setOnActivatedListener(listener: ActivatableNotificationView.OnActivatedListener) {
+ shelf.setOnActivatedListener(listener)
+ }
+
+ override fun bind(
+ ambientState: AmbientState,
+ notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
+ ) {
+ shelf.bind(ambientState, notificationStackScrollLayoutController)
+ }
+
+ override fun setOnClickListener(listener: View.OnClickListener) {
+ shelf.setOnClickListener(listener)
+ }
+
+ private val unsupported: Nothing
+ get() = error("Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is enabled")
+}
+
+/** Binds a [NotificationShelf] to its backend. */
+object NotificationShelfViewBinder {
+ fun bind(viewModel: NotificationShelfViewModel, shelf: NotificationShelf) {
+ shelf.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.canModifyColorOfNotifications
+ .onEach(shelf::setCanModifyColorOfNotifications)
+ .launchIn(this)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
new file mode 100644
index 0000000..b84834a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.shelf.ui.viewmodel
+
+import com.android.systemui.statusbar.NotificationShelf
+import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor
+import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** ViewModel for [NotificationShelf]. */
+@CentralSurfacesScope
+class NotificationShelfViewModel
+@Inject
+constructor(
+ private val interactor: NotificationShelfInteractor,
+) {
+ /** Is the shelf allowed to modify the color of notifications in the host layout? */
+ val canModifyColorOfNotifications: Flow<Boolean>
+ get() = interactor.isShelfStatic.map { static -> !static }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java
deleted file mode 100644
index 076e5f1..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.phone;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorFilter;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.view.WindowInsets;
-import android.widget.FrameLayout;
-
-/**
- * A view group which contains the preview of phone/camera and draws a black bar at the bottom as
- * the fake navigation bar.
- */
-public class KeyguardPreviewContainer extends FrameLayout {
-
- private Drawable mBlackBarDrawable = new Drawable() {
- @Override
- public void draw(Canvas canvas) {
- canvas.save();
- canvas.clipRect(0, getHeight() - getPaddingBottom(), getWidth(), getHeight());
- canvas.drawColor(Color.BLACK);
- canvas.restore();
- }
-
- @Override
- public void setAlpha(int alpha) {
- // noop
- }
-
- @Override
- public void setColorFilter(ColorFilter colorFilter) {
- // noop
- }
-
- @Override
- public int getOpacity() {
- return android.graphics.PixelFormat.OPAQUE;
- }
- };
-
- public KeyguardPreviewContainer(Context context, AttributeSet attrs) {
- super(context, attrs);
- setBackground(mBlackBarDrawable);
- }
-
- @Override
- public WindowInsets onApplyWindowInsets(WindowInsets insets) {
- setPadding(0, 0, 0, insets.getStableInsetBottom());
- return super.onApplyWindowInsets(insets);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index eb19c0d..057fa42 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -193,7 +193,6 @@
public void setupShelf(NotificationShelfController notificationShelfController) {
mShelfIcons = notificationShelfController.getShelfIcons();
- notificationShelfController.setCollapsedIcons(mNotificationIcons);
}
public void onDensityOrFontScaleChanged(Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index 0929233..5d4adda 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -33,6 +33,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.privacy.OngoingPrivacyChip;
import com.android.systemui.settings.UserTracker;
@@ -44,12 +45,14 @@
import com.android.systemui.shade.NotificationsQuickSettingsContainer;
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.LegacyNotificationShelfControllerImpl;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.NotificationShelfController;
import com.android.systemui.statusbar.OperatorNameViewController;
import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent;
+import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinderWrapperControllerImpl;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator;
@@ -76,6 +79,7 @@
import java.util.concurrent.Executor;
import javax.inject.Named;
+import javax.inject.Provider;
import dagger.Binds;
import dagger.Module;
@@ -130,16 +134,24 @@
@Provides
@CentralSurfacesComponent.CentralSurfacesScope
public static NotificationShelfController providesStatusBarWindowView(
+ FeatureFlags featureFlags,
+ Provider<NotificationShelfViewBinderWrapperControllerImpl> newImpl,
NotificationShelfComponent.Builder notificationShelfComponentBuilder,
NotificationShelf notificationShelf) {
- NotificationShelfComponent component = notificationShelfComponentBuilder
- .notificationShelf(notificationShelf)
- .build();
- NotificationShelfController notificationShelfController =
- component.getNotificationShelfController();
- notificationShelfController.init();
+ if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
+ NotificationShelfViewBinderWrapperControllerImpl impl = newImpl.get();
+ impl.init();
+ return impl;
+ } else {
+ NotificationShelfComponent component = notificationShelfComponentBuilder
+ .notificationShelf(notificationShelf)
+ .build();
+ LegacyNotificationShelfControllerImpl notificationShelfController =
+ component.getNotificationShelfController();
+ notificationShelfController.init();
- return notificationShelfController;
+ return notificationShelfController;
+ }
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
index 08c14e7..f800cf4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
@@ -33,6 +33,15 @@
/** Observable for the current wifi network activity. */
val wifiActivity: StateFlow<DataActivityModel>
+
+ /**
+ * Returns true if the device is currently connected to a wifi network with a valid SSID and
+ * false otherwise.
+ */
+ fun isWifiConnectedWithValidSsid(): Boolean {
+ val currentNetwork = wifiNetwork.value
+ return currentNetwork is WifiNetworkModel.Active && currentNetwork.hasValidSsid()
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
index 96ab074..1a41abf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.pipeline.wifi.domain.interactor
-import android.net.wifi.WifiManager
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
@@ -76,7 +75,7 @@
when {
info.isPasspointAccessPoint || info.isOnlineSignUpForPasspointAccessPoint ->
info.passpointProviderFriendlyName
- info.ssid != WifiManager.UNKNOWN_SSID -> info.ssid
+ info.hasValidSsid() -> info.ssid
else -> null
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt
index 0923d78..4b33c88 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.wifi.shared.model
+import android.net.wifi.WifiManager.UNKNOWN_SSID
import android.telephony.SubscriptionManager
import androidx.annotation.VisibleForTesting
import com.android.systemui.log.table.Diffable
@@ -223,6 +224,11 @@
}
}
+ /** Returns true if this network has a valid SSID and false otherwise. */
+ fun hasValidSsid(): Boolean {
+ return ssid != null && ssid != UNKNOWN_SSID
+ }
+
override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) {
if (prevVal !is Active) {
logFull(row)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index 654ba04..1e63b2a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -21,6 +21,8 @@
import static android.os.BatteryManager.EXTRA_HEALTH;
import static android.os.BatteryManager.EXTRA_PRESENT;
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_QS;
+
import android.annotation.WorkerThread;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -169,7 +171,8 @@
@Override
public void setPowerSaveMode(boolean powerSave, View view) {
if (powerSave) mPowerSaverStartView.set(new WeakReference<>(view));
- BatterySaverUtils.setPowerSaveMode(mContext, powerSave, /*needFirstTimeWarning*/ true);
+ BatterySaverUtils.setPowerSaveMode(mContext, powerSave, /*needFirstTimeWarning*/ true,
+ SAVER_ENABLED_QS);
}
@Override
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 f1269f2..673819b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -38,14 +38,14 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import dagger.Lazy;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Objects;
import javax.inject.Inject;
-import dagger.Lazy;
-
/**
*/
@SysUISingleton
diff --git a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java
deleted file mode 100644
index e93e862..0000000
--- a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.core.animation;
-
-import android.os.Looper;
-import android.os.SystemClock;
-import android.util.AndroidRuntimeException;
-
-import androidx.annotation.NonNull;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * NOTE: this is a copy of the {@link androidx.core.animation.AnimatorTestRule} which attempts to
- * circumvent the problems with {@link androidx.core.animation.AnimationHandler} having a static
- * list of callbacks.
- *
- * TODO(b/275602127): remove this and use the original rule once we have the updated androidx code.
- */
-public final class AnimatorTestRule2 implements TestRule {
-
- class TestAnimationHandler extends AnimationHandler {
- TestAnimationHandler() {
- super(new TestProvider());
- }
-
- List<AnimationFrameCallback> animationCallbacks = new ArrayList<>();
-
- @Override
- void addAnimationFrameCallback(AnimationFrameCallback callback) {
- animationCallbacks.add(callback);
- callback.doAnimationFrame(getCurrentTime());
- }
-
- @Override
- public void removeCallback(AnimationFrameCallback callback) {
- int id = animationCallbacks.indexOf(callback);
- if (id >= 0) {
- animationCallbacks.set(id, null);
- }
- }
-
- void onAnimationFrame(long frameTime) {
- for (int i = 0; i < animationCallbacks.size(); i++) {
- final AnimationFrameCallback callback = animationCallbacks.get(i);
- if (callback == null) {
- continue;
- }
- callback.doAnimationFrame(frameTime);
- }
- }
-
- @Override
- void autoCancelBasedOn(ObjectAnimator objectAnimator) {
- for (int i = animationCallbacks.size() - 1; i >= 0; i--) {
- AnimationFrameCallback cb = animationCallbacks.get(i);
- if (cb == null) {
- continue;
- }
- if (objectAnimator.shouldAutoCancel(cb)) {
- ((Animator) animationCallbacks.get(i)).cancel();
- }
- }
- }
- }
-
- final TestAnimationHandler mTestHandler;
- final long mStartTime;
- private long mTotalTimeDelta = 0;
- private final Object mLock = new Object();
-
- public AnimatorTestRule2() {
- mStartTime = SystemClock.uptimeMillis();
- mTestHandler = new TestAnimationHandler();
- }
-
- @NonNull
- @Override
- public Statement apply(@NonNull final Statement base, @NonNull Description description) {
- return new Statement() {
- @Override
- public void evaluate() throws Throwable {
- AnimationHandler.setTestHandler(mTestHandler);
- try {
- base.evaluate();
- } finally {
- AnimationHandler.setTestHandler(null);
- }
- }
- };
- }
-
- /**
- * Advances the animation clock by the given amount of delta in milliseconds. This call will
- * produce an animation frame to all the ongoing animations. This method needs to be
- * called on the same thread as {@link Animator#start()}.
- *
- * @param timeDelta the amount of milliseconds to advance
- */
- public void advanceTimeBy(long timeDelta) {
- if (Looper.myLooper() == null) {
- // Throw an exception
- throw new AndroidRuntimeException("AnimationTestRule#advanceTimeBy(long) may only be"
- + "called on Looper threads");
- }
- synchronized (mLock) {
- // Advance time & pulse a frame
- mTotalTimeDelta += timeDelta < 0 ? 0 : timeDelta;
- }
- // produce a frame
- mTestHandler.onAnimationFrame(getCurrentTime());
- }
-
-
- /**
- * Returns the current time in milliseconds tracked by AnimationHandler. Note that this is a
- * different time than the time tracked by {@link SystemClock} This method needs to be called on
- * the same thread as {@link Animator#start()}.
- */
- public long getCurrentTime() {
- if (Looper.myLooper() == null) {
- // Throw an exception
- throw new AndroidRuntimeException("AnimationTestRule#getCurrentTime() may only be"
- + "called on Looper threads");
- }
- synchronized (mLock) {
- return mStartTime + mTotalTimeDelta;
- }
- }
-
-
- private class TestProvider implements AnimationHandler.AnimationFrameCallbackProvider {
- TestProvider() {
- }
-
- @Override
- public void onNewCallbackAdded(AnimationHandler.AnimationFrameCallback callback) {
- callback.doAnimationFrame(getCurrentTime());
- }
-
- @Override
- public void postFrameCallback() {
- }
-
- @Override
- public void setFrameDelay(long delay) {
- }
-
- @Override
- public long getFrameDelay() {
- return 0;
- }
- }
-}
-
diff --git a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt
index bddd60b..e7738af 100644
--- a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt
+++ b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt
@@ -30,7 +30,7 @@
@RunWithLooper(setAsMainLooper = true)
class AnimatorTestRuleTest : SysuiTestCase() {
- @get:Rule val animatorTestRule = AnimatorTestRule2()
+ @get:Rule val animatorTestRule = AnimatorTestRule()
@Test
fun testA() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
index ecf7e0d..5557efa 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
@@ -38,8 +38,6 @@
import static org.mockito.Mockito.when;
import android.content.pm.PackageManager;
-import android.net.wifi.WifiInfo;
-import android.net.wifi.WifiManager;
import android.provider.Settings;
import android.telephony.ServiceState;
import android.telephony.SubscriptionInfo;
@@ -52,6 +50,8 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository;
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel;
import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -88,8 +88,7 @@
private static final SubscriptionInfo TEST_SUBSCRIPTION_ROAMING = new SubscriptionInfo(0, "", 0,
TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_CARRIER_ID, 0xFFFFFF, "",
DATA_ROAMING_ENABLE, null, null, null, null, false, null, "");
- @Mock
- private WifiManager mWifiManager;
+ private FakeWifiRepository mWifiRepository = new FakeWifiRepository();
@Mock
private WakefulnessLifecycle mWakefulnessLifecycle;
@Mock
@@ -121,7 +120,6 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
- mContext.addMockSystemService(WifiManager.class, mWifiManager);
mContext.addMockSystemService(PackageManager.class, mPackageManager);
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(true);
mContext.addMockSystemService(TelephonyManager.class, mTelephonyManager);
@@ -144,7 +142,7 @@
when(mTelephonyManager.getActiveModemCount()).thenReturn(3);
mCarrierTextManager = new CarrierTextManager.Builder(
- mContext, mContext.getResources(), mWifiManager,
+ mContext, mContext.getResources(), mWifiRepository,
mTelephonyManager, mTelephonyListenerManager, mWakefulnessLifecycle, mMainExecutor,
mBgExecutor, mKeyguardUpdateMonitor)
.setShowAirplaneMode(true)
@@ -364,7 +362,11 @@
when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
TelephonyManager.SIM_STATE_READY);
when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
- mockWifi();
+
+ assertFalse(mWifiRepository.isWifiConnectedWithValidSsid());
+ mWifiRepository.setWifiNetwork(
+ new WifiNetworkModel.Active(0, false, 0, "", false, false, null));
+ assertTrue(mWifiRepository.isWifiConnectedWithValidSsid());
mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
ServiceState ss = mock(ServiceState.class);
@@ -385,13 +387,6 @@
assertNotEquals(AIRPLANE_MODE_TEXT, captor.getValue().carrierText);
}
- private void mockWifi() {
- when(mWifiManager.isWifiEnabled()).thenReturn(true);
- WifiInfo wifiInfo = mock(WifiInfo.class);
- when(wifiInfo.getBSSID()).thenReturn("");
- when(mWifiManager.getConnectionInfo()).thenReturn(wifiInfo);
- }
-
@Test
public void testCreateInfo_noSubscriptions() {
reset(mCarrierTextCallback);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 2f627cb..b9f8dd94 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -48,8 +48,10 @@
import com.android.systemui.plugins.ClockAnimations;
import com.android.systemui.plugins.ClockController;
import com.android.systemui.plugins.ClockEvents;
+import com.android.systemui.plugins.ClockFaceConfig;
import com.android.systemui.plugins.ClockFaceController;
import com.android.systemui.plugins.ClockFaceEvents;
+import com.android.systemui.plugins.ClockTickRate;
import com.android.systemui.plugins.log.LogBuffer;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.clocks.AnimatableClockView;
@@ -185,6 +187,10 @@
when(mClockController.getAnimations()).thenReturn(mClockAnimations);
when(mClockRegistry.createCurrentClock()).thenReturn(mClockController);
when(mClockEventController.getClock()).thenReturn(mClockController);
+ when(mSmallClockController.getConfig())
+ .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false));
+ when(mLargeClockController.getConfig())
+ .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false));
mSliceView = new View(getContext());
when(mView.findViewById(R.id.keyguard_slice_view)).thenReturn(mSliceView);
@@ -367,6 +373,28 @@
}
@Test
+ public void testChangeClockDateWeatherEnabled_SetsDateWeatherViewVisibility() {
+ ArgumentCaptor<ClockRegistry.ClockChangeListener> listenerArgumentCaptor =
+ ArgumentCaptor.forClass(ClockRegistry.ClockChangeListener.class);
+ when(mSmartspaceController.isEnabled()).thenReturn(true);
+ when(mSmartspaceController.isDateWeatherDecoupled()).thenReturn(true);
+ when(mSmartspaceController.isWeatherEnabled()).thenReturn(true);
+ mController.init();
+ mExecutor.runAllReady();
+ assertEquals(View.VISIBLE, mFakeDateView.getVisibility());
+
+ when(mSmallClockController.getConfig())
+ .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, true));
+ when(mLargeClockController.getConfig())
+ .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, true));
+ verify(mClockRegistry).registerClockChangeListener(listenerArgumentCaptor.capture());
+ listenerArgumentCaptor.getValue().onCurrentClockChanged();
+
+ mExecutor.runAllReady();
+ assertEquals(View.GONE, mFakeDateView.getVisibility());
+ }
+
+ @Test
public void testGetClock_nullClock_returnsNull() {
when(mClockEventController.getClock()).thenReturn(null);
assertNull(mController.getClock());
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index 48f7d92..f1ee108 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -16,17 +16,15 @@
package com.android.keyguard;
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.keyguard.logging.KeyguardLogger;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.ClockController;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -45,26 +43,21 @@
@RunWith(AndroidTestingRunner.class)
public class KeyguardStatusViewControllerTest extends SysuiTestCase {
- @Mock
- private KeyguardStatusView mKeyguardStatusView;
- @Mock
- private KeyguardSliceViewController mKeyguardSliceViewController;
- @Mock
- private KeyguardClockSwitchController mKeyguardClockSwitchController;
- @Mock
- private KeyguardStateController mKeyguardStateController;
- @Mock
- private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @Mock
- ConfigurationController mConfigurationController;
- @Mock
- DozeParameters mDozeParameters;
- @Mock
- ScreenOffAnimationController mScreenOffAnimationController;
+ @Mock private KeyguardStatusView mKeyguardStatusView;
+ @Mock private KeyguardSliceViewController mKeyguardSliceViewController;
+ @Mock private KeyguardClockSwitchController mKeyguardClockSwitchController;
+ @Mock private KeyguardStateController mKeyguardStateController;
+ @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @Mock private ConfigurationController mConfigurationController;
+ @Mock private DozeParameters mDozeParameters;
+ @Mock private ScreenOffAnimationController mScreenOffAnimationController;
+ @Mock private KeyguardLogger mKeyguardLogger;
+ @Mock private KeyguardStatusViewController mControllerMock;
+ @Mock private FeatureFlags mFeatureFlags;
+ @Mock private InteractionJankMonitor mInteractionJankMonitor;
+
@Captor
private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor;
- @Mock
- KeyguardLogger mKeyguardLogger;
private KeyguardStatusViewController mController;
@@ -81,7 +74,9 @@
mConfigurationController,
mDozeParameters,
mScreenOffAnimationController,
- mKeyguardLogger);
+ mKeyguardLogger,
+ mFeatureFlags,
+ mInteractionJankMonitor);
}
@Test
@@ -116,12 +111,4 @@
configurationListenerArgumentCaptor.getValue().onLocaleListChanged();
verify(mKeyguardClockSwitchController).onLocaleListChanged();
}
-
- @Test
- public void getClock_forwardsToClockSwitch() {
- ClockController mockClock = mock(ClockController.class);
- when(mKeyguardClockSwitchController.getClock()).thenReturn(mockClock);
-
- assertEquals(mockClock, mController.getClockController());
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 08813a7..3eb9590 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -20,9 +20,12 @@
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
+import static android.hardware.biometrics.SensorProperties.STRENGTH_CONVENIENCE;
+import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
import static android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_PRIMARY_BOUNCER_SHOWN;
import static android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_STARTED_WAKING_UP;
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON;
+import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL;
import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE;
import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID;
@@ -41,6 +44,8 @@
import static com.google.common.truth.Truth.assertThat;
+import static junit.framework.Assert.assertEquals;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -79,7 +84,6 @@
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
-import android.hardware.biometrics.SensorProperties;
import android.hardware.face.FaceAuthenticateOptions;
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorProperties;
@@ -283,33 +287,13 @@
@Before
public void setup() throws RemoteException {
MockitoAnnotations.initMocks(this);
-
- mFaceSensorProperties =
- List.of(createFaceSensorProperties(/* supportsFaceDetection = */ false));
- when(mFaceManager.isHardwareDetected()).thenReturn(true);
- when(mAuthController.isFaceAuthEnrolled(anyInt())).thenReturn(true);
- when(mFaceManager.getSensorPropertiesInternal()).thenReturn(mFaceSensorProperties);
when(mSessionTracker.getSessionId(SESSION_KEYGUARD)).thenReturn(mKeyguardInstanceId);
- mFingerprintSensorProperties = List.of(
- new FingerprintSensorPropertiesInternal(1 /* sensorId */,
- FingerprintSensorProperties.STRENGTH_STRONG,
- 1 /* maxEnrollmentsPerUser */,
- List.of(new ComponentInfoInternal("fingerprintSensor" /* componentId */,
- "vendor/model/revision" /* hardwareVersion */,
- "1.01" /* firmwareVersion */,
- "00000001" /* serialNumber */, "" /* softwareVersion */)),
- FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
- false /* resetLockoutRequiresHAT */));
- when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
- when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
- when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(
- mFingerprintSensorProperties);
when(mUserManager.isUserUnlocked(anyInt())).thenReturn(true);
when(mUserManager.isPrimaryUser()).thenReturn(true);
when(mStrongAuthTracker.getStub()).thenReturn(mock(IStrongAuthTracker.Stub.class));
when(mStrongAuthTracker
- .isUnlockingWithBiometricAllowed(anyBoolean() /* isStrongBiometric */))
+ .isUnlockingWithBiometricAllowed(anyBoolean() /* isClass3Biometric */))
.thenReturn(true);
when(mTelephonyManager.getServiceStateForSubscriber(anyInt()))
.thenReturn(new ServiceState());
@@ -346,20 +330,9 @@
anyInt());
mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
-
- ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback> faceCaptor =
- ArgumentCaptor.forClass(IFaceAuthenticatorsRegisteredCallback.class);
- verify(mFaceManager).addAuthenticatorsRegisteredCallback(faceCaptor.capture());
- mFaceAuthenticatorsRegisteredCallback = faceCaptor.getValue();
- mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(mFaceSensorProperties);
-
- ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> fingerprintCaptor =
- ArgumentCaptor.forClass(IFingerprintAuthenticatorsRegisteredCallback.class);
- verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
- fingerprintCaptor.capture());
- mFingerprintAuthenticatorsRegisteredCallback = fingerprintCaptor.getValue();
- mFingerprintAuthenticatorsRegisteredCallback
- .onAllAuthenticatorsRegistered(mFingerprintSensorProperties);
+ captureAuthenticatorsRegisteredCallbacks();
+ setupFaceAuth(/* isClass3 */ false);
+ setupFingerprintAuth(/* isClass3 */ true);
verify(mBiometricManager)
.registerEnabledOnKeyguardCallback(mBiometricEnabledCallbackArgCaptor.capture());
@@ -381,8 +354,64 @@
when(mAuthController.areAllFingerprintAuthenticatorsRegistered()).thenReturn(true);
}
+ private void captureAuthenticatorsRegisteredCallbacks() throws RemoteException {
+ ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback> faceCaptor =
+ ArgumentCaptor.forClass(IFaceAuthenticatorsRegisteredCallback.class);
+ verify(mFaceManager).addAuthenticatorsRegisteredCallback(faceCaptor.capture());
+ mFaceAuthenticatorsRegisteredCallback = faceCaptor.getValue();
+ mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(mFaceSensorProperties);
+
+ ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> fingerprintCaptor =
+ ArgumentCaptor.forClass(IFingerprintAuthenticatorsRegisteredCallback.class);
+ verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
+ fingerprintCaptor.capture());
+ mFingerprintAuthenticatorsRegisteredCallback = fingerprintCaptor.getValue();
+ mFingerprintAuthenticatorsRegisteredCallback
+ .onAllAuthenticatorsRegistered(mFingerprintSensorProperties);
+ }
+
+ private void setupFaceAuth(boolean isClass3) throws RemoteException {
+ when(mFaceManager.isHardwareDetected()).thenReturn(true);
+ when(mAuthController.isFaceAuthEnrolled(anyInt())).thenReturn(true);
+ mFaceSensorProperties =
+ List.of(createFaceSensorProperties(/* supportsFaceDetection = */ false, isClass3));
+ when(mFaceManager.getSensorPropertiesInternal()).thenReturn(mFaceSensorProperties);
+ mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(mFaceSensorProperties);
+ assertEquals(isClass3, mKeyguardUpdateMonitor.isFaceClass3());
+ }
+
+ private void setupFingerprintAuth(boolean isClass3) throws RemoteException {
+ when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
+ when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
+ mFingerprintSensorProperties = List.of(
+ createFingerprintSensorPropertiesInternal(TYPE_UDFPS_OPTICAL, isClass3));
+ when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(
+ mFingerprintSensorProperties);
+ mFingerprintAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(
+ mFingerprintSensorProperties);
+ assertEquals(isClass3, mKeyguardUpdateMonitor.isFingerprintClass3());
+ }
+
+ private FingerprintSensorPropertiesInternal createFingerprintSensorPropertiesInternal(
+ @FingerprintSensorProperties.SensorType int sensorType,
+ boolean isClass3) {
+ final List<ComponentInfoInternal> componentInfo =
+ List.of(new ComponentInfoInternal("fingerprintSensor" /* componentId */,
+ "vendor/model/revision" /* hardwareVersion */,
+ "1.01" /* firmwareVersion */,
+ "00000001" /* serialNumber */, "" /* softwareVersion */));
+ return new FingerprintSensorPropertiesInternal(
+ FINGERPRINT_SENSOR_ID,
+ isClass3 ? STRENGTH_STRONG : STRENGTH_CONVENIENCE,
+ 1 /* maxEnrollmentsPerUser */,
+ componentInfo,
+ sensorType,
+ true /* resetLockoutRequiresHardwareAuthToken */);
+ }
+
@NonNull
- private FaceSensorPropertiesInternal createFaceSensorProperties(boolean supportsFaceDetection) {
+ private FaceSensorPropertiesInternal createFaceSensorProperties(
+ boolean supportsFaceDetection, boolean isClass3) {
final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */,
"vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
@@ -391,10 +420,9 @@
"" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
"vendor/version/revision" /* softwareVersion */));
-
return new FaceSensorPropertiesInternal(
- 0 /* id */,
- FaceSensorProperties.STRENGTH_STRONG,
+ FACE_SENSOR_ID /* id */,
+ isClass3 ? STRENGTH_STRONG : STRENGTH_CONVENIENCE,
1 /* maxTemplatesAllowed */,
componentInfo,
FaceSensorProperties.TYPE_UNKNOWN,
@@ -686,7 +714,7 @@
@Test
public void testUnlockingWithFaceAllowed_strongAuthTrackerUnlockingWithBiometricAllowed() {
// GIVEN unlocking with biometric is allowed
- strongAuthNotRequired();
+ primaryAuthNotRequiredByStrongAuthTracker();
// THEN unlocking with face and fp is allowed
Assert.assertTrue(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
@@ -706,12 +734,15 @@
}
@Test
- public void testUnlockingWithFaceAllowed_fingerprintLockout() {
- // GIVEN unlocking with biometric is allowed
- strongAuthNotRequired();
+ public void class3FingerprintLockOut_lockOutClass1Face() throws RemoteException {
+ setupFaceAuth(/* isClass3 */ false);
+ setupFingerprintAuth(/* isClass3 */ true);
- // WHEN fingerprint is locked out
- fingerprintErrorTemporaryLockedOut();
+ // GIVEN primary auth is not required by StrongAuthTracker
+ primaryAuthNotRequiredByStrongAuthTracker();
+
+ // WHEN fingerprint (class 3) is lock out
+ fingerprintErrorTemporaryLockOut();
// THEN unlocking with face is not allowed
Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
@@ -719,6 +750,54 @@
}
@Test
+ public void class3FingerprintLockOut_lockOutClass3Face() throws RemoteException {
+ setupFaceAuth(/* isClass3 */ true);
+ setupFingerprintAuth(/* isClass3 */ true);
+
+ // GIVEN primary auth is not required by StrongAuthTracker
+ primaryAuthNotRequiredByStrongAuthTracker();
+
+ // WHEN fingerprint (class 3) is lock out
+ fingerprintErrorTemporaryLockOut();
+
+ // THEN unlocking with face is not allowed
+ Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ BiometricSourceType.FACE));
+ }
+
+ @Test
+ public void class3FaceLockOut_lockOutClass3Fingerprint() throws RemoteException {
+ setupFaceAuth(/* isClass3 */ true);
+ setupFingerprintAuth(/* isClass3 */ true);
+
+ // GIVEN primary auth is not required by StrongAuthTracker
+ primaryAuthNotRequiredByStrongAuthTracker();
+
+ // WHEN face (class 3) is lock out
+ faceAuthLockOut();
+
+ // THEN unlocking with fingerprint is not allowed
+ Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ BiometricSourceType.FINGERPRINT));
+ }
+
+ @Test
+ public void class1FaceLockOut_doesNotLockOutClass3Fingerprint() throws RemoteException {
+ setupFaceAuth(/* isClass3 */ false);
+ setupFingerprintAuth(/* isClass3 */ true);
+
+ // GIVEN primary auth is not required by StrongAuthTracker
+ primaryAuthNotRequiredByStrongAuthTracker();
+
+ // WHEN face (class 1) is lock out
+ faceAuthLockOut();
+
+ // THEN unlocking with fingerprint is still allowed
+ Assert.assertTrue(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ BiometricSourceType.FINGERPRINT));
+ }
+
+ @Test
public void testUnlockingWithFpAllowed_strongAuthTrackerUnlockingWithBiometricNotAllowed() {
// GIVEN unlocking with biometric is not allowed
when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
@@ -731,10 +810,10 @@
@Test
public void testUnlockingWithFpAllowed_fingerprintLockout() {
// GIVEN unlocking with biometric is allowed
- strongAuthNotRequired();
+ primaryAuthNotRequiredByStrongAuthTracker();
- // WHEN fingerprint is locked out
- fingerprintErrorTemporaryLockedOut();
+ // WHEN fingerprint is lock out
+ fingerprintErrorTemporaryLockOut();
// THEN unlocking with fingerprint is not allowed
Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
@@ -757,8 +836,8 @@
mKeyguardUpdateMonitor.onTrustChanged(true, true, getCurrentUser(), 0, null);
Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser()));
- // WHEN fingerprint is locked out
- fingerprintErrorTemporaryLockedOut();
+ // WHEN fingerprint is lock out
+ fingerprintErrorTemporaryLockOut();
// THEN user is NOT considered as "having trust" and bouncer cannot be skipped
Assert.assertFalse(mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser()));
@@ -826,7 +905,7 @@
// GIVEN udfps is supported and strong auth required for weak biometrics (face) only
givenUdfpsSupported();
- strongAuthRequiredForWeakBiometricOnly(); // this allows fingerprint to run but not face
+ primaryAuthRequiredForWeakBiometricOnly(); // allows class3 fp to run but not class1 face
// WHEN the device wakes up
mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
@@ -863,7 +942,7 @@
public void noFaceDetect_whenStrongAuthRequiredAndBypass_faceDetectionUnsupported() {
// GIVEN bypass is enabled, face detection is NOT supported and strong auth is required
lockscreenBypassIsAllowed();
- strongAuthRequiredEncrypted();
+ primaryAuthRequiredEncrypted();
keyguardIsVisible();
// WHEN the device wakes up
@@ -931,6 +1010,7 @@
assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isFalse();
verifyFingerprintAuthenticateNeverCalled();
// WHEN alternate bouncer is shown
+ mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
mKeyguardUpdateMonitor.setAlternateBouncerShowing(true);
// THEN make sure FP listening begins
@@ -1011,10 +1091,10 @@
@Test
public void testOnFaceAuthenticated_skipsFaceWhenAuthenticated() {
- // test whether face will be skipped if authenticated, so the value of isStrongBiometric
+ // test whether face will be skipped if authenticated, so the value of isClass3Biometric
// doesn't matter here
mKeyguardUpdateMonitor.onFaceAuthenticated(KeyguardUpdateMonitor.getCurrentUser(),
- true /* isStrongBiometric */);
+ true /* isClass3Biometric */);
setKeyguardBouncerVisibility(true);
mTestableLooper.processAllMessages();
@@ -1027,7 +1107,7 @@
mTestableLooper.processAllMessages();
keyguardIsVisible();
- faceAuthLockedOut();
+ faceAuthLockOut();
verify(mLockPatternUtils, never()).requireStrongAuth(anyInt(), anyInt());
}
@@ -1050,7 +1130,7 @@
mTestableLooper.processAllMessages();
keyguardIsVisible();
- faceAuthLockedOut();
+ faceAuthLockOut();
mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback
.onAuthenticationError(FINGERPRINT_ERROR_LOCKOUT_PERMANENT, "");
@@ -1060,32 +1140,32 @@
@Test
public void testGetUserCanSkipBouncer_whenFace() {
int user = KeyguardUpdateMonitor.getCurrentUser();
- mKeyguardUpdateMonitor.onFaceAuthenticated(user, true /* isStrongBiometric */);
+ mKeyguardUpdateMonitor.onFaceAuthenticated(user, true /* isClass3Biometric */);
assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue();
}
@Test
public void testGetUserCanSkipBouncer_whenFace_nonStrongAndDisallowed() {
- when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(false /* isStrongBiometric */))
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(false /* isClass3Biometric */))
.thenReturn(false);
int user = KeyguardUpdateMonitor.getCurrentUser();
- mKeyguardUpdateMonitor.onFaceAuthenticated(user, false /* isStrongBiometric */);
+ mKeyguardUpdateMonitor.onFaceAuthenticated(user, false /* isClass3Biometric */);
assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isFalse();
}
@Test
public void testGetUserCanSkipBouncer_whenFingerprint() {
int user = KeyguardUpdateMonitor.getCurrentUser();
- mKeyguardUpdateMonitor.onFingerprintAuthenticated(user, true /* isStrongBiometric */);
+ mKeyguardUpdateMonitor.onFingerprintAuthenticated(user, true /* isClass3Biometric */);
assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue();
}
@Test
public void testGetUserCanSkipBouncer_whenFingerprint_nonStrongAndDisallowed() {
- when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(false /* isStrongBiometric */))
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(false /* isClass3Biometric */))
.thenReturn(false);
int user = KeyguardUpdateMonitor.getCurrentUser();
- mKeyguardUpdateMonitor.onFingerprintAuthenticated(user, false /* isStrongBiometric */);
+ mKeyguardUpdateMonitor.onFingerprintAuthenticated(user, false /* isClass3Biometric */);
assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isFalse();
}
@@ -1126,9 +1206,9 @@
@BiometricConstants.LockoutMode int fingerprintLockoutMode,
@BiometricConstants.LockoutMode int faceLockoutMode) {
final int newUser = 12;
- final boolean faceLocked =
+ final boolean faceLockOut =
faceLockoutMode != BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
- final boolean fpLocked =
+ final boolean fpLockOut =
fingerprintLockoutMode != BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
@@ -1161,8 +1241,8 @@
eq(false), eq(BiometricSourceType.FINGERPRINT));
// THEN locked out states are updated
- assertThat(mKeyguardUpdateMonitor.isFingerprintLockedOut()).isEqualTo(fpLocked);
- assertThat(mKeyguardUpdateMonitor.isFaceLockedOut()).isEqualTo(faceLocked);
+ assertThat(mKeyguardUpdateMonitor.isFingerprintLockedOut()).isEqualTo(fpLockOut);
+ assertThat(mKeyguardUpdateMonitor.isFaceLockedOut()).isEqualTo(faceLockOut);
// Fingerprint should be cancelled on lockout if going to lockout state, else
// restarted if it's not
@@ -1443,7 +1523,8 @@
throws RemoteException {
// GIVEN SFPS supported and enrolled
final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
- props.add(newFingerprintSensorPropertiesInternal(TYPE_POWER_BUTTON));
+ props.add(createFingerprintSensorPropertiesInternal(TYPE_POWER_BUTTON,
+ /* isClass3 */ true));
when(mAuthController.getSfpsProps()).thenReturn(props);
when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
@@ -1466,17 +1547,6 @@
assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue();
}
- private FingerprintSensorPropertiesInternal newFingerprintSensorPropertiesInternal(
- @FingerprintSensorProperties.SensorType int sensorType) {
- return new FingerprintSensorPropertiesInternal(
- 0 /* sensorId */,
- SensorProperties.STRENGTH_STRONG,
- 1 /* maxEnrollmentsPerUser */,
- new ArrayList<ComponentInfoInternal>(),
- sensorType,
- true /* resetLockoutRequiresHardwareAuthToken */);
- }
-
@Test
public void testShouldNotListenForUdfps_whenTrustEnabled() {
// GIVEN a "we should listen for udfps" state
@@ -1613,7 +1683,7 @@
keyguardNotGoingAway();
occludingAppRequestsFaceAuth();
currentUserIsPrimary();
- strongAuthNotRequired();
+ primaryAuthNotRequiredByStrongAuthTracker();
biometricsEnabledForCurrentUser();
currentUserDoesNotHaveTrust();
biometricsNotDisabledThroughDevicePolicyManager();
@@ -1622,7 +1692,7 @@
assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
// Fingerprint is locked out.
- fingerprintErrorTemporaryLockedOut();
+ fingerprintErrorTemporaryLockOut();
assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
}
@@ -1634,7 +1704,7 @@
bouncerFullyVisibleAndNotGoingToSleep();
keyguardNotGoingAway();
currentUserIsPrimary();
- strongAuthNotRequired();
+ primaryAuthNotRequiredByStrongAuthTracker();
biometricsEnabledForCurrentUser();
currentUserDoesNotHaveTrust();
biometricsNotDisabledThroughDevicePolicyManager();
@@ -1657,7 +1727,7 @@
bouncerFullyVisibleAndNotGoingToSleep();
keyguardNotGoingAway();
currentUserIsPrimary();
- strongAuthNotRequired();
+ primaryAuthNotRequiredByStrongAuthTracker();
biometricsEnabledForCurrentUser();
currentUserDoesNotHaveTrust();
biometricsNotDisabledThroughDevicePolicyManager();
@@ -1684,7 +1754,7 @@
// Face auth should run when the following is true.
keyguardNotGoingAway();
bouncerFullyVisibleAndNotGoingToSleep();
- strongAuthNotRequired();
+ primaryAuthNotRequiredByStrongAuthTracker();
biometricsEnabledForCurrentUser();
currentUserDoesNotHaveTrust();
biometricsNotDisabledThroughDevicePolicyManager();
@@ -1940,7 +2010,7 @@
assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
// Face is locked out.
- faceAuthLockedOut();
+ faceAuthLockOut();
mTestableLooper.processAllMessages();
// This is needed beccause we want to show face locked out error message whenever face auth
@@ -2578,7 +2648,7 @@
verify(mFingerprintManager).addLockoutResetCallback(fpLockoutResetCallbackCaptor.capture());
// GIVEN device is locked out
- fingerprintErrorTemporaryLockedOut();
+ fingerprintErrorTemporaryLockOut();
// GIVEN FP detection is running
givenDetectFingerprintWithClearingFingerprintManagerInvocations();
@@ -2662,6 +2732,21 @@
KeyguardUpdateMonitor.getCurrentUser())).isTrue();
}
+ @Test
+ public void testFingerprintListeningStateWhenOccluded() {
+ when(mAuthController.isUdfpsSupported()).thenReturn(true);
+
+ mKeyguardUpdateMonitor.setKeyguardShowing(false, false);
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_BIOMETRIC);
+ mKeyguardUpdateMonitor.setKeyguardShowing(false, true);
+
+ verifyFingerprintAuthenticateNeverCalled();
+
+ mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
+ mKeyguardUpdateMonitor.setAlternateBouncerShowing(true);
+
+ verifyFingerprintAuthenticateCall();
+ }
private void verifyFingerprintAuthenticateNeverCalled() {
verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), any());
@@ -2705,8 +2790,10 @@
}
private void supportsFaceDetection() throws RemoteException {
+ final boolean isClass3 = !mFaceSensorProperties.isEmpty()
+ && mFaceSensorProperties.get(0).sensorStrength == STRENGTH_STRONG;
mFaceSensorProperties =
- List.of(createFaceSensorProperties(/* supportsFaceDetection = */ true));
+ List.of(createFaceSensorProperties(/* supportsFaceDetection = */ true, isClass3));
mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(mFaceSensorProperties);
}
@@ -2734,7 +2821,7 @@
}
}
- private void faceAuthLockedOut() {
+ private void faceAuthLockOut() {
mKeyguardUpdateMonitor.mFaceAuthenticationCallback
.onAuthenticationError(FaceManager.FACE_ERROR_LOCKOUT_PERMANENT, "");
}
@@ -2767,7 +2854,7 @@
mKeyguardUpdateMonitor.setSwitchingUser(true);
}
- private void fingerprintErrorTemporaryLockedOut() {
+ private void fingerprintErrorTemporaryLockOut() {
mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback
.onAuthenticationError(FINGERPRINT_ERROR_LOCKOUT, "Fingerprint locked out");
}
@@ -2821,18 +2908,18 @@
);
}
- private void strongAuthRequiredEncrypted() {
+ private void primaryAuthRequiredEncrypted() {
when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser()))
.thenReturn(STRONG_AUTH_REQUIRED_AFTER_BOOT);
when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
}
- private void strongAuthRequiredForWeakBiometricOnly() {
+ private void primaryAuthRequiredForWeakBiometricOnly() {
when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(eq(true))).thenReturn(true);
when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(eq(false))).thenReturn(false);
}
- private void strongAuthNotRequired() {
+ private void primaryAuthNotRequiredByStrongAuthTracker() {
when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser()))
.thenReturn(0);
when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
@@ -2917,10 +3004,10 @@
}
private void givenDetectFace() throws RemoteException {
- // GIVEN bypass is enabled, face detection is supported and strong auth is required
+ // GIVEN bypass is enabled, face detection is supported and primary auth is required
lockscreenBypassIsAllowed();
supportsFaceDetection();
- strongAuthRequiredEncrypted();
+ primaryAuthRequiredEncrypted();
keyguardIsVisible();
// fingerprint is NOT running, UDFPS is NOT supported
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/SplitShadeTransitionAdapterTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt
similarity index 85%
rename from packages/SystemUI/tests/src/com/android/systemui/shade/SplitShadeTransitionAdapterTest.kt
rename to packages/SystemUI/tests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt
index 64fec5b..dea2082 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/SplitShadeTransitionAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt
@@ -13,15 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.shade
+package com.android.keyguard
import android.animation.Animator
import android.testing.AndroidTestingRunner
import android.transition.TransitionValues
import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardStatusViewController
+import com.android.keyguard.KeyguardStatusViewController.SplitShadeTransitionAdapter
import com.android.systemui.SysuiTestCase
-import com.android.systemui.shade.NotificationPanelViewController.SplitShadeTransitionAdapter
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
@@ -33,14 +32,14 @@
@RunWith(AndroidTestingRunner::class)
class SplitShadeTransitionAdapterTest : SysuiTestCase() {
- @Mock private lateinit var keyguardStatusViewController: KeyguardStatusViewController
+ @Mock private lateinit var KeyguardClockSwitchController: KeyguardClockSwitchController
private lateinit var adapter: SplitShadeTransitionAdapter
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- adapter = SplitShadeTransitionAdapter(keyguardStatusViewController)
+ adapter = SplitShadeTransitionAdapter(KeyguardClockSwitchController)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ChooserPinMigrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/ChooserPinMigrationTest.kt
deleted file mode 100644
index 44da5f4..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/ChooserPinMigrationTest.kt
+++ /dev/null
@@ -1,255 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui
-
-import android.content.Context
-import android.content.Intent
-import android.content.SharedPreferences
-import android.content.res.Resources
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.broadcast.BroadcastSender
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.kotlinArgumentCaptor
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import java.io.File
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.MockitoAnnotations
-
-@RunWith(AndroidTestingRunner::class)
-@SmallTest
-class ChooserPinMigrationTest : SysuiTestCase() {
-
- private val fakeFeatureFlags = FakeFeatureFlags()
- private val fakePreferences =
- mutableMapOf(
- "TestPinnedPackage/TestPinnedClass" to true,
- "TestUnpinnedPackage/TestUnpinnedClass" to false,
- )
- private val intent = kotlinArgumentCaptor<Intent>()
- private val permission = kotlinArgumentCaptor<String>()
-
- private lateinit var chooserPinMigration: ChooserPinMigration
-
- @Mock private lateinit var mockContext: Context
- @Mock private lateinit var mockResources: Resources
- @Mock
- private lateinit var mockLegacyPinPrefsFileSupplier:
- ChooserPinMigration.Companion.LegacyPinPrefsFileSupplier
- @Mock private lateinit var mockFile: File
- @Mock private lateinit var mockSharedPreferences: SharedPreferences
- @Mock private lateinit var mockSharedPreferencesEditor: SharedPreferences.Editor
- @Mock private lateinit var mockBroadcastSender: BroadcastSender
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
-
- whenever(mockContext.resources).thenReturn(mockResources)
- whenever(mockContext.getSharedPreferences(any<File>(), anyInt()))
- .thenReturn(mockSharedPreferences)
- whenever(mockResources.getString(anyInt())).thenReturn("TestPackage/TestClass")
- whenever(mockSharedPreferences.all).thenReturn(fakePreferences)
- whenever(mockSharedPreferences.edit()).thenReturn(mockSharedPreferencesEditor)
- whenever(mockSharedPreferencesEditor.commit()).thenReturn(true)
- whenever(mockLegacyPinPrefsFileSupplier.get()).thenReturn(mockFile)
- whenever(mockFile.exists()).thenReturn(true)
- whenever(mockFile.delete()).thenReturn(true)
- fakeFeatureFlags.set(Flags.CHOOSER_MIGRATION_ENABLED, true)
- }
-
- @Test
- fun start_performsMigration() {
- // Arrange
- chooserPinMigration =
- ChooserPinMigration(
- mockContext,
- fakeFeatureFlags,
- mockBroadcastSender,
- mockLegacyPinPrefsFileSupplier,
- )
-
- // Act
- chooserPinMigration.start()
-
- // Assert
- verify(mockBroadcastSender).sendBroadcast(intent.capture(), permission.capture())
- assertThat(intent.value.action).isEqualTo("android.intent.action.CHOOSER_PIN_MIGRATION")
- assertThat(intent.value.`package`).isEqualTo("TestPackage")
- assertThat(intent.value.extras?.keySet()).hasSize(2)
- assertThat(intent.value.hasExtra("TestPinnedPackage/TestPinnedClass")).isTrue()
- assertThat(intent.value.getBooleanExtra("TestPinnedPackage/TestPinnedClass", false))
- .isTrue()
- assertThat(intent.value.hasExtra("TestUnpinnedPackage/TestUnpinnedClass")).isTrue()
- assertThat(intent.value.getBooleanExtra("TestUnpinnedPackage/TestUnpinnedClass", true))
- .isFalse()
- assertThat(permission.value).isEqualTo("android.permission.RECEIVE_CHOOSER_PIN_MIGRATION")
-
- // Assert
- verify(mockSharedPreferencesEditor).clear()
- verify(mockSharedPreferencesEditor).commit()
-
- // Assert
- verify(mockFile).delete()
- }
-
- @Test
- fun start_doesNotDeleteLegacyPreferencesFile_whenClearingItFails() {
- // Arrange
- whenever(mockSharedPreferencesEditor.commit()).thenReturn(false)
- chooserPinMigration =
- ChooserPinMigration(
- mockContext,
- fakeFeatureFlags,
- mockBroadcastSender,
- mockLegacyPinPrefsFileSupplier,
- )
-
- // Act
- chooserPinMigration.start()
-
- // Assert
- verify(mockBroadcastSender).sendBroadcast(intent.capture(), permission.capture())
- assertThat(intent.value.action).isEqualTo("android.intent.action.CHOOSER_PIN_MIGRATION")
- assertThat(intent.value.`package`).isEqualTo("TestPackage")
- assertThat(intent.value.extras?.keySet()).hasSize(2)
- assertThat(intent.value.hasExtra("TestPinnedPackage/TestPinnedClass")).isTrue()
- assertThat(intent.value.getBooleanExtra("TestPinnedPackage/TestPinnedClass", false))
- .isTrue()
- assertThat(intent.value.hasExtra("TestUnpinnedPackage/TestUnpinnedClass")).isTrue()
- assertThat(intent.value.getBooleanExtra("TestUnpinnedPackage/TestUnpinnedClass", true))
- .isFalse()
- assertThat(permission.value).isEqualTo("android.permission.RECEIVE_CHOOSER_PIN_MIGRATION")
-
- // Assert
- verify(mockSharedPreferencesEditor).clear()
- verify(mockSharedPreferencesEditor).commit()
-
- // Assert
- verify(mockFile, never()).delete()
- }
-
- @Test
- fun start_OnlyDeletesLegacyPreferencesFile_whenEmpty() {
- // Arrange
- whenever(mockSharedPreferences.all).thenReturn(emptyMap())
- chooserPinMigration =
- ChooserPinMigration(
- mockContext,
- fakeFeatureFlags,
- mockBroadcastSender,
- mockLegacyPinPrefsFileSupplier,
- )
-
- // Act
- chooserPinMigration.start()
-
- // Assert
- verifyZeroInteractions(mockBroadcastSender)
-
- // Assert
- verifyZeroInteractions(mockSharedPreferencesEditor)
-
- // Assert
- verify(mockFile).delete()
- }
-
- @Test
- fun start_DoesNotDoMigration_whenFlagIsDisabled() {
- // Arrange
- fakeFeatureFlags.set(Flags.CHOOSER_MIGRATION_ENABLED, false)
- chooserPinMigration =
- ChooserPinMigration(
- mockContext,
- fakeFeatureFlags,
- mockBroadcastSender,
- mockLegacyPinPrefsFileSupplier,
- )
-
- // Act
- chooserPinMigration.start()
-
- // Assert
- verifyZeroInteractions(mockBroadcastSender)
-
- // Assert
- verifyZeroInteractions(mockSharedPreferencesEditor)
-
- // Assert
- verify(mockFile, never()).delete()
- }
-
- @Test
- fun start_DoesNotDoMigration_whenLegacyPreferenceFileNotPresent() {
- // Arrange
- whenever(mockFile.exists()).thenReturn(false)
- chooserPinMigration =
- ChooserPinMigration(
- mockContext,
- fakeFeatureFlags,
- mockBroadcastSender,
- mockLegacyPinPrefsFileSupplier,
- )
-
- // Act
- chooserPinMigration.start()
-
- // Assert
- verifyZeroInteractions(mockBroadcastSender)
-
- // Assert
- verifyZeroInteractions(mockSharedPreferencesEditor)
-
- // Assert
- verify(mockFile, never()).delete()
- }
-
- @Test
- fun start_DoesNotDoMigration_whenConfiguredChooserComponentIsInvalid() {
- // Arrange
- whenever(mockResources.getString(anyInt())).thenReturn("InvalidComponent")
- chooserPinMigration =
- ChooserPinMigration(
- mockContext,
- fakeFeatureFlags,
- mockBroadcastSender,
- mockLegacyPinPrefsFileSupplier,
- )
-
- // Act
- chooserPinMigration.start()
-
- // Assert
- verifyZeroInteractions(mockBroadcastSender)
-
- // Assert
- verifyZeroInteractions(mockSharedPreferencesEditor)
-
- // Assert
- verify(mockFile, never()).delete()
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
new file mode 100644
index 0000000..01d3a39
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
@@ -0,0 +1,119 @@
+/*
+ * 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
+
+import android.graphics.Point
+import android.hardware.display.DisplayManagerGlobal
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.Display
+import android.view.DisplayAdjustments
+import android.view.DisplayInfo
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.biometrics.AuthController
+import com.android.systemui.decor.FaceScanningProviderFactory
+import com.android.systemui.dump.logcatLogBuffer
+import com.android.systemui.log.ScreenDecorationsLogger
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Executor
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
+
+@RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class FaceScanningProviderFactoryTest : SysuiTestCase() {
+
+ private lateinit var underTest: FaceScanningProviderFactory
+
+ @Mock private lateinit var authController: AuthController
+
+ @Mock private lateinit var statusBarStateController: StatusBarStateController
+
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+
+ @Mock private lateinit var display: Display
+
+ private val displayId = 2
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ val displayInfo = DisplayInfo()
+ val dmGlobal = mock(DisplayManagerGlobal::class.java)
+ val display =
+ Display(
+ dmGlobal,
+ displayId,
+ displayInfo,
+ DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS
+ )
+ whenever(dmGlobal.getDisplayInfo(eq(displayId))).thenReturn(displayInfo)
+ val displayContext = context.createDisplayContext(display) as SysuiTestableContext
+ displayContext.orCreateTestableResources.addOverride(
+ R.array.config_displayUniqueIdArray,
+ arrayOf(displayId)
+ )
+ displayContext.orCreateTestableResources.addOverride(
+ R.bool.config_fillMainBuiltInDisplayCutout,
+ true
+ )
+ underTest =
+ FaceScanningProviderFactory(
+ authController,
+ displayContext,
+ statusBarStateController,
+ keyguardUpdateMonitor,
+ mock(Executor::class.java),
+ ScreenDecorationsLogger(logcatLogBuffer("FaceScanningProviderFactoryTest"))
+ )
+
+ whenever(authController.faceSensorLocation).thenReturn(Point(10, 10))
+ }
+
+ @Test
+ fun shouldNotShowFaceScanningAnimationIfFaceIsNotEnrolled() {
+ whenever(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(false)
+ whenever(authController.isShowing).thenReturn(true)
+
+ assertThat(underTest.shouldShowFaceScanningAnim()).isFalse()
+ }
+
+ @Test
+ fun shouldShowFaceScanningAnimationIfBiometricPromptIsShowing() {
+ whenever(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(true)
+ whenever(authController.isShowing).thenReturn(true)
+
+ assertThat(underTest.shouldShowFaceScanningAnim()).isTrue()
+ }
+
+ @Test
+ fun shouldShowFaceScanningAnimationIfKeyguardFaceDetectionIsShowing() {
+ whenever(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(true)
+ whenever(keyguardUpdateMonitor.isFaceDetectionRunning).thenReturn(true)
+
+ assertThat(underTest.shouldShowFaceScanningAnim()).isTrue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 6e002f5..2489e04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -59,11 +59,10 @@
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.util.mockito.KotlinArgumentCaptor
+import com.android.systemui.util.mockito.captureMany
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.SystemClock
import com.google.common.truth.Truth.assertThat
-import java.io.PrintWriter
-import java.io.StringWriter
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -81,6 +80,7 @@
import org.mockito.ArgumentMatchers.eq
import org.mockito.Captor
import org.mockito.Mock
+import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.isNull
import org.mockito.Mockito.mock
@@ -88,6 +88,8 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations
+import java.io.PrintWriter
+import java.io.StringWriter
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -120,6 +122,7 @@
private lateinit var authStatus: FlowValue<AuthenticationStatus?>
private lateinit var detectStatus: FlowValue<DetectionStatus?>
private lateinit var authRunning: FlowValue<Boolean?>
+ private lateinit var bypassEnabled: FlowValue<Boolean?>
private lateinit var lockedOut: FlowValue<Boolean?>
private lateinit var canFaceAuthRun: FlowValue<Boolean?>
private lateinit var authenticated: FlowValue<Boolean?>
@@ -726,6 +729,23 @@
}
@Test
+ fun isBypassEnabledReflectsBypassControllerState() =
+ testScope.runTest {
+ initCollectors()
+ runCurrent()
+ val listeners = captureMany {
+ verify(bypassController, atLeastOnce())
+ .registerOnBypassStateChangedListener(capture())
+ }
+
+ listeners.forEach { it.onBypassStateChanged(true) }
+ assertThat(bypassEnabled()).isTrue()
+
+ listeners.forEach { it.onBypassStateChanged(false) }
+ assertThat(bypassEnabled()).isFalse()
+ }
+
+ @Test
fun detectDoesNotRunWhenNonStrongBiometricIsAllowed() =
testScope.runTest {
testGatingCheckForDetect {
@@ -844,6 +864,7 @@
lockedOut = collectLastValue(underTest.isLockedOut)
canFaceAuthRun = collectLastValue(underTest.canRunFaceAuth)
authenticated = collectLastValue(underTest.isAuthenticated)
+ bypassEnabled = collectLastValue(underTest.isBypassEnabled)
fakeUserRepository.setSelectedUserInfo(primaryUser)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index aace566..1e465c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -24,6 +24,7 @@
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
+import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
@@ -42,6 +43,7 @@
import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import android.util.TypedValue
import android.view.View
import android.view.ViewGroup
import android.view.animation.Interpolator
@@ -101,7 +103,6 @@
import junit.framework.Assert.assertTrue
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
@@ -2200,8 +2201,7 @@
}
@Test
- @Ignore("b/276920368")
- fun bindRecommendation_carouselNotFitThreeRecs() {
+ fun bindRecommendation_carouselNotFitThreeRecs_OrientationPortrait() {
useRealConstraintSets()
setupUpdatedRecommendationViewHolder()
val albumArt = getColorIcon(Color.RED)
@@ -2229,16 +2229,84 @@
// set the screen width less than the width of media controls.
player.context.resources.configuration.screenWidthDp = 350
+ player.context.resources.configuration.orientation = Configuration.ORIENTATION_PORTRAIT
player.attachRecommendation(recommendationViewHolder)
player.bindRecommendation(data)
- assertThat(player.numberOfFittedRecommendations).isEqualTo(2)
- assertThat(expandedSet.getVisibility(coverContainer1.id)).isEqualTo(ConstraintSet.VISIBLE)
- assertThat(collapsedSet.getVisibility(coverContainer1.id)).isEqualTo(ConstraintSet.VISIBLE)
- assertThat(expandedSet.getVisibility(coverContainer2.id)).isEqualTo(ConstraintSet.VISIBLE)
- assertThat(collapsedSet.getVisibility(coverContainer2.id)).isEqualTo(ConstraintSet.VISIBLE)
- assertThat(expandedSet.getVisibility(coverContainer3.id)).isEqualTo(ConstraintSet.GONE)
- assertThat(collapsedSet.getVisibility(coverContainer3.id)).isEqualTo(ConstraintSet.GONE)
+ val res = player.context.resources
+ val displayAvailableWidth =
+ TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 350f, res.displayMetrics).toInt()
+ val recCoverWidth: Int =
+ (res.getDimensionPixelSize(R.dimen.qs_media_rec_album_width) +
+ res.getDimensionPixelSize(R.dimen.qs_media_info_spacing) * 2)
+ val numOfRecs = displayAvailableWidth / recCoverWidth
+
+ assertThat(player.numberOfFittedRecommendations).isEqualTo(numOfRecs)
+ recommendationViewHolder.mediaCoverContainers.forEachIndexed { index, container ->
+ if (index < numOfRecs) {
+ assertThat(expandedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.VISIBLE)
+ assertThat(collapsedSet.getVisibility(container.id))
+ .isEqualTo(ConstraintSet.VISIBLE)
+ } else {
+ assertThat(expandedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.GONE)
+ assertThat(collapsedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.GONE)
+ }
+ }
+ }
+
+ @Test
+ fun bindRecommendation_carouselNotFitThreeRecs_OrientationLandscape() {
+ useRealConstraintSets()
+ setupUpdatedRecommendationViewHolder()
+ val albumArt = getColorIcon(Color.RED)
+ val data =
+ smartspaceData.copy(
+ recommendations =
+ listOf(
+ SmartspaceAction.Builder("id1", "title1")
+ .setSubtitle("subtitle1")
+ .setIcon(albumArt)
+ .setExtras(Bundle.EMPTY)
+ .build(),
+ SmartspaceAction.Builder("id2", "title2")
+ .setSubtitle("subtitle1")
+ .setIcon(albumArt)
+ .setExtras(Bundle.EMPTY)
+ .build(),
+ SmartspaceAction.Builder("id3", "title3")
+ .setSubtitle("subtitle1")
+ .setIcon(albumArt)
+ .setExtras(Bundle.EMPTY)
+ .build()
+ )
+ )
+
+ // set the screen width less than the width of media controls.
+ // We should have dp width less than 378 to test. In landscape we should have 2x.
+ player.context.resources.configuration.screenWidthDp = 700
+ player.context.resources.configuration.orientation = Configuration.ORIENTATION_LANDSCAPE
+ player.attachRecommendation(recommendationViewHolder)
+ player.bindRecommendation(data)
+
+ val res = player.context.resources
+ val displayAvailableWidth =
+ TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 350f, res.displayMetrics).toInt()
+ val recCoverWidth: Int =
+ (res.getDimensionPixelSize(R.dimen.qs_media_rec_album_width) +
+ res.getDimensionPixelSize(R.dimen.qs_media_info_spacing) * 2)
+ val numOfRecs = displayAvailableWidth / recCoverWidth
+
+ assertThat(player.numberOfFittedRecommendations).isEqualTo(numOfRecs)
+ recommendationViewHolder.mediaCoverContainers.forEachIndexed { index, container ->
+ if (index < numOfRecs) {
+ assertThat(expandedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.VISIBLE)
+ assertThat(collapsedSet.getVisibility(container.id))
+ .isEqualTo(ConstraintSet.VISIBLE)
+ } else {
+ assertThat(expandedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.GONE)
+ assertThat(collapsedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.GONE)
+ }
+ }
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index 34d2b14..aa92177 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -51,8 +51,10 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingManagerFake;
-import com.android.systemui.dump.DumpManager;
import com.android.systemui.dump.nano.SystemUIProtoDump;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.qs.QSFactory;
@@ -62,7 +64,6 @@
import com.android.systemui.qs.external.CustomTileStatePersister;
import com.android.systemui.qs.external.TileLifecycleManager;
import com.android.systemui.qs.external.TileServiceKey;
-import com.android.systemui.qs.external.TileServiceRequestController;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.settings.UserFileManager;
@@ -110,8 +111,6 @@
@Mock
private Provider<AutoTileManager> mAutoTiles;
@Mock
- private DumpManager mDumpManager;
- @Mock
private CentralSurfaces mCentralSurfaces;
@Mock
private QSLogger mQSLogger;
@@ -125,10 +124,6 @@
@Mock
private CustomTileStatePersister mCustomTileStatePersister;
@Mock
- private TileServiceRequestController.Builder mTileServiceRequestControllerBuilder;
- @Mock
- private TileServiceRequestController mTileServiceRequestController;
- @Mock
private TileLifecycleManager.Factory mTileLifecycleManagerFactory;
@Mock
private TileLifecycleManager mTileLifecycleManager;
@@ -137,6 +132,8 @@
private SparseArray<SharedPreferences> mSharedPreferencesByUser;
+ private FakeFeatureFlags mFeatureFlags;
+
private FakeExecutor mMainExecutor;
private QSTileHost mQSTileHost;
@@ -144,12 +141,13 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ mFeatureFlags = new FakeFeatureFlags();
+
+ mFeatureFlags.set(Flags.QS_PIPELINE_NEW_HOST, false);
+
mMainExecutor = new FakeExecutor(new FakeSystemClock());
mSharedPreferencesByUser = new SparseArray<>();
-
- when(mTileServiceRequestControllerBuilder.create(any()))
- .thenReturn(mTileServiceRequestController);
when(mTileLifecycleManagerFactory.create(any(Intent.class), any(UserHandle.class)))
.thenReturn(mTileLifecycleManager);
when(mUserFileManager.getSharedPreferences(anyString(), anyInt(), anyInt()))
@@ -165,10 +163,9 @@
mSecureSettings = new FakeSettings();
saveSetting("");
mQSTileHost = new TestQSTileHost(mContext, mDefaultFactory, mMainExecutor,
- mPluginManager, mTunerService, mAutoTiles, mDumpManager, mCentralSurfaces,
+ mPluginManager, mTunerService, mAutoTiles, mCentralSurfaces,
mQSLogger, mUiEventLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister,
- mTileServiceRequestControllerBuilder, mTileLifecycleManagerFactory,
- mUserFileManager);
+ mTileLifecycleManagerFactory, mUserFileManager, mFeatureFlags);
mSecureSettings.registerContentObserverForUser(SETTING, new ContentObserver(null) {
@Override
@@ -686,18 +683,16 @@
TestQSTileHost(Context context,
QSFactory defaultFactory, Executor mainExecutor,
PluginManager pluginManager, TunerService tunerService,
- Provider<AutoTileManager> autoTiles, DumpManager dumpManager,
+ Provider<AutoTileManager> autoTiles,
CentralSurfaces centralSurfaces, QSLogger qsLogger, UiEventLogger uiEventLogger,
UserTracker userTracker, SecureSettings secureSettings,
CustomTileStatePersister customTileStatePersister,
- TileServiceRequestController.Builder tileServiceRequestControllerBuilder,
TileLifecycleManager.Factory tileLifecycleManagerFactory,
- UserFileManager userFileManager) {
+ UserFileManager userFileManager, FeatureFlags featureFlags) {
super(context, defaultFactory, mainExecutor, pluginManager,
- tunerService, autoTiles, dumpManager, Optional.of(centralSurfaces), qsLogger,
+ tunerService, autoTiles, Optional.of(centralSurfaces), qsLogger,
uiEventLogger, userTracker, secureSettings, customTileStatePersister,
- tileServiceRequestControllerBuilder, tileLifecycleManagerFactory,
- userFileManager);
+ tileLifecycleManagerFactory, userFileManager, featureFlags);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
index c03849b..50a8d26 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
@@ -170,6 +170,21 @@
}
@Test
+ fun addTileAtPosition_tooLarge_addedAtEnd() =
+ testScope.runTest {
+ val tiles by collectLastValue(underTest.tilesSpecs(0))
+
+ val specs = "a,custom(b/c)"
+ storeTilesForUser(specs, 0)
+
+ underTest.addTile(userId = 0, TileSpec.create("d"), position = 100)
+
+ val expected = "a,custom(b/c),d"
+ assertThat(loadTilesForUser(0)).isEqualTo(expected)
+ assertThat(tiles).isEqualTo(expected.toTileSpecs())
+ }
+
+ @Test
fun addTileForOtherUser_addedInThatUser() =
testScope.runTest {
val tilesUser0 by collectLastValue(underTest.tilesSpecs(0))
@@ -187,27 +202,27 @@
}
@Test
- fun removeTile() =
+ fun removeTiles() =
testScope.runTest {
val tiles by collectLastValue(underTest.tilesSpecs(0))
storeTilesForUser("a,b", 0)
- underTest.removeTile(userId = 0, TileSpec.create("a"))
+ underTest.removeTiles(userId = 0, listOf(TileSpec.create("a")))
assertThat(loadTilesForUser(0)).isEqualTo("b")
assertThat(tiles).isEqualTo("b".toTileSpecs())
}
@Test
- fun removeTileNotThere_noop() =
+ fun removeTilesNotThere_noop() =
testScope.runTest {
val tiles by collectLastValue(underTest.tilesSpecs(0))
val specs = "a,b"
storeTilesForUser(specs, 0)
- underTest.removeTile(userId = 0, TileSpec.create("c"))
+ underTest.removeTiles(userId = 0, listOf(TileSpec.create("c")))
assertThat(loadTilesForUser(0)).isEqualTo(specs)
assertThat(tiles).isEqualTo(specs.toTileSpecs())
@@ -221,7 +236,7 @@
val specs = "a,b"
storeTilesForUser(specs, 0)
- underTest.removeTile(userId = 0, TileSpec.Invalid)
+ underTest.removeTiles(userId = 0, listOf(TileSpec.Invalid))
assertThat(loadTilesForUser(0)).isEqualTo(specs)
assertThat(tiles).isEqualTo(specs.toTileSpecs())
@@ -237,7 +252,7 @@
storeTilesForUser(specs, 0)
storeTilesForUser(specs, 1)
- underTest.removeTile(userId = 1, TileSpec.create("a"))
+ underTest.removeTiles(userId = 1, listOf(TileSpec.create("a")))
assertThat(loadTilesForUser(0)).isEqualTo(specs)
assertThat(user0Tiles).isEqualTo(specs.toTileSpecs())
@@ -246,6 +261,19 @@
}
@Test
+ fun removeMultipleTiles() =
+ testScope.runTest {
+ val tiles by collectLastValue(underTest.tilesSpecs(0))
+
+ storeTilesForUser("a,b,c,d", 0)
+
+ underTest.removeTiles(userId = 0, listOf(TileSpec.create("a"), TileSpec.create("c")))
+
+ assertThat(loadTilesForUser(0)).isEqualTo("b,d")
+ assertThat(tiles).isEqualTo("b,d".toTileSpecs())
+ }
+
+ @Test
fun changeTiles() =
testScope.runTest {
val tiles by collectLastValue(underTest.tilesSpecs(0))
@@ -310,8 +338,8 @@
storeTilesForUser(specs, 0)
coroutineScope {
- underTest.removeTile(userId = 0, TileSpec.create("c"))
- underTest.removeTile(userId = 0, TileSpec.create("a"))
+ underTest.removeTiles(userId = 0, listOf(TileSpec.create("c")))
+ underTest.removeTiles(userId = 0, listOf(TileSpec.create("a")))
}
assertThat(loadTilesForUser(0)).isEqualTo("b")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
new file mode 100644
index 0000000..7ecb4dc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
@@ -0,0 +1,674 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.interactor
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.UserInfo
+import android.os.UserHandle
+import android.service.quicksettings.Tile
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dump.nano.SystemUIProtoDump
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.plugins.qs.QSTile.BooleanState
+import com.android.systemui.qs.FakeQSFactory
+import com.android.systemui.qs.external.CustomTile
+import com.android.systemui.qs.external.CustomTileStatePersister
+import com.android.systemui.qs.external.TileLifecycleManager
+import com.android.systemui.qs.external.TileServiceKey
+import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository
+import com.android.systemui.qs.pipeline.data.repository.FakeCustomTileAddedRepository
+import com.android.systemui.qs.pipeline.data.repository.FakeTileSpecRepository
+import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
+import com.android.systemui.qs.pipeline.domain.model.TileModel
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.qs.toProto
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import com.google.protobuf.nano.MessageNano
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mock
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class CurrentTilesInteractorImplTest : SysuiTestCase() {
+
+ private val tileSpecRepository: TileSpecRepository = FakeTileSpecRepository()
+ private val userRepository = FakeUserRepository()
+ private val tileFactory = FakeQSFactory(::tileCreator)
+ private val customTileAddedRepository: CustomTileAddedRepository =
+ FakeCustomTileAddedRepository()
+ private val featureFlags = FakeFeatureFlags()
+ private val tileLifecycleManagerFactory = TLMFactory()
+
+ @Mock private lateinit var customTileStatePersister: CustomTileStatePersister
+
+ @Mock private lateinit var userTracker: UserTracker
+
+ @Mock private lateinit var logger: QSPipelineLogger
+
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ private val unavailableTiles = mutableSetOf("e")
+
+ private lateinit var underTest: CurrentTilesInteractorImpl
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ featureFlags.set(Flags.QS_PIPELINE_NEW_HOST, true)
+
+ userRepository.setUserInfos(listOf(USER_INFO_0, USER_INFO_1))
+ setUserTracker(0)
+
+ underTest =
+ CurrentTilesInteractorImpl(
+ tileSpecRepository = tileSpecRepository,
+ userRepository = userRepository,
+ customTileStatePersister = customTileStatePersister,
+ tileFactory = tileFactory,
+ customTileAddedRepository = customTileAddedRepository,
+ tileLifecycleManagerFactory = tileLifecycleManagerFactory,
+ userTracker = userTracker,
+ mainDispatcher = testDispatcher,
+ backgroundDispatcher = testDispatcher,
+ scope = testScope.backgroundScope,
+ logger = logger,
+ featureFlags = featureFlags,
+ )
+ }
+
+ @Test
+ fun initialState() =
+ testScope.runTest(USER_INFO_0) {
+ assertThat(underTest.currentTiles.value).isEmpty()
+ assertThat(underTest.currentQSTiles).isEmpty()
+ assertThat(underTest.currentTilesSpecs).isEmpty()
+ assertThat(underTest.userId.value).isEqualTo(0)
+ assertThat(underTest.userContext.value.userId).isEqualTo(0)
+ }
+
+ @Test
+ fun correctTiles() =
+ testScope.runTest(USER_INFO_0) {
+ val tiles by collectLastValue(underTest.currentTiles)
+
+ val specs =
+ listOf(
+ TileSpec.create("a"),
+ TileSpec.create("e"),
+ CUSTOM_TILE_SPEC,
+ TileSpec.create("d"),
+ TileSpec.create("non_existent")
+ )
+ tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+
+ // check each tile
+
+ // Tile a
+ val tile0 = tiles!![0]
+ assertThat(tile0.spec).isEqualTo(specs[0])
+ assertThat(tile0.tile.tileSpec).isEqualTo(specs[0].spec)
+ assertThat(tile0.tile).isInstanceOf(FakeQSTile::class.java)
+ assertThat(tile0.tile.isAvailable).isTrue()
+
+ // Tile e is not available and is not in the list
+
+ // Custom Tile
+ val tile1 = tiles!![1]
+ assertThat(tile1.spec).isEqualTo(specs[2])
+ assertThat(tile1.tile.tileSpec).isEqualTo(specs[2].spec)
+ assertThat(tile1.tile).isInstanceOf(CustomTile::class.java)
+ assertThat(tile1.tile.isAvailable).isTrue()
+
+ // Tile d
+ val tile2 = tiles!![2]
+ assertThat(tile2.spec).isEqualTo(specs[3])
+ assertThat(tile2.tile.tileSpec).isEqualTo(specs[3].spec)
+ assertThat(tile2.tile).isInstanceOf(FakeQSTile::class.java)
+ assertThat(tile2.tile.isAvailable).isTrue()
+
+ // Tile non-existent shouldn't be created. Therefore, only 3 tiles total
+ assertThat(tiles?.size).isEqualTo(3)
+ }
+
+ @Test
+ fun logTileCreated() =
+ testScope.runTest(USER_INFO_0) {
+ val specs =
+ listOf(
+ TileSpec.create("a"),
+ CUSTOM_TILE_SPEC,
+ )
+ tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+ runCurrent()
+
+ specs.forEach { verify(logger).logTileCreated(it) }
+ }
+
+ @Test
+ fun logTileNotFoundInFactory() =
+ testScope.runTest(USER_INFO_0) {
+ val specs =
+ listOf(
+ TileSpec.create("non_existing"),
+ )
+ tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+ runCurrent()
+
+ verify(logger, never()).logTileCreated(any())
+ verify(logger).logTileNotFoundInFactory(specs[0])
+ }
+
+ @Test
+ fun tileNotAvailableDestroyed_logged() =
+ testScope.runTest(USER_INFO_0) {
+ val specs =
+ listOf(
+ TileSpec.create("e"),
+ )
+ tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+ runCurrent()
+
+ verify(logger, never()).logTileCreated(any())
+ verify(logger)
+ .logTileDestroyed(
+ specs[0],
+ QSPipelineLogger.TileDestroyedReason.NEW_TILE_NOT_AVAILABLE
+ )
+ }
+
+ @Test
+ fun someTilesNotValid_repositorySetToDefinitiveList() =
+ testScope.runTest(USER_INFO_0) {
+ val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
+
+ val specs =
+ listOf(
+ TileSpec.create("a"),
+ TileSpec.create("e"),
+ )
+ tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+
+ assertThat(tiles).isEqualTo(listOf(TileSpec.create("a")))
+ }
+
+ @Test
+ fun deduplicatedTiles() =
+ testScope.runTest(USER_INFO_0) {
+ val tiles by collectLastValue(underTest.currentTiles)
+
+ val specs = listOf(TileSpec.create("a"), TileSpec.create("a"))
+
+ tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+
+ assertThat(tiles?.size).isEqualTo(1)
+ assertThat(tiles!![0].spec).isEqualTo(specs[0])
+ }
+
+ @Test
+ fun tilesChange_platformTileNotRecreated() =
+ testScope.runTest(USER_INFO_0) {
+ val tiles by collectLastValue(underTest.currentTiles)
+
+ val specs =
+ listOf(
+ TileSpec.create("a"),
+ )
+
+ tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+ val originalTileA = tiles!![0].tile
+
+ tileSpecRepository.addTile(USER_INFO_0.id, TileSpec.create("b"))
+
+ assertThat(tiles?.size).isEqualTo(2)
+ assertThat(tiles!![0].tile).isSameInstanceAs(originalTileA)
+ }
+
+ @Test
+ fun tileRemovedIsDestroyed() =
+ testScope.runTest(USER_INFO_0) {
+ val tiles by collectLastValue(underTest.currentTiles)
+
+ val specs = listOf(TileSpec.create("a"), TileSpec.create("c"))
+
+ tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+ val originalTileC = tiles!![1].tile
+
+ tileSpecRepository.removeTiles(USER_INFO_0.id, listOf(TileSpec.create("c")))
+
+ assertThat(tiles?.size).isEqualTo(1)
+ assertThat(tiles!![0].spec).isEqualTo(TileSpec.create("a"))
+
+ assertThat((originalTileC as FakeQSTile).destroyed).isTrue()
+ verify(logger)
+ .logTileDestroyed(
+ TileSpec.create("c"),
+ QSPipelineLogger.TileDestroyedReason.TILE_REMOVED
+ )
+ }
+
+ @Test
+ fun tileBecomesNotAvailable_destroyed() =
+ testScope.runTest(USER_INFO_0) {
+ val tiles by collectLastValue(underTest.currentTiles)
+ val repoTiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
+
+ val specs = listOf(TileSpec.create("a"))
+
+ tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+ val originalTileA = tiles!![0].tile
+
+ // Tile becomes unavailable
+ (originalTileA as FakeQSTile).available = false
+ unavailableTiles.add("a")
+ // and there is some change in the specs
+ tileSpecRepository.addTile(USER_INFO_0.id, TileSpec.create("b"))
+ runCurrent()
+
+ assertThat(originalTileA.destroyed).isTrue()
+ verify(logger)
+ .logTileDestroyed(
+ TileSpec.create("a"),
+ QSPipelineLogger.TileDestroyedReason.EXISTING_TILE_NOT_AVAILABLE
+ )
+
+ assertThat(tiles?.size).isEqualTo(1)
+ assertThat(tiles!![0].spec).isEqualTo(TileSpec.create("b"))
+ assertThat(tiles!![0].tile).isNotSameInstanceAs(originalTileA)
+
+ assertThat(repoTiles).isEqualTo(tiles!!.map(TileModel::spec))
+ }
+
+ @Test
+ fun userChange_tilesChange() =
+ testScope.runTest(USER_INFO_0) {
+ val tiles by collectLastValue(underTest.currentTiles)
+
+ val specs0 = listOf(TileSpec.create("a"))
+ val specs1 = listOf(TileSpec.create("b"))
+ tileSpecRepository.setTiles(USER_INFO_0.id, specs0)
+ tileSpecRepository.setTiles(USER_INFO_1.id, specs1)
+
+ switchUser(USER_INFO_1)
+
+ assertThat(tiles!![0].spec).isEqualTo(specs1[0])
+ assertThat(tiles!![0].tile.tileSpec).isEqualTo(specs1[0].spec)
+ }
+
+ @Test
+ fun tileNotPresentInSecondaryUser_destroyedInUserChange() =
+ testScope.runTest(USER_INFO_0) {
+ val tiles by collectLastValue(underTest.currentTiles)
+
+ val specs0 = listOf(TileSpec.create("a"))
+ val specs1 = listOf(TileSpec.create("b"))
+ tileSpecRepository.setTiles(USER_INFO_0.id, specs0)
+ tileSpecRepository.setTiles(USER_INFO_1.id, specs1)
+
+ val originalTileA = tiles!![0].tile
+
+ switchUser(USER_INFO_1)
+ runCurrent()
+
+ assertThat((originalTileA as FakeQSTile).destroyed).isTrue()
+ verify(logger)
+ .logTileDestroyed(
+ specs0[0],
+ QSPipelineLogger.TileDestroyedReason.TILE_NOT_PRESENT_IN_NEW_USER
+ )
+ }
+
+ @Test
+ fun userChange_customTileDestroyed_lifecycleNotTerminated() {
+ testScope.runTest(USER_INFO_0) {
+ val tiles by collectLastValue(underTest.currentTiles)
+
+ val specs = listOf(CUSTOM_TILE_SPEC)
+ tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+ tileSpecRepository.setTiles(USER_INFO_1.id, specs)
+
+ val originalCustomTile = tiles!![0].tile
+
+ switchUser(USER_INFO_1)
+ runCurrent()
+
+ verify(originalCustomTile).destroy()
+ assertThat(tileLifecycleManagerFactory.created).isEmpty()
+ }
+ }
+
+ @Test
+ fun userChange_sameTileUserChanged() =
+ testScope.runTest(USER_INFO_0) {
+ val tiles by collectLastValue(underTest.currentTiles)
+
+ val specs = listOf(TileSpec.create("a"))
+ tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+ tileSpecRepository.setTiles(USER_INFO_1.id, specs)
+
+ val originalTileA = tiles!![0].tile as FakeQSTile
+ assertThat(originalTileA.user).isEqualTo(USER_INFO_0.id)
+
+ switchUser(USER_INFO_1)
+ runCurrent()
+
+ assertThat(tiles!![0].tile).isSameInstanceAs(originalTileA)
+ assertThat(originalTileA.user).isEqualTo(USER_INFO_1.id)
+ verify(logger).logTileUserChanged(specs[0], USER_INFO_1.id)
+ }
+
+ @Test
+ fun addTile() =
+ testScope.runTest(USER_INFO_0) {
+ val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
+ val spec = TileSpec.create("a")
+ val currentSpecs =
+ listOf(
+ TileSpec.create("b"),
+ TileSpec.create("c"),
+ )
+ tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs)
+
+ underTest.addTile(spec, position = 1)
+
+ val expectedSpecs =
+ listOf(
+ TileSpec.create("b"),
+ spec,
+ TileSpec.create("c"),
+ )
+ assertThat(tiles).isEqualTo(expectedSpecs)
+ }
+
+ @Test
+ fun addTile_currentUser() =
+ testScope.runTest(USER_INFO_1) {
+ val tiles0 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
+ val tiles1 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_1.id))
+ val spec = TileSpec.create("a")
+ val currentSpecs =
+ listOf(
+ TileSpec.create("b"),
+ TileSpec.create("c"),
+ )
+ tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs)
+ tileSpecRepository.setTiles(USER_INFO_1.id, currentSpecs)
+
+ switchUser(USER_INFO_1)
+ underTest.addTile(spec, position = 1)
+
+ assertThat(tiles0).isEqualTo(currentSpecs)
+
+ val expectedSpecs =
+ listOf(
+ TileSpec.create("b"),
+ spec,
+ TileSpec.create("c"),
+ )
+ assertThat(tiles1).isEqualTo(expectedSpecs)
+ }
+
+ @Test
+ fun removeTile_platform() =
+ testScope.runTest(USER_INFO_0) {
+ val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
+
+ val specs = listOf(TileSpec.create("a"), TileSpec.create("b"))
+ tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+ runCurrent()
+
+ underTest.removeTiles(specs.subList(0, 1))
+
+ assertThat(tiles).isEqualTo(specs.subList(1, 2))
+ }
+
+ @Test
+ fun removeTile_customTile_lifecycleEnded() {
+ testScope.runTest(USER_INFO_0) {
+ val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
+
+ val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC)
+ tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+ runCurrent()
+ assertThat(customTileAddedRepository.isTileAdded(TEST_COMPONENT, USER_INFO_0.id))
+ .isTrue()
+
+ underTest.removeTiles(listOf(CUSTOM_TILE_SPEC))
+
+ assertThat(tiles).isEqualTo(specs.subList(0, 1))
+
+ val tileLifecycleManager =
+ tileLifecycleManagerFactory.created[USER_INFO_0.id to TEST_COMPONENT]
+ assertThat(tileLifecycleManager).isNotNull()
+
+ with(inOrder(tileLifecycleManager!!)) {
+ verify(tileLifecycleManager).onStopListening()
+ verify(tileLifecycleManager).onTileRemoved()
+ verify(tileLifecycleManager).flushMessagesAndUnbind()
+ }
+ assertThat(customTileAddedRepository.isTileAdded(TEST_COMPONENT, USER_INFO_0.id))
+ .isFalse()
+ verify(customTileStatePersister)
+ .removeState(TileServiceKey(TEST_COMPONENT, USER_INFO_0.id))
+ }
+ }
+
+ @Test
+ fun removeTiles_currentUser() =
+ testScope.runTest {
+ val tiles0 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
+ val tiles1 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_1.id))
+ val currentSpecs =
+ listOf(
+ TileSpec.create("a"),
+ TileSpec.create("b"),
+ TileSpec.create("c"),
+ )
+ tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs)
+ tileSpecRepository.setTiles(USER_INFO_1.id, currentSpecs)
+
+ switchUser(USER_INFO_1)
+ runCurrent()
+
+ underTest.removeTiles(currentSpecs.subList(0, 2))
+
+ assertThat(tiles0).isEqualTo(currentSpecs)
+ assertThat(tiles1).isEqualTo(currentSpecs.subList(2, 3))
+ }
+
+ @Test
+ fun setTiles() =
+ testScope.runTest(USER_INFO_0) {
+ val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
+
+ val currentSpecs = listOf(TileSpec.create("a"), TileSpec.create("b"))
+ tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs)
+ runCurrent()
+
+ val newSpecs = listOf(TileSpec.create("b"), TileSpec.create("c"), TileSpec.create("a"))
+ underTest.setTiles(newSpecs)
+ runCurrent()
+
+ assertThat(tiles).isEqualTo(newSpecs)
+ }
+
+ @Test
+ fun setTiles_customTiles_lifecycleEndedIfGone() =
+ testScope.runTest(USER_INFO_0) {
+ val otherCustomTileSpec = TileSpec.create("custom(b/c)")
+
+ val currentSpecs = listOf(CUSTOM_TILE_SPEC, TileSpec.create("a"), otherCustomTileSpec)
+ tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs)
+ runCurrent()
+
+ val newSpecs =
+ listOf(
+ otherCustomTileSpec,
+ TileSpec.create("a"),
+ )
+
+ underTest.setTiles(newSpecs)
+ runCurrent()
+
+ val tileLifecycleManager =
+ tileLifecycleManagerFactory.created[USER_INFO_0.id to TEST_COMPONENT]!!
+
+ with(inOrder(tileLifecycleManager)) {
+ verify(tileLifecycleManager).onStopListening()
+ verify(tileLifecycleManager).onTileRemoved()
+ verify(tileLifecycleManager).flushMessagesAndUnbind()
+ }
+ assertThat(customTileAddedRepository.isTileAdded(TEST_COMPONENT, USER_INFO_0.id))
+ .isFalse()
+ verify(customTileStatePersister)
+ .removeState(TileServiceKey(TEST_COMPONENT, USER_INFO_0.id))
+ }
+
+ @Test
+ fun protoDump() =
+ testScope.runTest(USER_INFO_0) {
+ val tiles by collectLastValue(underTest.currentTiles)
+ val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC)
+
+ tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+
+ val stateA = tiles!![0].tile.state
+ stateA.fillIn(Tile.STATE_INACTIVE, "A", "AA")
+ val stateCustom = QSTile.BooleanState()
+ stateCustom.fillIn(Tile.STATE_ACTIVE, "B", "BB")
+ stateCustom.spec = CUSTOM_TILE_SPEC.spec
+ whenever(tiles!![1].tile.state).thenReturn(stateCustom)
+
+ val proto = SystemUIProtoDump()
+ underTest.dumpProto(proto, emptyArray())
+
+ assertThat(MessageNano.messageNanoEquals(proto.tiles[0], stateA.toProto())).isTrue()
+ assertThat(MessageNano.messageNanoEquals(proto.tiles[1], stateCustom.toProto()))
+ .isTrue()
+ }
+
+ private fun QSTile.State.fillIn(state: Int, label: CharSequence, secondaryLabel: CharSequence) {
+ this.state = state
+ this.label = label
+ this.secondaryLabel = secondaryLabel
+ if (this is BooleanState) {
+ value = state == Tile.STATE_ACTIVE
+ }
+ }
+
+ private fun tileCreator(spec: String): QSTile? {
+ val currentUser = userTracker.userId
+ return when (spec) {
+ CUSTOM_TILE_SPEC.spec ->
+ mock<CustomTile> {
+ var tileSpecReference: String? = null
+ whenever(user).thenReturn(currentUser)
+ whenever(component).thenReturn(CUSTOM_TILE_SPEC.componentName)
+ whenever(isAvailable).thenReturn(true)
+ whenever(setTileSpec(anyString())).thenAnswer {
+ tileSpecReference = it.arguments[0] as? String
+ Unit
+ }
+ whenever(tileSpec).thenAnswer { tileSpecReference }
+ // Also, add it to the set of added tiles (as this happens as part of the tile
+ // creation).
+ customTileAddedRepository.setTileAdded(
+ CUSTOM_TILE_SPEC.componentName,
+ currentUser,
+ true
+ )
+ }
+ in VALID_TILES -> FakeQSTile(currentUser, available = spec !in unavailableTiles)
+ else -> null
+ }
+ }
+
+ private fun TestScope.runTest(user: UserInfo, body: suspend TestScope.() -> Unit) {
+ return runTest {
+ switchUser(user)
+ body()
+ }
+ }
+
+ private suspend fun switchUser(user: UserInfo) {
+ setUserTracker(user.id)
+ userRepository.setSelectedUserInfo(user)
+ }
+
+ private fun setUserTracker(user: Int) {
+ val mockContext = mockUserContext(user)
+ whenever(userTracker.userContext).thenReturn(mockContext)
+ whenever(userTracker.userId).thenReturn(user)
+ }
+
+ private class TLMFactory : TileLifecycleManager.Factory {
+
+ val created = mutableMapOf<Pair<Int, ComponentName>, TileLifecycleManager>()
+
+ override fun create(intent: Intent, userHandle: UserHandle): TileLifecycleManager {
+ val componentName = intent.component!!
+ val user = userHandle.identifier
+ val manager: TileLifecycleManager = mock()
+ created[user to componentName] = manager
+ return manager
+ }
+ }
+
+ private fun mockUserContext(user: Int): Context {
+ return mock {
+ whenever(this.userId).thenReturn(user)
+ whenever(this.user).thenReturn(UserHandle.of(user))
+ }
+ }
+
+ companion object {
+ private val USER_INFO_0 = UserInfo().apply { id = 0 }
+ private val USER_INFO_1 = UserInfo().apply { id = 1 }
+
+ private val VALID_TILES = setOf("a", "b", "c", "d", "e")
+ private val TEST_COMPONENT = ComponentName("pkg", "cls")
+ private val CUSTOM_TILE_SPEC = TileSpec.Companion.create(TEST_COMPONENT)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt
new file mode 100644
index 0000000..e509696
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.interactor
+
+import android.content.Context
+import android.view.View
+import com.android.internal.logging.InstanceId
+import com.android.systemui.plugins.qs.QSIconView
+import com.android.systemui.plugins.qs.QSTile
+
+class FakeQSTile(
+ var user: Int,
+ var available: Boolean = true,
+) : QSTile {
+ private var tileSpec: String? = null
+ var destroyed = false
+ private val state = QSTile.State()
+
+ override fun getTileSpec(): String? {
+ return tileSpec
+ }
+
+ override fun isAvailable(): Boolean {
+ return available
+ }
+
+ override fun setTileSpec(tileSpec: String?) {
+ this.tileSpec = tileSpec
+ state.spec = tileSpec
+ }
+
+ override fun refreshState() {}
+
+ override fun addCallback(callback: QSTile.Callback?) {}
+
+ override fun removeCallback(callback: QSTile.Callback?) {}
+
+ override fun removeCallbacks() {}
+
+ override fun createTileView(context: Context?): QSIconView? {
+ return null
+ }
+
+ override fun click(view: View?) {}
+
+ override fun secondaryClick(view: View?) {}
+
+ override fun longClick(view: View?) {}
+
+ override fun userSwitch(currentUser: Int) {
+ user = currentUser
+ }
+
+ override fun getMetricsCategory(): Int {
+ return 0
+ }
+
+ override fun setListening(client: Any?, listening: Boolean) {}
+
+ override fun setDetailListening(show: Boolean) {}
+
+ override fun destroy() {
+ destroyed = true
+ }
+
+ override fun getTileLabel(): CharSequence {
+ return ""
+ }
+
+ override fun getState(): QSTile.State {
+ return state
+ }
+
+ override fun getInstanceId(): InstanceId {
+ return InstanceId.fakeInstanceId(0)
+ }
+
+ override fun isListening(): Boolean {
+ return false
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 9997997..5ca3771 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -32,6 +32,7 @@
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -66,6 +67,7 @@
import com.android.internal.util.LatencyTracker;
import com.android.keyguard.KeyguardClockSwitch;
import com.android.keyguard.KeyguardClockSwitchController;
+import com.android.keyguard.KeyguardSliceViewController;
import com.android.keyguard.KeyguardStatusView;
import com.android.keyguard.KeyguardStatusViewController;
import com.android.keyguard.KeyguardUpdateMonitor;
@@ -74,6 +76,7 @@
import com.android.keyguard.dagger.KeyguardStatusBarViewComponent;
import com.android.keyguard.dagger.KeyguardStatusViewComponent;
import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
+import com.android.keyguard.logging.KeyguardLogger;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
@@ -233,7 +236,6 @@
@Mock protected KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory;
@Mock protected KeyguardStatusBarViewComponent mKeyguardStatusBarViewComponent;
@Mock protected KeyguardClockSwitchController mKeyguardClockSwitchController;
- @Mock protected KeyguardStatusViewController mKeyguardStatusViewController;
@Mock protected KeyguardStatusBarViewController mKeyguardStatusBarViewController;
@Mock protected NotificationStackScrollLayoutController
mNotificationStackScrollLayoutController;
@@ -293,6 +295,9 @@
@Mock protected AlternateBouncerInteractor mAlternateBouncerInteractor;
@Mock protected MotionEvent mDownMotionEvent;
@Mock protected CoroutineDispatcher mMainDispatcher;
+ @Mock protected KeyguardSliceViewController mKeyguardSliceViewController;
+ @Mock protected KeyguardLogger mKeyguardLogger;
+ @Mock protected KeyguardStatusView mKeyguardStatusView;
@Captor
protected ArgumentCaptor<NotificationStackScrollLayout.OnEmptySpaceClickListener>
mEmptySpaceClickListenerCaptor;
@@ -309,6 +314,7 @@
protected List<View.OnAttachStateChangeListener> mOnAttachStateChangeListeners;
protected Handler mMainHandler;
protected View.OnLayoutChangeListener mLayoutChangeListener;
+ protected KeyguardStatusViewController mKeyguardStatusViewController;
protected final FalsingManagerFake mFalsingManager = new FalsingManagerFake();
protected final Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty();
@@ -335,6 +341,18 @@
KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext);
keyguardStatusView.setId(R.id.keyguard_status_view);
+ mKeyguardStatusViewController = spy(new KeyguardStatusViewController(
+ mKeyguardStatusView,
+ mKeyguardSliceViewController,
+ mKeyguardClockSwitchController,
+ mKeyguardStateController,
+ mUpdateMonitor,
+ mConfigurationController,
+ mDozeParameters,
+ mScreenOffAnimationController,
+ mKeyguardLogger,
+ mFeatureFlags,
+ mInteractionJankMonitor));
when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(false);
when(mHeadsUpCallback.getContext()).thenReturn(mContext);
@@ -366,12 +384,15 @@
when(mView.findViewById(R.id.keyguard_bottom_area)).thenReturn(mKeyguardBottomArea);
when(mKeyguardBottomArea.animate()).thenReturn(mViewPropertyAnimator);
when(mView.animate()).thenReturn(mViewPropertyAnimator);
+ when(mKeyguardStatusView.animate()).thenReturn(mViewPropertyAnimator);
when(mViewPropertyAnimator.translationX(anyFloat())).thenReturn(mViewPropertyAnimator);
when(mViewPropertyAnimator.alpha(anyFloat())).thenReturn(mViewPropertyAnimator);
when(mViewPropertyAnimator.setDuration(anyLong())).thenReturn(mViewPropertyAnimator);
+ when(mViewPropertyAnimator.setStartDelay(anyLong())).thenReturn(mViewPropertyAnimator);
when(mViewPropertyAnimator.setInterpolator(any())).thenReturn(mViewPropertyAnimator);
when(mViewPropertyAnimator.setListener(any())).thenReturn(mViewPropertyAnimator);
when(mViewPropertyAnimator.setUpdateListener(any())).thenReturn(mViewPropertyAnimator);
+ when(mViewPropertyAnimator.withEndAction(any())).thenReturn(mViewPropertyAnimator);
when(mView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame);
when(mView.findViewById(R.id.keyguard_status_view))
.thenReturn(mock(KeyguardStatusView.class));
@@ -650,9 +671,13 @@
@After
public void tearDown() {
- mNotificationPanelViewController.mBottomAreaShadeAlphaAnimator.cancel();
- mNotificationPanelViewController.cancelHeightAnimator();
- mMainHandler.removeCallbacksAndMessages(null);
+ if (mNotificationPanelViewController != null) {
+ mNotificationPanelViewController.mBottomAreaShadeAlphaAnimator.cancel();
+ mNotificationPanelViewController.cancelHeightAnimator();
+ }
+ if (mMainHandler != null) {
+ mMainHandler.removeCallbacksAndMessages(null);
+ }
}
protected void setBottomPadding(int stackBottom, int lockIconPadding, int indicationPadding,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 569f90b..4438b98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -614,9 +614,8 @@
public void onBiometricHelp_coEx_faceFailure() {
createController();
- // GIVEN unlocking with fingerprint is possible
- when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(anyInt()))
- .thenReturn(true);
+ // GIVEN unlocking with fingerprint is possible and allowed
+ fingerprintUnlockIsPossibleAndAllowed();
String message = "A message";
mController.setVisible(true);
@@ -641,9 +640,8 @@
public void onBiometricHelp_coEx_faceUnavailable() {
createController();
- // GIVEN unlocking with fingerprint is possible
- when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(anyInt()))
- .thenReturn(true);
+ // GIVEN unlocking with fingerprint is possible and allowed
+ fingerprintUnlockIsPossibleAndAllowed();
String message = "A message";
mController.setVisible(true);
@@ -664,6 +662,35 @@
mContext.getString(R.string.keyguard_suggest_fingerprint));
}
+
+ @Test
+ public void onBiometricHelp_coEx_faceUnavailable_fpNotAllowed() {
+ createController();
+
+ // GIVEN unlocking with fingerprint is possible but not allowed
+ setupFingerprintUnlockPossible(true);
+ when(mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed())
+ .thenReturn(false);
+
+ String message = "A message";
+ mController.setVisible(true);
+
+ // WHEN there's a face unavailable message
+ mController.getKeyguardCallback().onBiometricHelp(
+ BIOMETRIC_HELP_FACE_NOT_AVAILABLE,
+ message,
+ BiometricSourceType.FACE);
+
+ // THEN show sequential messages such as: 'face unlock unavailable' and
+ // 'try fingerprint instead'
+ verifyIndicationMessage(
+ INDICATION_TYPE_BIOMETRIC_MESSAGE,
+ message);
+ verifyIndicationMessage(
+ INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+ mContext.getString(R.string.keyguard_unlock));
+ }
+
@Test
public void onBiometricHelp_coEx_fpFailure_faceAlreadyUnlocked() {
createController();
@@ -818,8 +845,7 @@
@Test
public void faceErrorTimeout_whenFingerprintEnrolled_doesNotShowMessage() {
createController();
- when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
- getCurrentUser())).thenReturn(true);
+ fingerprintUnlockIsPossibleAndAllowed();
String message = "A message";
mController.setVisible(true);
@@ -832,9 +858,8 @@
public void sendFaceHelpMessages_fingerprintEnrolled() {
createController();
- // GIVEN fingerprint enrolled
- when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
- getCurrentUser())).thenReturn(true);
+ // GIVEN unlocking with fingerprint is possible and allowed
+ fingerprintUnlockIsPossibleAndAllowed();
// WHEN help messages received that are allowed to show
final String helpString = "helpString";
@@ -859,9 +884,8 @@
public void doNotSendMostFaceHelpMessages_fingerprintEnrolled() {
createController();
- // GIVEN fingerprint enrolled
- when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
- getCurrentUser())).thenReturn(true);
+ // GIVEN unlocking with fingerprint is possible and allowed
+ fingerprintUnlockIsPossibleAndAllowed();
// WHEN help messages received that aren't supposed to show
final String helpString = "helpString";
@@ -886,9 +910,8 @@
public void sendAllFaceHelpMessages_fingerprintNotEnrolled() {
createController();
- // GIVEN fingerprint NOT enrolled
- when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
- getCurrentUser())).thenReturn(false);
+ // GIVEN fingerprint NOT possible
+ fingerprintUnlockIsNotPossible();
// WHEN help messages received
final Set<CharSequence> helpStrings = new HashSet<>();
@@ -917,9 +940,8 @@
public void sendTooDarkFaceHelpMessages_onTimeout_noFpEnrolled() {
createController();
- // GIVEN fingerprint NOT enrolled
- when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
- getCurrentUser())).thenReturn(false);
+ // GIVEN fingerprint not possible
+ fingerprintUnlockIsNotPossible();
// WHEN help message received and deferred message is valid
final String helpString = "helpMsg";
@@ -948,9 +970,8 @@
public void sendTooDarkFaceHelpMessages_onTimeout_fingerprintEnrolled() {
createController();
- // GIVEN fingerprint enrolled
- when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
- getCurrentUser())).thenReturn(true);
+ // GIVEN unlocking with fingerprint is possible and allowed
+ fingerprintUnlockIsPossibleAndAllowed();
// WHEN help message received and deferredMessage is valid
final String helpString = "helpMsg";
@@ -1500,7 +1521,7 @@
@Test
public void onBiometricError_faceLockedOutFirstTimeAndFpAllowed_showsTheFpFollowupMessage() {
createController();
- fingerprintUnlockIsPossible();
+ fingerprintUnlockIsPossibleAndAllowed();
onFaceLockoutError("first lockout");
verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
@@ -1559,7 +1580,7 @@
@Test
public void onBiometricError_faceLockedOutAgainAndFpAllowed_showsTheFpFollowupMessage() {
createController();
- fingerprintUnlockIsPossible();
+ fingerprintUnlockIsPossibleAndAllowed();
onFaceLockoutError("first lockout");
clearInvocations(mRotateTextViewController);
@@ -1668,7 +1689,7 @@
public void onBiometricError_screenIsTurningOn_faceLockedOutFpIsAvailable_showsMessage() {
createController();
screenIsTurningOn();
- fingerprintUnlockIsPossible();
+ fingerprintUnlockIsPossibleAndAllowed();
onFaceLockoutError("lockout error");
verifyNoMoreInteractions(mRotateTextViewController);
@@ -1746,8 +1767,9 @@
setupFingerprintUnlockPossible(false);
}
- private void fingerprintUnlockIsPossible() {
+ private void fingerprintUnlockIsPossibleAndAllowed() {
setupFingerprintUnlockPossible(true);
+ when(mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed()).thenReturn(true);
}
private void setupFingerprintUnlockPossible(boolean possible) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
index 7b59cc2..08a9f31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
@@ -22,7 +22,7 @@
import android.testing.TestableLooper.RunWithLooper
import android.view.View
import android.widget.FrameLayout
-import androidx.core.animation.AnimatorTestRule2
+import androidx.core.animation.AnimatorTestRule
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
@@ -70,7 +70,7 @@
private lateinit var systemStatusAnimationScheduler: SystemStatusAnimationScheduler
private val fakeFeatureFlags = FakeFeatureFlags()
- @get:Rule val animatorTestRule = AnimatorTestRule2()
+ @get:Rule val animatorTestRule = AnimatorTestRule()
@Before
fun setup() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
index be3b723..aff705f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
@@ -18,7 +18,7 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
-import androidx.core.animation.AnimatorTestRule2
+import androidx.core.animation.AnimatorTestRule
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
@@ -51,7 +51,7 @@
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class NotificationWakeUpCoordinatorTest : SysuiTestCase() {
- @get:Rule val animatorTestRule = AnimatorTestRule2()
+ @get:Rule val animatorTestRule = AnimatorTestRule()
private val dumpManager: DumpManager = mock()
private val headsUpManager: HeadsUpManager = mock()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
new file mode 100644
index 0000000..14e5f9e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.shelf.domain.interactor
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class NotificationShelfInteractorTest : SysuiTestCase() {
+
+ private val keyguardRepository = FakeKeyguardRepository()
+ private val deviceEntryFaceAuthRepository = FakeDeviceEntryFaceAuthRepository()
+ private val underTest =
+ NotificationShelfInteractor(keyguardRepository, deviceEntryFaceAuthRepository)
+
+ @Test
+ fun shelfIsNotStatic_whenKeyguardNotShowing() = runTest {
+ val shelfStatic by collectLastValue(underTest.isShelfStatic)
+
+ keyguardRepository.setKeyguardShowing(false)
+
+ assertThat(shelfStatic).isFalse()
+ }
+
+ @Test
+ fun shelfIsNotStatic_whenKeyguardShowingAndNotBypass() = runTest {
+ val shelfStatic by collectLastValue(underTest.isShelfStatic)
+
+ keyguardRepository.setKeyguardShowing(true)
+ deviceEntryFaceAuthRepository.isBypassEnabled.value = false
+
+ assertThat(shelfStatic).isFalse()
+ }
+
+ @Test
+ fun shelfIsStatic_whenBypass() = runTest {
+ val shelfStatic by collectLastValue(underTest.isShelfStatic)
+
+ keyguardRepository.setKeyguardShowing(true)
+ deviceEntryFaceAuthRepository.isBypassEnabled.value = true
+
+ assertThat(shelfStatic).isTrue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
new file mode 100644
index 0000000..6c5fb8b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.shelf.ui.viewmodel
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class NotificationShelfViewModelTest : SysuiTestCase() {
+
+ private val keyguardRepository = FakeKeyguardRepository()
+ private val deviceEntryFaceAuthRepository = FakeDeviceEntryFaceAuthRepository()
+ private val interactor =
+ NotificationShelfInteractor(keyguardRepository, deviceEntryFaceAuthRepository)
+ private val underTest = NotificationShelfViewModel(interactor)
+
+ @Test
+ fun canModifyColorOfNotifications_whenKeyguardNotShowing() = runTest {
+ val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
+
+ keyguardRepository.setKeyguardShowing(false)
+
+ assertThat(canModifyNotifColor).isTrue()
+ }
+
+ @Test
+ fun canModifyColorOfNotifications_whenKeyguardShowingAndNotBypass() = runTest {
+ val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
+
+ keyguardRepository.setKeyguardShowing(true)
+ deviceEntryFaceAuthRepository.isBypassEnabled.value = false
+
+ assertThat(canModifyNotifColor).isTrue()
+ }
+
+ @Test
+ fun cannotModifyColorOfNotifications_whenBypass() = runTest {
+ val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
+
+ keyguardRepository.setKeyguardShowing(true)
+ deviceEntryFaceAuthRepository.isBypassEnabled.value = true
+
+ assertThat(canModifyNotifColor).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
index ddc6d48..d30e024 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
@@ -29,6 +29,7 @@
import android.net.wifi.WifiInfo
import android.net.wifi.WifiManager
import android.net.wifi.WifiManager.TrafficStateCallback
+import android.net.wifi.WifiManager.UNKNOWN_SSID
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -49,16 +50,14 @@
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.Executor
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import org.junit.After
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentMatchers.anyInt
@@ -80,9 +79,10 @@
@Mock private lateinit var connectivityManager: ConnectivityManager
@Mock private lateinit var wifiManager: WifiManager
private lateinit var executor: Executor
- private lateinit var scope: CoroutineScope
private lateinit var connectivityRepository: ConnectivityRepository
+ private val testScope = TestScope(UnconfinedTestDispatcher())
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -96,7 +96,6 @@
)
.thenReturn(flowOf(Unit))
executor = FakeExecutor(FakeSystemClock())
- scope = CoroutineScope(IMMEDIATE)
connectivityRepository =
ConnectivityRepositoryImpl(
@@ -105,21 +104,16 @@
context,
mock(),
mock(),
- scope,
+ testScope.backgroundScope,
mock(),
)
underTest = createRepo()
}
- @After
- fun tearDown() {
- scope.cancel()
- }
-
@Test
fun isWifiEnabled_initiallyGetsWifiManagerValue() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
whenever(wifiManager.isWifiEnabled).thenReturn(true)
underTest = createRepo()
@@ -129,7 +123,7 @@
@Test
fun isWifiEnabled_networkCapabilitiesChanged_valueUpdated() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
// We need to call launch on the flows so that they start updating
val networkJob = underTest.wifiNetwork.launchIn(this)
val enabledJob = underTest.isWifiEnabled.launchIn(this)
@@ -152,7 +146,7 @@
@Test
fun isWifiEnabled_networkLost_valueUpdated() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
// We need to call launch on the flows so that they start updating
val networkJob = underTest.wifiNetwork.launchIn(this)
val enabledJob = underTest.isWifiEnabled.launchIn(this)
@@ -173,7 +167,7 @@
@Test
fun isWifiEnabled_intentsReceived_valueUpdated() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val intentFlow = MutableSharedFlow<Unit>()
whenever(
broadcastDispatcher.broadcastFlow(
@@ -203,7 +197,7 @@
@Test
fun isWifiEnabled_bothIntentAndNetworkUpdates_valueAlwaysUpdated() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val intentFlow = MutableSharedFlow<Unit>()
whenever(
broadcastDispatcher.broadcastFlow(
@@ -242,7 +236,7 @@
@Test
fun isWifiDefault_initiallyGetsDefault() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val job = underTest.isWifiDefault.launchIn(this)
assertThat(underTest.isWifiDefault.value).isFalse()
@@ -252,7 +246,7 @@
@Test
fun isWifiDefault_wifiNetwork_isTrue() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val job = underTest.isWifiDefault.launchIn(this)
val wifiInfo = mock<WifiInfo>().apply { whenever(this.ssid).thenReturn(SSID) }
@@ -268,7 +262,7 @@
/** Regression test for b/266628069. */
@Test
fun isWifiDefault_transportInfoIsNotWifi_andNoWifiTransport_false() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val job = underTest.isWifiDefault.launchIn(this)
val transportInfo =
@@ -294,7 +288,7 @@
/** Regression test for b/266628069. */
@Test
fun isWifiDefault_transportInfoIsNotWifi_butHasWifiTransport_true() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val job = underTest.isWifiDefault.launchIn(this)
val transportInfo =
@@ -319,7 +313,7 @@
@Test
fun isWifiDefault_carrierMergedViaCellular_isTrue() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val job = underTest.isWifiDefault.launchIn(this)
val carrierMergedInfo =
@@ -341,7 +335,7 @@
@Test
fun isWifiDefault_carrierMergedViaCellular_withVcnTransport_isTrue() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val job = underTest.isWifiDefault.launchIn(this)
val capabilities =
@@ -360,7 +354,7 @@
@Test
fun isWifiDefault_carrierMergedViaWifi_isTrue() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val job = underTest.isWifiDefault.launchIn(this)
val carrierMergedInfo =
@@ -382,7 +376,7 @@
@Test
fun isWifiDefault_carrierMergedViaWifi_withVcnTransport_isTrue() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val job = underTest.isWifiDefault.launchIn(this)
val capabilities =
@@ -401,7 +395,7 @@
@Test
fun isWifiDefault_cellularAndWifiTransports_usesCellular_isTrue() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val job = underTest.isWifiDefault.launchIn(this)
val capabilities =
@@ -420,7 +414,7 @@
@Test
fun isWifiDefault_cellularNotVcnNetwork_isFalse() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val job = underTest.isWifiDefault.launchIn(this)
val capabilities =
@@ -438,7 +432,7 @@
@Test
fun isWifiDefault_isCarrierMergedViaUnderlyingWifi_isTrue() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val job = underTest.isWifiDefault.launchIn(this)
val underlyingNetwork = mock<Network>()
@@ -473,7 +467,7 @@
@Test
fun isWifiDefault_isCarrierMergedViaUnderlyingCellular_isTrue() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val job = underTest.isWifiDefault.launchIn(this)
val underlyingCarrierMergedNetwork = mock<Network>()
@@ -507,7 +501,7 @@
@Test
fun isWifiDefault_wifiNetworkLost_isFalse() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val job = underTest.isWifiDefault.launchIn(this)
// First, add a network
@@ -526,7 +520,7 @@
@Test
fun wifiNetwork_initiallyGetsDefault() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -537,7 +531,7 @@
@Test
fun wifiNetwork_primaryWifiNetworkAdded_flowHasNetwork() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -561,7 +555,7 @@
@Test
fun wifiNetwork_isCarrierMerged_flowHasCarrierMerged() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -581,7 +575,7 @@
@Test
fun wifiNetwork_isCarrierMergedViaUnderlyingWifi_flowHasCarrierMerged() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -618,7 +612,7 @@
@Test
fun wifiNetwork_isCarrierMergedViaUnderlyingCellular_flowHasCarrierMerged() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -656,7 +650,7 @@
@Test
fun wifiNetwork_carrierMergedButInvalidSubId_flowHasInvalid() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -680,7 +674,7 @@
@Test
fun wifiNetwork_isCarrierMerged_getsCorrectValues() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -715,7 +709,7 @@
@Test
fun wifiNetwork_notValidated_networkNotValidated() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -732,7 +726,7 @@
@Test
fun wifiNetwork_validated_networkValidated() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -749,7 +743,7 @@
@Test
fun wifiNetwork_nonPrimaryWifiNetworkAdded_flowHasNoNetwork() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -770,7 +764,7 @@
/** Regression test for b/266628069. */
@Test
fun wifiNetwork_transportInfoIsNotWifi_flowHasNoNetwork() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -789,7 +783,7 @@
@Test
fun wifiNetwork_cellularVcnNetworkAdded_flowHasNetwork() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -811,7 +805,7 @@
@Test
fun wifiNetwork_nonPrimaryCellularVcnNetworkAdded_flowHasNoNetwork() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -835,7 +829,7 @@
@Test
fun wifiNetwork_cellularNotVcnNetworkAdded_flowHasNoNetwork() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -854,7 +848,7 @@
@Test
fun wifiNetwork_cellularAndWifiTransports_usesCellular() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -877,7 +871,7 @@
@Test
fun wifiNetwork_newPrimaryWifiNetwork_flowHasNewNetwork() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -910,7 +904,7 @@
@Test
fun wifiNetwork_newNonPrimaryWifiNetwork_flowHasOldNetwork() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -943,7 +937,7 @@
@Test
fun wifiNetwork_newNetworkCapabilities_flowHasNewData() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -986,7 +980,7 @@
@Test
fun wifiNetwork_noCurrentNetwork_networkLost_flowHasNoNetwork() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -1001,7 +995,7 @@
@Test
fun wifiNetwork_currentNetworkLost_flowHasNoNetwork() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -1020,7 +1014,7 @@
@Test
fun wifiNetwork_unknownNetworkLost_flowHasPreviousNetwork() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -1043,7 +1037,7 @@
@Test
fun wifiNetwork_notCurrentNetworkLost_flowHasCurrentNetwork() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -1069,7 +1063,7 @@
/** Regression test for b/244173280. */
@Test
fun wifiNetwork_multipleSubscribers_newSubscribersGetCurrentValue() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest1: WifiNetworkModel? = null
val job1 = underTest.wifiNetwork.onEach { latest1 = it }.launchIn(this)
@@ -1096,8 +1090,151 @@
}
@Test
+ fun isWifiConnectedWithValidSsid_inactiveNetwork_false() =
+ testScope.runTest {
+ val job = underTest.wifiNetwork.launchIn(this)
+
+ val wifiInfo =
+ mock<WifiInfo>().apply {
+ whenever(this.ssid).thenReturn(SSID)
+ // A non-primary network is inactive
+ whenever(this.isPrimary).thenReturn(false)
+ }
+
+ getNetworkCallback()
+ .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
+
+ assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isWifiConnectedWithValidSsid_carrierMergedNetwork_false() =
+ testScope.runTest {
+ val job = underTest.wifiNetwork.launchIn(this)
+
+ val wifiInfo =
+ mock<WifiInfo>().apply {
+ whenever(this.isPrimary).thenReturn(true)
+ whenever(this.isCarrierMerged).thenReturn(true)
+ }
+
+ getNetworkCallback()
+ .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
+
+ assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isWifiConnectedWithValidSsid_invalidNetwork_false() =
+ testScope.runTest {
+ val job = underTest.wifiNetwork.launchIn(this)
+
+ val wifiInfo =
+ mock<WifiInfo>().apply {
+ whenever(this.isPrimary).thenReturn(true)
+ whenever(this.isCarrierMerged).thenReturn(true)
+ whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID)
+ }
+
+ getNetworkCallback()
+ .onCapabilitiesChanged(
+ NETWORK,
+ createWifiNetworkCapabilities(wifiInfo),
+ )
+
+ assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isWifiConnectedWithValidSsid_activeNetwork_nullSsid_false() =
+ testScope.runTest {
+ val job = underTest.wifiNetwork.launchIn(this)
+
+ val wifiInfo =
+ mock<WifiInfo>().apply {
+ whenever(this.isPrimary).thenReturn(true)
+ whenever(this.ssid).thenReturn(null)
+ }
+
+ getNetworkCallback()
+ .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
+
+ assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isWifiConnectedWithValidSsid_activeNetwork_unknownSsid_false() =
+ testScope.runTest {
+ val job = underTest.wifiNetwork.launchIn(this)
+
+ val wifiInfo =
+ mock<WifiInfo>().apply {
+ whenever(this.isPrimary).thenReturn(true)
+ whenever(this.ssid).thenReturn(UNKNOWN_SSID)
+ }
+
+ getNetworkCallback()
+ .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
+
+ assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isWifiConnectedWithValidSsid_activeNetwork_validSsid_true() =
+ testScope.runTest {
+ val job = underTest.wifiNetwork.launchIn(this)
+
+ val wifiInfo =
+ mock<WifiInfo>().apply {
+ whenever(this.isPrimary).thenReturn(true)
+ whenever(this.ssid).thenReturn("FakeSsid")
+ }
+
+ getNetworkCallback()
+ .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
+
+ assertThat(underTest.isWifiConnectedWithValidSsid()).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isWifiConnectedWithValidSsid_activeToInactive_trueToFalse() =
+ testScope.runTest {
+ val job = underTest.wifiNetwork.launchIn(this)
+
+ // Start with active
+ val wifiInfo =
+ mock<WifiInfo>().apply {
+ whenever(this.isPrimary).thenReturn(true)
+ whenever(this.ssid).thenReturn("FakeSsid")
+ }
+ getNetworkCallback()
+ .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
+ assertThat(underTest.isWifiConnectedWithValidSsid()).isTrue()
+
+ // WHEN the network is lost
+ getNetworkCallback().onLost(NETWORK)
+
+ // THEN the isWifiConnected updates
+ assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
fun wifiActivity_callbackGivesNone_activityFlowHasNone() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: DataActivityModel? = null
val job = underTest.wifiActivity.onEach { latest = it }.launchIn(this)
@@ -1111,7 +1248,7 @@
@Test
fun wifiActivity_callbackGivesIn_activityFlowHasIn() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: DataActivityModel? = null
val job = underTest.wifiActivity.onEach { latest = it }.launchIn(this)
@@ -1125,7 +1262,7 @@
@Test
fun wifiActivity_callbackGivesOut_activityFlowHasOut() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: DataActivityModel? = null
val job = underTest.wifiActivity.onEach { latest = it }.launchIn(this)
@@ -1139,7 +1276,7 @@
@Test
fun wifiActivity_callbackGivesInout_activityFlowHasInAndOut() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: DataActivityModel? = null
val job = underTest.wifiActivity.onEach { latest = it }.launchIn(this)
@@ -1159,7 +1296,7 @@
logger,
tableLogger,
executor,
- scope,
+ testScope.backgroundScope,
wifiManager,
)
}
@@ -1204,5 +1341,3 @@
}
}
}
-
-private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt
index ab4e93c..4e0c309 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.wifi.shared.model
+import android.net.wifi.WifiManager.UNKNOWN_SSID
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -50,6 +51,42 @@
WifiNetworkModel.CarrierMerged(NETWORK_ID, INVALID_SUBSCRIPTION_ID, 1)
}
+ @Test
+ fun active_hasValidSsid_nullSsid_false() {
+ val network =
+ WifiNetworkModel.Active(
+ NETWORK_ID,
+ level = MAX_VALID_LEVEL,
+ ssid = null,
+ )
+
+ assertThat(network.hasValidSsid()).isFalse()
+ }
+
+ @Test
+ fun active_hasValidSsid_unknownSsid_false() {
+ val network =
+ WifiNetworkModel.Active(
+ NETWORK_ID,
+ level = MAX_VALID_LEVEL,
+ ssid = UNKNOWN_SSID,
+ )
+
+ assertThat(network.hasValidSsid()).isFalse()
+ }
+
+ @Test
+ fun active_hasValidSsid_validSsid_true() {
+ val network =
+ WifiNetworkModel.Active(
+ NETWORK_ID,
+ level = MAX_VALID_LEVEL,
+ ssid = "FakeSsid",
+ )
+
+ assertThat(network.hasValidSsid()).isTrue()
+ }
+
// Non-exhaustive logDiffs test -- just want to make sure the logging logic isn't totally broken
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
index 1eee08c..91c88ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
@@ -21,6 +21,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker;
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_QS;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
@@ -167,8 +168,10 @@
mBatteryController.setPowerSaveMode(false, mView);
StaticInOrder inOrder = inOrder(staticMockMarker(BatterySaverUtils.class));
- inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), true, true));
- inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), false, true));
+ inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), true, true,
+ SAVER_ENABLED_QS));
+ inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), false, true,
+ SAVER_ENABLED_QS));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index 01e94ba..391c8ca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -62,7 +62,7 @@
import android.window.WindowOnBackInvokedDispatcher;
import androidx.annotation.NonNull;
-import androidx.core.animation.AnimatorTestRule2;
+import androidx.core.animation.AnimatorTestRule;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.UiEventLogger;
@@ -110,7 +110,7 @@
private final UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
@ClassRule
- public static AnimatorTestRule2 mAnimatorTestRule = new AnimatorTestRule2();
+ public static AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
@Before
public void setUp() throws Exception {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
new file mode 100644
index 0000000..c08ecd0
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import com.android.keyguard.FaceAuthUiEvent
+import com.android.systemui.keyguard.shared.model.AuthenticationStatus
+import com.android.systemui.keyguard.shared.model.DetectionStatus
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+
+class FakeDeviceEntryFaceAuthRepository : DeviceEntryFaceAuthRepository {
+
+ override val isAuthenticated = MutableStateFlow(false)
+ override val canRunFaceAuth = MutableStateFlow(false)
+ private val _authenticationStatus = MutableStateFlow<AuthenticationStatus?>(null)
+ override val authenticationStatus: Flow<AuthenticationStatus> =
+ _authenticationStatus.filterNotNull()
+ fun setAuthenticationStatus(status: AuthenticationStatus) {
+ _authenticationStatus.value = status
+ }
+ private val _detectionStatus = MutableStateFlow<DetectionStatus?>(null)
+ override val detectionStatus: Flow<DetectionStatus>
+ get() = _detectionStatus.filterNotNull()
+ fun setDetectionStatus(status: DetectionStatus) {
+ _detectionStatus.value = status
+ }
+ override val isLockedOut = MutableStateFlow(false)
+ private val _runningAuthRequest = MutableStateFlow<Pair<FaceAuthUiEvent, Boolean>?>(null)
+ val runningAuthRequest: StateFlow<Pair<FaceAuthUiEvent, Boolean>?> =
+ _runningAuthRequest.asStateFlow()
+ override val isAuthRunning = _runningAuthRequest.map { it != null }
+ override val isBypassEnabled = MutableStateFlow(false)
+
+ override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
+ _runningAuthRequest.value = uiEvent to fallbackToDetection
+ }
+
+ override fun cancel() {
+ _runningAuthRequest.value = null
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSFactory.kt
new file mode 100644
index 0000000..9383a0a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSFactory.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs
+
+import android.content.Context
+import com.android.systemui.plugins.qs.QSFactory
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.plugins.qs.QSTileView
+
+class FakeQSFactory(private val tileCreator: (String) -> QSTile?) : QSFactory {
+ override fun createTile(tileSpec: String): QSTile? {
+ return tileCreator(tileSpec)
+ }
+
+ override fun createTileView(
+ context: Context?,
+ tile: QSTile?,
+ collapsedView: Boolean
+ ): QSTileView {
+ throw NotImplementedError("Not implemented")
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeCustomTileAddedRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeCustomTileAddedRepository.kt
new file mode 100644
index 0000000..7771304
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeCustomTileAddedRepository.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.data.repository
+
+import android.content.ComponentName
+
+class FakeCustomTileAddedRepository : CustomTileAddedRepository {
+
+ private val tileAddedRegistry = mutableSetOf<Pair<Int, ComponentName>>()
+
+ override fun isTileAdded(componentName: ComponentName, userId: Int): Boolean {
+ return (userId to componentName) in tileAddedRegistry
+ }
+
+ override fun setTileAdded(componentName: ComponentName, userId: Int, added: Boolean) {
+ if (added) {
+ tileAddedRegistry.add(userId to componentName)
+ } else {
+ tileAddedRegistry.remove(userId to componentName)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt
new file mode 100644
index 0000000..2865710
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.data.repository
+
+import android.util.Log
+import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository.Companion.POSITION_AT_END
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeTileSpecRepository : TileSpecRepository {
+
+ private val tilesPerUser = mutableMapOf<Int, MutableStateFlow<List<TileSpec>>>()
+
+ override fun tilesSpecs(userId: Int): Flow<List<TileSpec>> {
+ return getFlow(userId).asStateFlow().also { Log.d("Fabian", "Retrieving flow for $userId") }
+ }
+
+ override suspend fun addTile(userId: Int, tile: TileSpec, position: Int) {
+ if (tile == TileSpec.Invalid) return
+ with(getFlow(userId)) {
+ value =
+ value.toMutableList().apply {
+ if (position == POSITION_AT_END) {
+ add(tile)
+ } else {
+ add(position, tile)
+ }
+ }
+ }
+ }
+
+ override suspend fun removeTiles(userId: Int, tiles: Collection<TileSpec>) {
+ with(getFlow(userId)) {
+ value =
+ value.toMutableList().apply { removeAll(tiles.filter { it != TileSpec.Invalid }) }
+ }
+ }
+
+ override suspend fun setTiles(userId: Int, tiles: List<TileSpec>) {
+ getFlow(userId).value = tiles.filter { it != TileSpec.Invalid }
+ }
+
+ private fun getFlow(userId: Int): MutableStateFlow<List<TileSpec>> =
+ tilesPerUser.getOrPut(userId) { MutableStateFlow(emptyList()) }
+}
diff --git a/services/Android.bp b/services/Android.bp
index 6e6c553..b0a0e5e 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -112,6 +112,7 @@
":services.searchui-sources",
":services.selectiontoolbar-sources",
":services.smartspace-sources",
+ ":services.soundtrigger-sources",
":services.systemcaptions-sources",
":services.translation-sources",
":services.texttospeech-sources",
@@ -169,6 +170,7 @@
"services.searchui",
"services.selectiontoolbar",
"services.smartspace",
+ "services.soundtrigger",
"services.systemcaptions",
"services.translation",
"services.texttospeech",
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 8d039fc..2a964b8 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -2073,15 +2073,14 @@
@Override
public void onSaveRequestSuccess(@NonNull String servicePackageName,
@Nullable IntentSender intentSender) {
- // Log onSaveRequest result.
- mSaveEventLogger.maybeSetIsSaved(true);
- final long saveRequestFinishTimestamp = SystemClock.elapsedRealtime() - mLatencyBaseTime;
- mSaveEventLogger.maybeSetLatencySaveFinishMillis(saveRequestFinishTimestamp);
- mSaveEventLogger.logAndEndEvent();
-
synchronized (mLock) {
mSessionFlags.mShowingSaveUi = false;
-
+ // Log onSaveRequest result.
+ mSaveEventLogger.maybeSetIsSaved(true);
+ final long saveRequestFinishTimestamp =
+ SystemClock.elapsedRealtime() - mLatencyBaseTime;
+ mSaveEventLogger.maybeSetLatencySaveFinishMillis(saveRequestFinishTimestamp);
+ mSaveEventLogger.logAndEndEvent();
if (mDestroyed) {
Slog.w(TAG, "Call to Session#onSaveRequestSuccess() rejected - session: "
+ id + " destroyed");
@@ -2108,14 +2107,13 @@
@NonNull String servicePackageName) {
boolean showMessage = !TextUtils.isEmpty(message);
- // Log onSaveRequest result.
- final long saveRequestFinishTimestamp = SystemClock.elapsedRealtime() - mLatencyBaseTime;
- mSaveEventLogger.maybeSetLatencySaveFinishMillis(saveRequestFinishTimestamp);
- mSaveEventLogger.logAndEndEvent();
-
synchronized (mLock) {
mSessionFlags.mShowingSaveUi = false;
-
+ // Log onSaveRequest result.
+ final long saveRequestFinishTimestamp =
+ SystemClock.elapsedRealtime() - mLatencyBaseTime;
+ mSaveEventLogger.maybeSetLatencySaveFinishMillis(saveRequestFinishTimestamp);
+ mSaveEventLogger.logAndEndEvent();
if (mDestroyed) {
Slog.w(TAG, "Call to Session#onSaveRequestFailure() rejected - session: "
+ id + " destroyed");
@@ -2228,8 +2226,8 @@
// AutoFillUiCallback
@Override
public void save() {
- mSaveEventLogger.maybeSetSaveButtonClicked(true);
synchronized (mLock) {
+ mSaveEventLogger.maybeSetSaveButtonClicked(true);
if (mDestroyed) {
Slog.w(TAG, "Call to Session#save() rejected - session: "
+ id + " destroyed");
@@ -2247,10 +2245,9 @@
// AutoFillUiCallback
@Override
public void cancelSave() {
- mSaveEventLogger.maybeSetDialogDismissed(true);
synchronized (mLock) {
mSessionFlags.mShowingSaveUi = false;
-
+ mSaveEventLogger.maybeSetDialogDismissed(true);
if (mDestroyed) {
Slog.w(TAG, "Call to Session#cancelSave() rejected - session: "
+ id + " destroyed");
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 542cc2f..5b320a8 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -19,6 +19,7 @@
import static android.Manifest.permission.MANAGE_COMPANION_DEVICES;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
+import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Process.SYSTEM_UID;
@@ -1066,6 +1067,10 @@
// No role was granted to for this association, there is nothing else we need to here.
return true;
}
+ // Do not need to remove the system role since it was pre-granted by the system.
+ if (deviceProfile.equals(DEVICE_PROFILE_AUTOMOTIVE_PROJECTION)) {
+ return true;
+ }
// Check if the applications is associated with another devices with the profile. If so,
// it should remain the role holder.
diff --git a/services/core/Android.bp b/services/core/Android.bp
index c8caab9..cfdf3ac 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -174,7 +174,6 @@
"android.hardware.configstore-V1.1-java",
"android.hardware.ir-V1-java",
"android.hardware.rebootescrow-V1-java",
- "android.hardware.soundtrigger-V2.3-java",
"android.hardware.power.stats-V2-java",
"android.hardware.power-V4-java",
"android.hidl.manager-V1.2-java",
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java b/services/core/java/com/android/server/SoundTriggerInternal.java
similarity index 97%
rename from services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
rename to services/core/java/com/android/server/SoundTriggerInternal.java
index cc398d9..e6c1750 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
+++ b/services/core/java/com/android/server/SoundTriggerInternal.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.soundtrigger;
+package com.android.server;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -29,15 +29,13 @@
import android.media.permission.Identity;
import android.os.IBinder;
-import com.android.server.voiceinteraction.VoiceInteractionManagerService;
-
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.List;
/**
* Provides a local service for managing voice-related recoginition models. This is primarily used
- * by the {@link VoiceInteractionManagerService}.
+ * by the {@code VoiceInteractionManagerService}.
*/
public interface SoundTriggerInternal {
/**
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 7b618b1..e248007 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -26,6 +26,17 @@
import static android.app.ActivityManager.PROCESS_STATE_HEAVY_WEIGHT;
import static android.app.ActivityManager.PROCESS_STATE_RECEIVER;
import static android.app.ActivityManager.PROCESS_STATE_TOP;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BIND_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_COMPONENT_DISABLED;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_EXECUTING_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_NONE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_TASK;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_STOP_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UID_IDLE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UNBIND_SERVICE;
import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_DEPRECATED;
import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_DISABLED;
import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_OK;
@@ -117,6 +128,7 @@
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
+import android.app.ActivityManagerInternal.OomAdjReason;
import android.app.ActivityManagerInternal.ServiceNotificationPolicy;
import android.app.ActivityThread;
import android.app.AppGlobals;
@@ -1146,7 +1158,7 @@
} finally {
/* Will be a no-op if nothing pending */
mAm.updateOomAdjPendingTargetsLocked(
- OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+ OOM_ADJ_REASON_START_SERVICE);
}
} else {
unbindServiceLocked(connection);
@@ -1236,8 +1248,7 @@
/* ignore - local call */
} finally {
/* Will be a no-op if nothing pending */
- mAm.updateOomAdjPendingTargetsLocked(
- OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
}
} else { // Starting a service
try {
@@ -1311,7 +1322,7 @@
false /* packageFrozen */,
true /* enqueueOomAdj */);
/* Will be a no-op if nothing pending */
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
if (error != null) {
return new ComponentName("!!", error);
}
@@ -1496,7 +1507,7 @@
stopServiceLocked(service, true);
}
if (size > 0) {
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_UID_IDLE);
}
}
}
@@ -3296,7 +3307,7 @@
Slog.e(TAG_SERVICE, "Short FGS procstate demoted: " + sr);
- mAm.updateOomAdjLocked(sr.app, OomAdjuster.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT);
+ mAm.updateOomAdjLocked(sr.app, OOM_ADJ_REASON_SHORT_FGS_TIMEOUT);
}
}
@@ -3630,7 +3641,7 @@
needOomAdj = true;
if (bringUpServiceLocked(s, service.getFlags(), callerFg, false,
permissionsReviewRequired, packageFrozen, true) != null) {
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_BIND_SERVICE);
return 0;
}
}
@@ -3655,7 +3666,7 @@
mAm.enqueueOomAdjTargetLocked(s.app);
}
if (needOomAdj) {
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_BIND_SERVICE);
}
final int packageState = wasStopped
@@ -3787,7 +3798,8 @@
}
}
- serviceDoneExecutingLocked(r, mDestroyingServices.contains(r), false, false);
+ serviceDoneExecutingLocked(r, mDestroyingServices.contains(r), false, false,
+ OOM_ADJ_REASON_EXECUTING_SERVICE);
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -3878,7 +3890,7 @@
}
}
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_UNBIND_SERVICE);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
@@ -3925,7 +3937,8 @@
}
}
- serviceDoneExecutingLocked(r, inDestroying, false, false);
+ serviceDoneExecutingLocked(r, inDestroying, false, false,
+ OOM_ADJ_REASON_UNBIND_SERVICE);
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -4360,11 +4373,11 @@
/**
* Bump the given service record into executing state.
* @param oomAdjReason The caller requests it to perform the oomAdjUpdate not {@link
- * OomAdjuster#OOM_ADJ_REASON_NONE}.
+ * ActivityManagerInternal#OOM_ADJ_REASON_NONE}.
* @return {@code true} if it performed oomAdjUpdate.
*/
private boolean bumpServiceExecutingLocked(
- ServiceRecord r, boolean fg, String why, @OomAdjuster.OomAdjReason int oomAdjReason) {
+ ServiceRecord r, boolean fg, String why, @OomAdjReason int oomAdjReason) {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, ">>> EXECUTING "
+ why + " of " + r + " in app " + r.app);
else if (DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING, ">>> EXECUTING "
@@ -4416,7 +4429,7 @@
}
}
boolean oomAdjusted = false;
- if (oomAdjReason != OomAdjuster.OOM_ADJ_REASON_NONE && r.app != null
+ if (oomAdjReason != OOM_ADJ_REASON_NONE && r.app != null
&& r.app.mState.getCurProcState() > ActivityManager.PROCESS_STATE_SERVICE) {
// Force an immediate oomAdjUpdate, so the client app could be in the correct process
// state before doing any service related transactions
@@ -4440,8 +4453,7 @@
+ " rebind=" + rebind);
if ((!i.requested || rebind) && i.apps.size() > 0) {
try {
- bumpServiceExecutingLocked(r, execInFg, "bind",
- OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE);
+ bumpServiceExecutingLocked(r, execInFg, "bind", OOM_ADJ_REASON_BIND_SERVICE);
if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, "requestServiceBinding="
+ i.intent.getIntent() + ". bindSeq=" + mBindServiceSeqCounter);
@@ -4457,13 +4469,15 @@
// Keep the executeNesting count accurate.
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r, e);
final boolean inDestroying = mDestroyingServices.contains(r);
- serviceDoneExecutingLocked(r, inDestroying, inDestroying, false);
+ serviceDoneExecutingLocked(r, inDestroying, inDestroying, false,
+ OOM_ADJ_REASON_UNBIND_SERVICE);
throw e;
} catch (RemoteException e) {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r);
// Keep the executeNesting count accurate.
final boolean inDestroying = mDestroyingServices.contains(r);
- serviceDoneExecutingLocked(r, inDestroying, inDestroying, false);
+ serviceDoneExecutingLocked(r, inDestroying, inDestroying, false,
+ OOM_ADJ_REASON_UNBIND_SERVICE);
return false;
}
}
@@ -4841,7 +4855,7 @@
// Ignore, it's been logged and nothing upstack cares.
} finally {
/* Will be a no-op if nothing pending */
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
}
}
@@ -5193,13 +5207,14 @@
final ProcessServiceRecord psr = app.mServices;
final boolean newService = psr.startService(r);
- bumpServiceExecutingLocked(r, execInFg, "create", OomAdjuster.OOM_ADJ_REASON_NONE);
+ bumpServiceExecutingLocked(r, execInFg, "create",
+ OOM_ADJ_REASON_NONE /* use "none" to avoid extra oom adj */);
mAm.updateLruProcessLocked(app, false, null);
updateServiceForegroundLocked(psr, /* oomAdj= */ false);
// Force an immediate oomAdjUpdate, so the client app could be in the correct process state
// before doing any service related transactions
mAm.enqueueOomAdjTargetLocked(app);
- mAm.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+ mAm.updateOomAdjLocked(app, OOM_ADJ_REASON_START_SERVICE);
boolean created = false;
try {
@@ -5233,7 +5248,8 @@
if (!created) {
// Keep the executeNesting count accurate.
final boolean inDestroying = mDestroyingServices.contains(r);
- serviceDoneExecutingLocked(r, inDestroying, inDestroying, false);
+ serviceDoneExecutingLocked(r, inDestroying, inDestroying, false,
+ OOM_ADJ_REASON_STOP_SERVICE);
// Cleanup.
if (newService) {
@@ -5319,7 +5335,8 @@
mAm.grantImplicitAccess(r.userId, si.intent, si.callingId,
UserHandle.getAppId(r.appInfo.uid)
);
- bumpServiceExecutingLocked(r, execInFg, "start", OomAdjuster.OOM_ADJ_REASON_NONE);
+ bumpServiceExecutingLocked(r, execInFg, "start",
+ OOM_ADJ_REASON_NONE /* use "none" to avoid extra oom adj */);
if (r.fgRequired && !r.fgWaiting) {
if (!r.isForeground) {
if (DEBUG_BACKGROUND_CHECK) {
@@ -5345,7 +5362,7 @@
if (!oomAdjusted) {
mAm.enqueueOomAdjTargetLocked(r.app);
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
}
ParceledListSlice<ServiceStartArgs> slice = new ParceledListSlice<>(args);
slice.setInlineCountLimit(4);
@@ -5371,10 +5388,11 @@
// Keep nesting count correct
final boolean inDestroying = mDestroyingServices.contains(r);
for (int i = 0, size = args.size(); i < size; i++) {
- serviceDoneExecutingLocked(r, inDestroying, inDestroying, true);
+ serviceDoneExecutingLocked(r, inDestroying, inDestroying, true,
+ OOM_ADJ_REASON_STOP_SERVICE);
}
/* Will be a no-op if nothing pending */
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_STOP_SERVICE);
if (caughtException instanceof TransactionTooLargeException) {
throw (TransactionTooLargeException)caughtException;
}
@@ -5461,7 +5479,7 @@
if (ibr.hasBound) {
try {
oomAdjusted |= bumpServiceExecutingLocked(r, false, "bring down unbind",
- OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+ OOM_ADJ_REASON_UNBIND_SERVICE);
ibr.hasBound = false;
ibr.requested = false;
r.app.getThread().scheduleUnbindService(r,
@@ -5615,7 +5633,7 @@
} else {
try {
oomAdjusted |= bumpServiceExecutingLocked(r, false, "destroy",
- oomAdjusted ? 0 : OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+ oomAdjusted ? 0 : OOM_ADJ_REASON_STOP_SERVICE);
mDestroyingServices.add(r);
r.destroying = true;
r.app.getThread().scheduleStopService(r);
@@ -5637,7 +5655,7 @@
if (!oomAdjusted) {
mAm.enqueueOomAdjTargetLocked(r.app);
if (!enqueueOomAdj) {
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_STOP_SERVICE);
}
}
if (r.bindings.size() > 0) {
@@ -5762,8 +5780,7 @@
if (s.app != null && s.app.getThread() != null && b.intent.apps.size() == 0
&& b.intent.hasBound) {
try {
- bumpServiceExecutingLocked(s, false, "unbind",
- OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+ bumpServiceExecutingLocked(s, false, "unbind", OOM_ADJ_REASON_UNBIND_SERVICE);
if (b.client != s.app && c.notHasFlag(Context.BIND_WAIVE_PRIORITY)
&& s.app.mState.getSetProcState() <= PROCESS_STATE_HEAVY_WEIGHT) {
// If this service's process is not already in the cached list,
@@ -5886,7 +5903,8 @@
}
}
final long origId = Binder.clearCallingIdentity();
- serviceDoneExecutingLocked(r, inDestroying, inDestroying, enqueueOomAdj);
+ serviceDoneExecutingLocked(r, inDestroying, inDestroying, enqueueOomAdj,
+ OOM_ADJ_REASON_EXECUTING_SERVICE);
Binder.restoreCallingIdentity(origId);
} else {
Slog.w(TAG, "Done executing unknown service from pid "
@@ -5905,11 +5923,11 @@
r.tracker.setStarted(false, memFactor, now);
}
}
- serviceDoneExecutingLocked(r, true, true, enqueueOomAdj);
+ serviceDoneExecutingLocked(r, true, true, enqueueOomAdj, OOM_ADJ_REASON_PROCESS_END);
}
private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying,
- boolean finishing, boolean enqueueOomAdj) {
+ boolean finishing, boolean enqueueOomAdj, @OomAdjReason int oomAdjReason) {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "<<< DONE EXECUTING " + r
+ ": nesting=" + r.executeNesting
+ ", inDestroying=" + inDestroying + ", app=" + r.app);
@@ -5945,7 +5963,7 @@
if (enqueueOomAdj) {
mAm.enqueueOomAdjTargetLocked(r.app);
} else {
- mAm.updateOomAdjLocked(r.app, OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+ mAm.updateOomAdjLocked(r.app, oomAdjReason);
}
}
r.executeFg = false;
@@ -6015,7 +6033,7 @@
bringDownServiceLocked(sr, true);
}
/* Will be a no-op if nothing pending */
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
}
} catch (RemoteException e) {
Slog.w(TAG, "Exception in new application when starting service "
@@ -6075,7 +6093,7 @@
}
}
if (needOomAdj) {
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_PROCESS_END);
}
}
@@ -6146,7 +6164,7 @@
bringDownServiceLocked(mTmpCollectionResults.get(i), true);
}
if (size > 0) {
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_COMPONENT_DISABLED);
}
if (fullStop && !mTmpCollectionResults.isEmpty()) {
// if we're tearing down the app's entire service state, account for possible
@@ -6273,7 +6291,7 @@
}
}
if (needOomAdj) {
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_REMOVE_TASK);
}
}
@@ -6444,7 +6462,7 @@
}
}
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_STOP_SERVICE);
if (!allowRestart) {
psr.stopAllServices();
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ef7d5ae..4a6c9b2 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -44,6 +44,14 @@
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_CREATED;
import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_DESTROYED;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHELL;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SYSTEM_INIT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.OP_NONE;
import static android.content.pm.ApplicationInfo.HIDDEN_API_ENFORCEMENT_DEFAULT;
@@ -199,6 +207,7 @@
import android.app.ActivityManagerInternal.BroadcastEventListener;
import android.app.ActivityManagerInternal.ForegroundServiceStateListener;
import android.app.ActivityManagerInternal.MediaProjectionTokenEvent;
+import android.app.ActivityManagerInternal.OomAdjReason;
import android.app.ActivityTaskManager.RootTaskInfo;
import android.app.ActivityThread;
import android.app.AnrController;
@@ -368,7 +377,6 @@
import android.util.IndentingPrintWriter;
import android.util.IntArray;
import android.util.Log;
-import android.util.LogWriter;
import android.util.Pair;
import android.util.PrintWriterPrinter;
import android.util.Slog;
@@ -1968,7 +1976,7 @@
app.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_SYSTEM);
addPidLocked(app);
updateLruProcessLocked(app, false, null);
- updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdjLocked(OOM_ADJ_REASON_SYSTEM_INIT);
}
} catch (PackageManager.NameNotFoundException e) {
throw new RuntimeException(
@@ -2502,7 +2510,7 @@
// bind background threads to little cores
// this is expected to fail inside of framework tests because apps can't touch cpusets directly
// make sure we've already adjusted system_server's internal view of itself first
- updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdjLocked(OOM_ADJ_REASON_SYSTEM_INIT);
try {
Process.setThreadGroupAndCpuset(BackgroundThread.get().getThreadId(),
Process.THREAD_GROUP_SYSTEM);
@@ -3387,7 +3395,7 @@
handleAppDiedLocked(app, pid, false, true, fromBinderDied);
if (doOomAdj) {
- updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_PROCESS_END);
+ updateOomAdjLocked(OOM_ADJ_REASON_PROCESS_END);
}
if (doLowMem) {
mAppProfiler.doLowMemReportIfNeededLocked(app);
@@ -4843,7 +4851,7 @@
}
if (!didSomething) {
- updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN);
+ updateOomAdjLocked(app, OOM_ADJ_REASON_PROCESS_BEGIN);
checkTime(startTime, "finishAttachApplicationInner: after updateOomAdjLocked");
}
@@ -5485,7 +5493,7 @@
"setProcessLimit()");
synchronized (this) {
mConstants.setOverrideMaxCachedProcesses(max);
- trimApplicationsLocked(true, OomAdjuster.OOM_ADJ_REASON_PROCESS_END);
+ trimApplicationsLocked(true, OOM_ADJ_REASON_PROCESS_END);
}
}
@@ -5513,7 +5521,7 @@
pr.mState.setForcingToImportant(null);
clearProcessForegroundLocked(pr);
}
- updateOomAdjLocked(pr, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+ updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY);
}
}
@@ -5560,7 +5568,7 @@
}
if (changed) {
- updateOomAdjLocked(pr, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+ updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY);
}
}
}
@@ -6869,7 +6877,7 @@
new HostingRecord(HostingRecord.HOSTING_TYPE_ADDED_APPLICATION,
customProcess != null ? customProcess : info.processName));
updateLruProcessLocked(app, false, null);
- updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN);
+ updateOomAdjLocked(app, OOM_ADJ_REASON_PROCESS_BEGIN);
}
// Report usage as process is persistent and being started.
@@ -6986,7 +6994,7 @@
mOomAdjProfiler.onWakefulnessChanged(wakefulness);
mOomAdjuster.onWakefulnessChanged(wakefulness);
- updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+ updateOomAdjLocked(OOM_ADJ_REASON_UI_VISIBILITY);
}
}
}
@@ -7748,7 +7756,7 @@
}
}
if (changed) {
- updateOomAdjLocked(pr, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+ updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY);
}
}
} finally {
@@ -9508,7 +9516,7 @@
mAppProfiler.setMemFactorOverrideLocked(level);
// Kick off an oom adj update since we forced a mem factor update.
- updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdjLocked(OOM_ADJ_REASON_SHELL);
}
}
@@ -13408,7 +13416,7 @@
proc.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_BACKUP);
// Try not to kill the process during backup
- updateOomAdjLocked(proc, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdjLocked(proc, OOM_ADJ_REASON_BACKUP);
// If the process is already attached, schedule the creation of the backup agent now.
// If it is not yet live, this will be done when it attaches to the framework.
@@ -13532,7 +13540,7 @@
// Not backing this app up any more; reset its OOM adjustment
final ProcessRecord proc = backupTarget.app;
- updateOomAdjLocked(proc, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdjLocked(proc, OOM_ADJ_REASON_BACKUP);
proc.setInFullBackup(false);
proc.mProfile.clearHostingComponentType(HOSTING_COMPONENT_TYPE_BACKUP);
@@ -13713,7 +13721,7 @@
if (!sdkSandboxManagerLocal.canRegisterBroadcastReceiver(
/*IntentFilter=*/ filter, flags, onlyProtectedBroadcasts)) {
throw new SecurityException("SDK sandbox not allowed to register receiver"
- + " with the given IntentFilter: " + filter.toString());
+ + " with the given IntentFilter: " + filter.toLongString());
}
}
@@ -13923,7 +13931,7 @@
// If we actually concluded any broadcasts, we might now be able
// to trim the recipients' apps from our working set
if (doTrim) {
- trimApplicationsLocked(false, OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER);
+ trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER);
return;
}
}
@@ -15185,7 +15193,7 @@
queue.finishReceiverLocked(callerApp, resultCode,
resultData, resultExtras, resultAbort, true);
// updateOomAdjLocked() will be done here
- trimApplicationsLocked(false, OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER);
+ trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER);
}
} finally {
@@ -16130,7 +16138,7 @@
item.foregroundServiceTypes = fgServiceTypes;
}
if (oomAdj) {
- updateOomAdjLocked(proc, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+ updateOomAdjLocked(proc, OOM_ADJ_REASON_UI_VISIBILITY);
}
}
@@ -16196,7 +16204,7 @@
* {@link #enqueueOomAdjTargetLocked}.
*/
@GuardedBy("this")
- void updateOomAdjPendingTargetsLocked(@OomAdjuster.OomAdjReason int oomAdjReason) {
+ void updateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) {
mOomAdjuster.updateOomAdjPendingTargetsLocked(oomAdjReason);
}
@@ -16215,7 +16223,7 @@
}
@GuardedBy("this")
- final void updateOomAdjLocked(@OomAdjuster.OomAdjReason int oomAdjReason) {
+ final void updateOomAdjLocked(@OomAdjReason int oomAdjReason) {
mOomAdjuster.updateOomAdjLocked(oomAdjReason);
}
@@ -16227,8 +16235,7 @@
* @return whether updateOomAdjLocked(app) was successful.
*/
@GuardedBy("this")
- final boolean updateOomAdjLocked(
- ProcessRecord app, @OomAdjuster.OomAdjReason int oomAdjReason) {
+ final boolean updateOomAdjLocked(ProcessRecord app, @OomAdjReason int oomAdjReason) {
return mOomAdjuster.updateOomAdjLocked(app, oomAdjReason);
}
@@ -16461,16 +16468,14 @@
mOomAdjuster.setUidTempAllowlistStateLSP(uid, onAllowlist);
}
- private void trimApplications(
- boolean forceFullOomAdj, @OomAdjuster.OomAdjReason int oomAdjReason) {
+ private void trimApplications(boolean forceFullOomAdj, @OomAdjReason int oomAdjReason) {
synchronized (this) {
trimApplicationsLocked(forceFullOomAdj, oomAdjReason);
}
}
@GuardedBy("this")
- private void trimApplicationsLocked(
- boolean forceFullOomAdj, @OomAdjuster.OomAdjReason int oomAdjReason) {
+ private void trimApplicationsLocked(boolean forceFullOomAdj, @OomAdjReason int oomAdjReason) {
// First remove any unused application processes whose package
// has been removed.
boolean didSomething = false;
@@ -17442,7 +17447,7 @@
}
pr.mState.setHasOverlayUi(hasOverlayUi);
//Slog.i(TAG, "Setting hasOverlayUi=" + pr.hasOverlayUi + " for pid=" + pid);
- updateOomAdjLocked(pr, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+ updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY);
}
}
@@ -17577,7 +17582,7 @@
@Override
public void trimApplications() {
- ActivityManagerService.this.trimApplications(true, OomAdjuster.OOM_ADJ_REASON_ACTIVITY);
+ ActivityManagerService.this.trimApplications(true, OOM_ADJ_REASON_ACTIVITY);
}
public void killProcessesForRemovedTask(ArrayList<Object> procsToKill) {
@@ -17626,9 +17631,9 @@
}
@Override
- public void updateOomAdj() {
+ public void updateOomAdj(@OomAdjReason int oomAdjReason) {
synchronized (ActivityManagerService.this) {
- ActivityManagerService.this.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ ActivityManagerService.this.updateOomAdjLocked(oomAdjReason);
}
}
@@ -18288,8 +18293,7 @@
// sends to the activity. After this race issue between WM/ATMS and AMS is solved, this
// workaround can be removed. (b/213288355)
if (isNewPending) {
- mOomAdjuster.mCachedAppOptimizer.unfreezeProcess(pid,
- OomAdjuster.OOM_ADJ_REASON_ACTIVITY);
+ mOomAdjuster.mCachedAppOptimizer.unfreezeProcess(pid, OOM_ADJ_REASON_ACTIVITY);
}
// We need to update the network rules for the app coming to the top state so that
// it can access network when the device or the app is in a restricted state
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index bfc8251..9e61ce4 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -18,7 +18,6 @@
import static com.android.internal.util.Preconditions.checkState;
import static com.android.server.am.BroadcastRecord.deliveryStateToString;
-import static com.android.server.am.BroadcastRecord.isDeliveryStateTerminal;
import static com.android.server.am.BroadcastRecord.isReceiverEquals;
import android.annotation.IntDef;
@@ -709,7 +708,7 @@
|| consecutiveHighPriorityCount >= maxHighPriorityDispatchLimit);
final boolean isLPQueueEligible = shouldConsiderLPQueue
&& nextLPRecord.enqueueTime <= nextHPRecord.enqueueTime
- && !blockedOnOrderedDispatch(nextLPRecord, nextLPRecordIndex);
+ && !nextLPRecord.isBlocked(nextLPRecordIndex);
return isLPQueueEligible ? lowPriorityQueue : highPriorityQueue;
}
@@ -912,39 +911,20 @@
}
}
- private boolean blockedOnOrderedDispatch(BroadcastRecord r, int index) {
- final int blockedUntilTerminalCount = r.blockedUntilTerminalCount[index];
-
- int existingDeferredCount = 0;
- if (r.deferUntilActive) {
- for (int i = 0; i < index; i++) {
- if (r.deferredUntilActive[i]) existingDeferredCount++;
- }
- }
-
- // We might be blocked waiting for other receivers to finish,
- // typically for an ordered broadcast or priority traunches
- if ((r.terminalCount + existingDeferredCount) < blockedUntilTerminalCount
- && !isDeliveryStateTerminal(r.getDeliveryState(index))) {
- return true;
- }
- return false;
- }
-
/**
* Update {@link #getRunnableAt()} if it's currently invalidated.
*/
private void updateRunnableAt() {
- final SomeArgs next = peekNextBroadcast();
+ if (!mRunnableAtInvalidated) return;
mRunnableAtInvalidated = false;
+
+ final SomeArgs next = peekNextBroadcast();
if (next != null) {
final BroadcastRecord r = (BroadcastRecord) next.arg1;
final int index = next.argi1;
final long runnableAt = r.enqueueTime;
- // If we're specifically queued behind other ordered dispatch activity,
- // we aren't runnable yet
- if (blockedOnOrderedDispatch(r, index)) {
+ if (r.isBlocked(index)) {
mRunnableAt = Long.MAX_VALUE;
mRunnableAtReason = REASON_BLOCKED;
return;
@@ -1262,12 +1242,12 @@
pw.print(info.activityInfo.name);
}
pw.println();
- final int blockedUntilTerminalCount = record.blockedUntilTerminalCount[recordIndex];
- if (blockedUntilTerminalCount != -1) {
+ final int blockedUntilBeyondCount = record.blockedUntilBeyondCount[recordIndex];
+ if (blockedUntilBeyondCount != -1) {
pw.print(" blocked until ");
- pw.print(blockedUntilTerminalCount);
+ pw.print(blockedUntilBeyondCount);
pw.print(", currently at ");
- pw.print(record.terminalCount);
+ pw.print(record.beyondCount);
pw.print(" of ");
pw.println(record.receivers.size());
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index bd36c3f..5a4d315 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -17,6 +17,7 @@
package com.android.server.am;
import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
import static android.text.TextUtils.formatSimple;
@@ -37,7 +38,6 @@
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
-import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_START_RECEIVER;
import android.annotation.NonNull;
import android.annotation.Nullable;
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index a4bdf61..e532c15 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -16,6 +16,7 @@
package com.android.server.am;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
@@ -38,7 +39,6 @@
import static com.android.server.am.BroadcastRecord.getReceiverProcessName;
import static com.android.server.am.BroadcastRecord.getReceiverUid;
import static com.android.server.am.BroadcastRecord.isDeliveryStateTerminal;
-import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_START_RECEIVER;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -1008,6 +1008,7 @@
}
final BroadcastRecord r = queue.getActive();
+ final int index = queue.getActiveIndex();
if (r.ordered) {
r.resultCode = resultCode;
r.resultData = resultData;
@@ -1015,18 +1016,24 @@
if (!r.isNoAbort()) {
r.resultAbort = resultAbort;
}
+ }
- // When the caller aborted an ordered broadcast, we mark all
- // remaining receivers as skipped
- if (r.resultAbort) {
- for (int i = r.terminalCount + 1; i < r.receivers.size(); i++) {
- setDeliveryState(null, null, r, i, r.receivers.get(i),
- BroadcastRecord.DELIVERY_SKIPPED, "resultAbort");
- }
+ // To ensure that "beyond" high-water marks are updated in a monotonic
+ // way, we finish this receiver before possibly skipping any remaining
+ // aborted receivers
+ final boolean res = finishReceiverActiveLocked(queue,
+ BroadcastRecord.DELIVERY_DELIVERED, "remote app");
+
+ // When the caller aborted an ordered broadcast, we mark all
+ // remaining receivers as skipped
+ if (r.resultAbort) {
+ for (int i = index + 1; i < r.receivers.size(); i++) {
+ setDeliveryState(null, null, r, i, r.receivers.get(i),
+ BroadcastRecord.DELIVERY_SKIPPED, "resultAbort");
}
}
- return finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_DELIVERED, "remote app");
+ return res;
}
/**
@@ -1108,21 +1115,10 @@
@NonNull Object receiver, @DeliveryState int newDeliveryState,
@NonNull String reason) {
final int cookie = traceBegin("setDeliveryState");
- final int oldDeliveryState = getDeliveryState(r, index);
- boolean checkFinished = false;
- // Only apply state when we haven't already reached a terminal state;
- // this is how we ignore racing timeout messages
- if (!isDeliveryStateTerminal(oldDeliveryState)) {
- r.setDeliveryState(index, newDeliveryState, reason);
- if (oldDeliveryState == BroadcastRecord.DELIVERY_DEFERRED) {
- r.deferredCount--;
- } else if (newDeliveryState == BroadcastRecord.DELIVERY_DEFERRED) {
- // If we're deferring a broadcast, maybe that's enough to unblock the final callback
- r.deferredCount++;
- checkFinished = true;
- }
- }
+ // Remember the old state and apply the new state
+ final int oldDeliveryState = getDeliveryState(r, index);
+ final boolean beyondCountChanged = r.setDeliveryState(index, newDeliveryState, reason);
// Emit any relevant tracing results when we're changing the delivery
// state as part of running from a queue
@@ -1147,15 +1143,13 @@
+ deliveryStateToString(newDeliveryState) + " because " + reason);
}
- r.terminalCount++;
notifyFinishReceiver(queue, app, r, index, receiver);
- checkFinished = true;
}
- // When entire ordered broadcast finished, deliver final result
- if (checkFinished) {
- final boolean recordFinished =
- ((r.terminalCount + r.deferredCount) == r.receivers.size());
- if (recordFinished) {
+
+ // When we've reached a new high-water mark, we might be in a position
+ // to unblock other receivers or the final resultTo
+ if (beyondCountChanged) {
+ if (r.beyondCount == r.receivers.size()) {
scheduleResultTo(r);
}
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index c368290..64fe393 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -24,6 +24,7 @@
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE;
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_TARGET_T_ONLY;
+import android.annotation.CheckResult;
import android.annotation.CurrentTimeMillisLong;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.IntDef;
@@ -101,8 +102,7 @@
final @NonNull List<Object> receivers; // contains BroadcastFilter and ResolveInfo
final @DeliveryState int[] delivery; // delivery state of each receiver
final @NonNull String[] deliveryReasons; // reasons for delivery state of each receiver
- final boolean[] deferredUntilActive; // whether each receiver is infinitely deferred
- final int[] blockedUntilTerminalCount; // blocked until count of each receiver
+ final int[] blockedUntilBeyondCount; // blocked until count of each receiver
@Nullable ProcessRecord resultToApp; // who receives final result if non-null
@Nullable IIntentReceiver resultTo; // who receives final result if non-null
boolean deferred;
@@ -134,6 +134,7 @@
int manifestSkipCount; // number of manifest receivers skipped.
int terminalCount; // number of receivers in terminal state.
int deferredCount; // number of receivers in deferred state.
+ int beyondCount; // high-water number of receivers we've moved beyond.
@Nullable BroadcastQueue queue; // the outbound queue handling this broadcast
// Determines the privileges the app's process has in regard to background starts.
@@ -219,6 +220,23 @@
}
}
+ /**
+ * Return if the given delivery state is "beyond", which means that we've
+ * moved beyond this receiver, and future receivers are now unblocked.
+ */
+ static boolean isDeliveryStateBeyond(@DeliveryState int deliveryState) {
+ switch (deliveryState) {
+ case DELIVERY_DELIVERED:
+ case DELIVERY_SKIPPED:
+ case DELIVERY_TIMEOUT:
+ case DELIVERY_FAILURE:
+ case DELIVERY_DEFERRED:
+ return true;
+ default:
+ return false;
+ }
+ }
+
ProcessRecord curApp; // hosting application of current receiver.
ComponentName curComponent; // the receiver class that is currently running.
ActivityInfo curReceiver; // the manifest receiver that is currently running.
@@ -356,7 +374,7 @@
TimeUtils.formatDuration(terminalTime[i] - scheduledTime[i], pw);
pw.print(' ');
}
- pw.print("("); pw.print(blockedUntilTerminalCount[i]); pw.print(") ");
+ pw.print("("); pw.print(blockedUntilBeyondCount[i]); pw.print(") ");
pw.print("#"); pw.print(i); pw.print(": ");
if (o instanceof BroadcastFilter) {
pw.println(o);
@@ -411,8 +429,7 @@
urgent = calculateUrgent(_intent, _options);
deferUntilActive = calculateDeferUntilActive(_callingUid,
_options, _resultTo, _serialized, urgent);
- deferredUntilActive = new boolean[deferUntilActive ? delivery.length : 0];
- blockedUntilTerminalCount = calculateBlockedUntilTerminalCount(receivers, _serialized);
+ blockedUntilBeyondCount = calculateBlockedUntilBeyondCount(receivers, _serialized);
scheduledTime = new long[delivery.length];
terminalTime = new long[delivery.length];
resultToApp = _resultToApp;
@@ -423,7 +440,7 @@
ordered = _serialized;
sticky = _sticky;
initialSticky = _initialSticky;
- prioritized = isPrioritized(blockedUntilTerminalCount, _serialized);
+ prioritized = isPrioritized(blockedUntilBeyondCount, _serialized);
userId = _userId;
nextReceiver = 0;
state = IDLE;
@@ -467,8 +484,7 @@
delivery = from.delivery;
deliveryReasons = from.deliveryReasons;
deferUntilActive = from.deferUntilActive;
- deferredUntilActive = from.deferredUntilActive;
- blockedUntilTerminalCount = from.blockedUntilTerminalCount;
+ blockedUntilBeyondCount = from.blockedUntilBeyondCount;
scheduledTime = from.scheduledTime;
terminalTime = from.terminalTime;
resultToApp = from.resultToApp;
@@ -627,32 +643,72 @@
/**
* Update the delivery state of the given {@link #receivers} index.
* Automatically updates any time measurements related to state changes.
+ *
+ * @return if {@link #beyondCount} changed due to this state transition,
+ * indicating that other events may be unblocked.
*/
- void setDeliveryState(int index, @DeliveryState int deliveryState,
+ @CheckResult
+ boolean setDeliveryState(int index, @DeliveryState int newDeliveryState,
@NonNull String reason) {
- delivery[index] = deliveryState;
- deliveryReasons[index] = reason;
- if (deferUntilActive) deferredUntilActive[index] = false;
- switch (deliveryState) {
+ final int oldDeliveryState = delivery[index];
+ if (isDeliveryStateTerminal(oldDeliveryState)
+ || newDeliveryState == oldDeliveryState) {
+ // We've already arrived in terminal or requested state, so leave
+ // any statistics and reasons intact from the first transition
+ return false;
+ }
+
+ switch (oldDeliveryState) {
+ case DELIVERY_DEFERRED:
+ deferredCount--;
+ break;
+ }
+ switch (newDeliveryState) {
+ case DELIVERY_SCHEDULED:
+ scheduledTime[index] = SystemClock.uptimeMillis();
+ break;
+ case DELIVERY_DEFERRED:
+ deferredCount++;
+ break;
case DELIVERY_DELIVERED:
case DELIVERY_SKIPPED:
case DELIVERY_TIMEOUT:
case DELIVERY_FAILURE:
terminalTime[index] = SystemClock.uptimeMillis();
- break;
- case DELIVERY_SCHEDULED:
- scheduledTime[index] = SystemClock.uptimeMillis();
- break;
- case DELIVERY_DEFERRED:
- if (deferUntilActive) deferredUntilActive[index] = true;
+ terminalCount++;
break;
}
+
+ delivery[index] = newDeliveryState;
+ deliveryReasons[index] = reason;
+
+ // If this state change might bring us to a new high-water mark, bring
+ // ourselves as high as we possibly can
+ final int oldBeyondCount = beyondCount;
+ if (index >= beyondCount) {
+ for (int i = beyondCount; i < delivery.length; i++) {
+ if (isDeliveryStateBeyond(getDeliveryState(i))) {
+ beyondCount = i + 1;
+ } else {
+ break;
+ }
+ }
+ }
+ return (beyondCount != oldBeyondCount);
}
@DeliveryState int getDeliveryState(int index) {
return delivery[index];
}
+ /**
+ * @return if the given {@link #receivers} index should be considered
+ * blocked based on the current status of the overall broadcast.
+ */
+ boolean isBlocked(int index) {
+ return (beyondCount < blockedUntilBeyondCount[index]);
+ }
+
boolean wasDeliveryAttempted(int index) {
final int deliveryState = getDeliveryState(index);
switch (deliveryState) {
@@ -757,36 +813,36 @@
* has prioritized tranches of receivers.
*/
@VisibleForTesting
- static boolean isPrioritized(@NonNull int[] blockedUntilTerminalCount,
+ static boolean isPrioritized(@NonNull int[] blockedUntilBeyondCount,
boolean ordered) {
- return !ordered && (blockedUntilTerminalCount.length > 0)
- && (blockedUntilTerminalCount[0] != -1);
+ return !ordered && (blockedUntilBeyondCount.length > 0)
+ && (blockedUntilBeyondCount[0] != -1);
}
/**
- * Calculate the {@link #terminalCount} that each receiver should be
+ * Calculate the {@link #beyondCount} that each receiver should be
* considered blocked until.
* <p>
* For example, in an ordered broadcast, receiver {@code N} is blocked until
- * receiver {@code N-1} reaches a terminal state. Similarly, in a
- * prioritized broadcast, receiver {@code N} is blocked until all receivers
- * of a higher priority reach a terminal state.
+ * receiver {@code N-1} reaches a terminal or deferred state. Similarly, in
+ * a prioritized broadcast, receiver {@code N} is blocked until all
+ * receivers of a higher priority reach a terminal or deferred state.
* <p>
- * When there are no terminal count constraints, the blocked value for each
+ * When there are no beyond count constraints, the blocked value for each
* receiver is {@code -1}.
*/
@VisibleForTesting
- static @NonNull int[] calculateBlockedUntilTerminalCount(
+ static @NonNull int[] calculateBlockedUntilBeyondCount(
@NonNull List<Object> receivers, boolean ordered) {
final int N = receivers.size();
- final int[] blockedUntilTerminalCount = new int[N];
+ final int[] blockedUntilBeyondCount = new int[N];
int lastPriority = 0;
int lastPriorityIndex = 0;
for (int i = 0; i < N; i++) {
if (ordered) {
// When sending an ordered broadcast, we need to block this
// receiver until all previous receivers have terminated
- blockedUntilTerminalCount[i] = i;
+ blockedUntilBeyondCount[i] = i;
} else {
// When sending a prioritized broadcast, we only need to wait
// for the previous tranche of receivers to be terminated
@@ -794,18 +850,18 @@
if ((i == 0) || (thisPriority != lastPriority)) {
lastPriority = thisPriority;
lastPriorityIndex = i;
- blockedUntilTerminalCount[i] = i;
+ blockedUntilBeyondCount[i] = i;
} else {
- blockedUntilTerminalCount[i] = lastPriorityIndex;
+ blockedUntilBeyondCount[i] = lastPriorityIndex;
}
}
}
// If the entire list is in the same priority tranche, mark as -1 to
// indicate that none of them need to wait
- if (N > 0 && blockedUntilTerminalCount[N - 1] == 0) {
- Arrays.fill(blockedUntilTerminalCount, -1);
+ if (N > 0 && blockedUntilBeyondCount[N - 1] == 0) {
+ Arrays.fill(blockedUntilBeyondCount, -1);
}
- return blockedUntilTerminalCount;
+ return blockedUntilBeyondCount;
}
static int getReceiverUid(@NonNull Object receiver) {
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 568997b..f42087f 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -18,6 +18,28 @@
import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN;
import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_UNFROZEN;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ALLOWLIST;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BIND_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_COMPONENT_DISABLED;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_EXECUTING_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_GET_PROVIDER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_PROVIDER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_TASK;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHELL;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_STOP_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SYSTEM_INIT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UID_IDLE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UNBIND_SERVICE;
import static android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_COMPACTION;
@@ -26,6 +48,7 @@
import android.annotation.IntDef;
import android.app.ActivityManager;
+import android.app.ActivityManagerInternal.OomAdjReason;
import android.app.ActivityThread;
import android.app.ApplicationExitInfo;
import android.app.IApplicationThread;
@@ -139,6 +162,26 @@
FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_BINDER_TXNS;
static final int UNFREEZE_REASON_FEATURE_FLAGS =
FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_FEATURE_FLAGS;
+ static final int UNFREEZE_REASON_SHORT_FGS_TIMEOUT =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_SHORT_FGS_TIMEOUT;
+ static final int UNFREEZE_REASON_SYSTEM_INIT =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_SYSTEM_INIT;
+ static final int UNFREEZE_REASON_BACKUP =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_BACKUP;
+ static final int UNFREEZE_REASON_SHELL =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_SHELL;
+ static final int UNFREEZE_REASON_REMOVE_TASK =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_REMOVE_TASK;
+ static final int UNFREEZE_REASON_UID_IDLE =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_UID_IDLE;
+ static final int UNFREEZE_REASON_STOP_SERVICE =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_STOP_SERVICE;
+ static final int UNFREEZE_REASON_EXECUTING_SERVICE =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_EXECUTING_SERVICE;
+ static final int UNFREEZE_REASON_RESTRICTION_CHANGE =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_RESTRICTION_CHANGE;
+ static final int UNFREEZE_REASON_COMPONENT_DISABLED =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_COMPONENT_DISABLED;
@IntDef(prefix = {"UNFREEZE_REASON_"}, value = {
UNFREEZE_REASON_NONE,
@@ -160,6 +203,16 @@
UNFREEZE_REASON_FILE_LOCK_CHECK_FAILURE,
UNFREEZE_REASON_BINDER_TXNS,
UNFREEZE_REASON_FEATURE_FLAGS,
+ UNFREEZE_REASON_SHORT_FGS_TIMEOUT,
+ UNFREEZE_REASON_SYSTEM_INIT,
+ UNFREEZE_REASON_BACKUP,
+ UNFREEZE_REASON_SHELL,
+ UNFREEZE_REASON_REMOVE_TASK,
+ UNFREEZE_REASON_UID_IDLE,
+ UNFREEZE_REASON_STOP_SERVICE,
+ UNFREEZE_REASON_EXECUTING_SERVICE,
+ UNFREEZE_REASON_RESTRICTION_CHANGE,
+ UNFREEZE_REASON_COMPONENT_DISABLED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface UnfreezeReason {}
@@ -1329,7 +1382,7 @@
}
try {
- traceAppFreeze(app.processName, pid, false);
+ traceAppFreeze(app.processName, pid, reason);
Process.setProcessFrozen(pid, app.uid, false);
opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis());
@@ -1341,7 +1394,7 @@
}
if (!opt.isFrozen()) {
- Slog.d(TAG_AM, "sync unfroze " + pid + " " + app.processName);
+ Slog.d(TAG_AM, "sync unfroze " + pid + " " + app.processName + " for " + reason);
mFreezeHandler.sendMessage(
mFreezeHandler.obtainMessage(REPORT_UNFREEZE_MSG,
@@ -1365,13 +1418,13 @@
* The caller of this function should still trigger updateOomAdj for AMS to unfreeze the app.
* @param pid pid of the process to be unfrozen
*/
- void unfreezeProcess(int pid, @OomAdjuster.OomAdjReason int reason) {
+ void unfreezeProcess(int pid, @OomAdjReason int reason) {
synchronized (mFreezerLock) {
ProcessRecord app = mFrozenProcesses.get(pid);
if (app == null) {
return;
}
- Slog.d(TAG_AM, "quick sync unfreeze " + pid);
+ Slog.d(TAG_AM, "quick sync unfreeze " + pid + " for " + reason);
try {
freezeBinder(pid, false, FREEZE_BINDER_TIMEOUT_MS);
} catch (RuntimeException e) {
@@ -1380,7 +1433,7 @@
}
try {
- traceAppFreeze(app.processName, pid, false);
+ traceAppFreeze(app.processName, pid, reason);
Process.setProcessFrozen(pid, app.uid, false);
} catch (Exception e) {
Slog.e(TAG_AM, "Unable to quick unfreeze " + pid);
@@ -1388,9 +1441,15 @@
}
}
- private static void traceAppFreeze(String processName, int pid, boolean freeze) {
+ /**
+ * Trace app freeze status
+ * @param processName The name of the target process
+ * @param pid The pid of the target process
+ * @param reason UNFREEZE_REASON_XXX (>=0) for unfreezing and -1 for freezing
+ */
+ private static void traceAppFreeze(String processName, int pid, int reason) {
Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_FREEZER_TRACK,
- (freeze ? "Freeze " : "Unfreeze ") + processName + ":" + pid);
+ (reason < 0 ? "Freeze " : "Unfreeze ") + processName + ":" + pid + " " + reason);
}
/**
@@ -1540,12 +1599,12 @@
public long mOrigAnonRss;
public int mProcState;
public int mOomAdj;
- public @OomAdjuster.OomAdjReason int mOomAdjReason;
+ public @OomAdjReason int mOomAdjReason;
SingleCompactionStats(long[] rss, CompactSource source, String processName,
long deltaAnonRss, long zramConsumed, long anonMemFreed, long origAnonRss,
long cpuTimeMillis, int procState, int oomAdj,
- @OomAdjuster.OomAdjReason int oomAdjReason, int uid) {
+ @OomAdjReason int oomAdjReason, int uid) {
mRssAfterCompaction = rss;
mSourceType = source;
mProcessName = processName;
@@ -2063,7 +2122,7 @@
long unfreezeTime = opt.getFreezeUnfreezeTime();
try {
- traceAppFreeze(proc.processName, pid, true);
+ traceAppFreeze(proc.processName, pid, -1);
Process.setProcessFrozen(pid, proc.uid, true);
opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis());
@@ -2127,7 +2186,7 @@
private void reportUnfreeze(int pid, int frozenDuration, String processName,
@UnfreezeReason int reason) {
- EventLog.writeEvent(EventLogTags.AM_UNFREEZE, pid, processName);
+ EventLog.writeEvent(EventLogTags.AM_UNFREEZE, pid, processName, reason);
// See above for why we're not taking mPhenotypeFlagLock here
if (mRandom.nextFloat() < mFreezerStatsdSampleRate) {
@@ -2201,32 +2260,52 @@
}
}
- static int getUnfreezeReasonCodeFromOomAdjReason(@OomAdjuster.OomAdjReason int oomAdjReason) {
+ static int getUnfreezeReasonCodeFromOomAdjReason(@OomAdjReason int oomAdjReason) {
switch (oomAdjReason) {
- case OomAdjuster.OOM_ADJ_REASON_ACTIVITY:
+ case OOM_ADJ_REASON_ACTIVITY:
return UNFREEZE_REASON_ACTIVITY;
- case OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER:
+ case OOM_ADJ_REASON_FINISH_RECEIVER:
return UNFREEZE_REASON_FINISH_RECEIVER;
- case OomAdjuster.OOM_ADJ_REASON_START_RECEIVER:
+ case OOM_ADJ_REASON_START_RECEIVER:
return UNFREEZE_REASON_START_RECEIVER;
- case OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE:
+ case OOM_ADJ_REASON_BIND_SERVICE:
return UNFREEZE_REASON_BIND_SERVICE;
- case OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE:
+ case OOM_ADJ_REASON_UNBIND_SERVICE:
return UNFREEZE_REASON_UNBIND_SERVICE;
- case OomAdjuster.OOM_ADJ_REASON_START_SERVICE:
+ case OOM_ADJ_REASON_START_SERVICE:
return UNFREEZE_REASON_START_SERVICE;
- case OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER:
+ case OOM_ADJ_REASON_GET_PROVIDER:
return UNFREEZE_REASON_GET_PROVIDER;
- case OomAdjuster.OOM_ADJ_REASON_REMOVE_PROVIDER:
+ case OOM_ADJ_REASON_REMOVE_PROVIDER:
return UNFREEZE_REASON_REMOVE_PROVIDER;
- case OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY:
+ case OOM_ADJ_REASON_UI_VISIBILITY:
return UNFREEZE_REASON_UI_VISIBILITY;
- case OomAdjuster.OOM_ADJ_REASON_ALLOWLIST:
+ case OOM_ADJ_REASON_ALLOWLIST:
return UNFREEZE_REASON_ALLOWLIST;
- case OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN:
+ case OOM_ADJ_REASON_PROCESS_BEGIN:
return UNFREEZE_REASON_PROCESS_BEGIN;
- case OomAdjuster.OOM_ADJ_REASON_PROCESS_END:
+ case OOM_ADJ_REASON_PROCESS_END:
return UNFREEZE_REASON_PROCESS_END;
+ case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT:
+ return UNFREEZE_REASON_SHORT_FGS_TIMEOUT;
+ case OOM_ADJ_REASON_SYSTEM_INIT:
+ return UNFREEZE_REASON_SYSTEM_INIT;
+ case OOM_ADJ_REASON_BACKUP:
+ return UNFREEZE_REASON_BACKUP;
+ case OOM_ADJ_REASON_SHELL:
+ return UNFREEZE_REASON_SHELL;
+ case OOM_ADJ_REASON_REMOVE_TASK:
+ return UNFREEZE_REASON_REMOVE_TASK;
+ case OOM_ADJ_REASON_UID_IDLE:
+ return UNFREEZE_REASON_UID_IDLE;
+ case OOM_ADJ_REASON_STOP_SERVICE:
+ return UNFREEZE_REASON_STOP_SERVICE;
+ case OOM_ADJ_REASON_EXECUTING_SERVICE:
+ return UNFREEZE_REASON_EXECUTING_SERVICE;
+ case OOM_ADJ_REASON_RESTRICTION_CHANGE:
+ return UNFREEZE_REASON_RESTRICTION_CHANGE;
+ case OOM_ADJ_REASON_COMPONENT_DISABLED:
+ return UNFREEZE_REASON_COMPONENT_DISABLED;
default:
return UNFREEZE_REASON_NONE;
}
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index a1fcd42..d8cb094 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -16,6 +16,8 @@
package com.android.server.am;
import static android.Manifest.permission.GET_ANY_PROVIDER_TYPE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_GET_PROVIDER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_PROVIDER;
import static android.content.ContentProvider.isAuthorityRedirectedForCloneProfile;
import static android.os.Process.PROC_CHAR;
import static android.os.Process.PROC_OUT_LONG;
@@ -292,7 +294,7 @@
checkTime(startTime, "getContentProviderImpl: before updateOomAdj");
final int verifiedAdj = cpr.proc.mState.getVerifiedAdj();
boolean success = mService.updateOomAdjLocked(cpr.proc,
- OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER);
+ OOM_ADJ_REASON_GET_PROVIDER);
// XXX things have changed so updateOomAdjLocked doesn't actually tell us
// if the process has been successfully adjusted. So to reduce races with
// it, we will check whether the process still exists. Note that this doesn't
@@ -757,7 +759,7 @@
// update the app's oom adj value and each provider's usage stats
if (providersPublished) {
- mService.updateOomAdjLocked(r, OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER);
+ mService.updateOomAdjLocked(r, OOM_ADJ_REASON_GET_PROVIDER);
for (int i = 0, size = providers.size(); i < size; i++) {
ContentProviderHolder src = providers.get(i);
if (src == null || src.info == null || src.provider == null) {
@@ -835,8 +837,7 @@
ContentProviderRecord localCpr = mProviderMap.getProviderByClass(comp, userId);
if (localCpr.hasExternalProcessHandles()) {
if (localCpr.removeExternalProcessHandleLocked(token)) {
- mService.updateOomAdjLocked(localCpr.proc,
- OomAdjuster.OOM_ADJ_REASON_REMOVE_PROVIDER);
+ mService.updateOomAdjLocked(localCpr.proc, OOM_ADJ_REASON_REMOVE_PROVIDER);
} else {
Slog.e(TAG, "Attempt to remove content provider " + localCpr
+ " with no external reference for token: " + token + ".");
@@ -1506,8 +1507,7 @@
mService.stopAssociationLocked(conn.client.uid, conn.client.processName, cpr.uid,
cpr.appInfo.longVersionCode, cpr.name, cpr.info.processName);
if (updateOomAdj) {
- mService.updateOomAdjLocked(conn.provider.proc,
- OomAdjuster.OOM_ADJ_REASON_REMOVE_PROVIDER);
+ mService.updateOomAdjLocked(conn.provider.proc, OOM_ADJ_REASON_REMOVE_PROVIDER);
}
}
}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index a98571b..365dcd9 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -41,6 +41,29 @@
import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
import static android.app.ActivityManager.PROCESS_STATE_TOP;
import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ALLOWLIST;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BIND_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_COMPONENT_DISABLED;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_EXECUTING_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_GET_PROVIDER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_NONE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_PROVIDER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_TASK;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHELL;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_STOP_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SYSTEM_INIT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UID_IDLE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UNBIND_SERVICE;
import static android.content.Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
@@ -101,9 +124,9 @@
import static com.android.server.am.ProcessList.VISIBLE_APP_ADJ;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
-import android.annotation.IntDef;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.ActivityManagerInternal.OomAdjReason;
import android.app.ActivityThread;
import android.app.AppProtoEnums;
import android.app.ApplicationExitInfo;
@@ -141,8 +164,6 @@
import com.android.server.wm.WindowProcessController;
import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
@@ -154,32 +175,6 @@
public class OomAdjuster {
static final String TAG = "OomAdjuster";
- static final int OOM_ADJ_REASON_NONE = 0;
- static final int OOM_ADJ_REASON_ACTIVITY = 1;
- static final int OOM_ADJ_REASON_FINISH_RECEIVER = 2;
- static final int OOM_ADJ_REASON_START_RECEIVER = 3;
- static final int OOM_ADJ_REASON_BIND_SERVICE = 4;
- static final int OOM_ADJ_REASON_UNBIND_SERVICE = 5;
- static final int OOM_ADJ_REASON_START_SERVICE = 6;
- static final int OOM_ADJ_REASON_GET_PROVIDER = 7;
- static final int OOM_ADJ_REASON_REMOVE_PROVIDER = 8;
- static final int OOM_ADJ_REASON_UI_VISIBILITY = 9;
- static final int OOM_ADJ_REASON_ALLOWLIST = 10;
- static final int OOM_ADJ_REASON_PROCESS_BEGIN = 11;
- static final int OOM_ADJ_REASON_PROCESS_END = 12;
- static final int OOM_ADJ_REASON_SHORT_FGS_TIMEOUT = 13;
-
- @IntDef(prefix = {"OOM_ADJ_REASON_"},
- value = {OOM_ADJ_REASON_NONE, OOM_ADJ_REASON_ACTIVITY, OOM_ADJ_REASON_FINISH_RECEIVER,
- OOM_ADJ_REASON_START_RECEIVER, OOM_ADJ_REASON_BIND_SERVICE,
- OOM_ADJ_REASON_UNBIND_SERVICE, OOM_ADJ_REASON_START_SERVICE,
- OOM_ADJ_REASON_GET_PROVIDER, OOM_ADJ_REASON_REMOVE_PROVIDER,
- OOM_ADJ_REASON_UI_VISIBILITY, OOM_ADJ_REASON_ALLOWLIST,
- OOM_ADJ_REASON_PROCESS_BEGIN, OOM_ADJ_REASON_PROCESS_END,
- OOM_ADJ_REASON_SHORT_FGS_TIMEOUT})
- @Retention(RetentionPolicy.SOURCE)
- public @interface OomAdjReason {}
-
public static final int oomAdjReasonToProto(@OomAdjReason int oomReason) {
switch (oomReason) {
case OOM_ADJ_REASON_NONE:
@@ -210,6 +205,24 @@
return AppProtoEnums.OOM_ADJ_REASON_PROCESS_END;
case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT:
return AppProtoEnums.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT;
+ case OOM_ADJ_REASON_SYSTEM_INIT:
+ return AppProtoEnums.OOM_ADJ_REASON_SYSTEM_INIT;
+ case OOM_ADJ_REASON_BACKUP:
+ return AppProtoEnums.OOM_ADJ_REASON_BACKUP;
+ case OOM_ADJ_REASON_SHELL:
+ return AppProtoEnums.OOM_ADJ_REASON_SHELL;
+ case OOM_ADJ_REASON_REMOVE_TASK:
+ return AppProtoEnums.OOM_ADJ_REASON_REMOVE_TASK;
+ case OOM_ADJ_REASON_UID_IDLE:
+ return AppProtoEnums.OOM_ADJ_REASON_UID_IDLE;
+ case OOM_ADJ_REASON_STOP_SERVICE:
+ return AppProtoEnums.OOM_ADJ_REASON_STOP_SERVICE;
+ case OOM_ADJ_REASON_EXECUTING_SERVICE:
+ return AppProtoEnums.OOM_ADJ_REASON_EXECUTING_SERVICE;
+ case OOM_ADJ_REASON_RESTRICTION_CHANGE:
+ return AppProtoEnums.OOM_ADJ_REASON_RESTRICTION_CHANGE;
+ case OOM_ADJ_REASON_COMPONENT_DISABLED:
+ return AppProtoEnums.OOM_ADJ_REASON_COMPONENT_DISABLED;
default:
return AppProtoEnums.OOM_ADJ_REASON_UNKNOWN_TO_PROTO;
}
@@ -246,6 +259,24 @@
return OOM_ADJ_REASON_METHOD + "_processEnd";
case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT:
return OOM_ADJ_REASON_METHOD + "_shortFgs";
+ case OOM_ADJ_REASON_SYSTEM_INIT:
+ return OOM_ADJ_REASON_METHOD + "_systemInit";
+ case OOM_ADJ_REASON_BACKUP:
+ return OOM_ADJ_REASON_METHOD + "_backup";
+ case OOM_ADJ_REASON_SHELL:
+ return OOM_ADJ_REASON_METHOD + "_shell";
+ case OOM_ADJ_REASON_REMOVE_TASK:
+ return OOM_ADJ_REASON_METHOD + "_removeTask";
+ case OOM_ADJ_REASON_UID_IDLE:
+ return OOM_ADJ_REASON_METHOD + "_uidIdle";
+ case OOM_ADJ_REASON_STOP_SERVICE:
+ return OOM_ADJ_REASON_METHOD + "_stopService";
+ case OOM_ADJ_REASON_EXECUTING_SERVICE:
+ return OOM_ADJ_REASON_METHOD + "_executingService";
+ case OOM_ADJ_REASON_RESTRICTION_CHANGE:
+ return OOM_ADJ_REASON_METHOD + "_restrictionChange";
+ case OOM_ADJ_REASON_COMPONENT_DISABLED:
+ return OOM_ADJ_REASON_METHOD + "_componentDisabled";
default:
return "_unknown";
}
@@ -874,8 +905,7 @@
}
@GuardedBy("mService")
- private void performUpdateOomAdjPendingTargetsLocked(
- @OomAdjuster.OomAdjReason int oomAdjReason) {
+ private void performUpdateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) {
final ProcessRecord topApp = mService.getTopApp();
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
@@ -3453,7 +3483,7 @@
}
@GuardedBy("mService")
- void unfreezeTemporarily(ProcessRecord app, @OomAdjuster.OomAdjReason int reason) {
+ void unfreezeTemporarily(ProcessRecord app, @OomAdjReason int reason) {
if (!mCachedAppOptimizer.useFreezer()) {
return;
}
diff --git a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
index 24cc533..f233107 100644
--- a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
+++ b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
@@ -16,6 +16,8 @@
package com.android.server.am;
+import android.app.ActivityManagerInternal.OomAdjReason;
+
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -51,7 +53,7 @@
/**
* Last oom adjust change reason for this app.
*/
- @GuardedBy("mProcLock") private @OomAdjuster.OomAdjReason int mLastOomAdjChangeReason;
+ @GuardedBy("mProcLock") private @OomAdjReason int mLastOomAdjChangeReason;
/**
* The most recent compaction action performed for this app.
@@ -139,12 +141,12 @@
}
@GuardedBy("mProcLock")
- void setLastOomAdjChangeReason(@OomAdjuster.OomAdjReason int reason) {
+ void setLastOomAdjChangeReason(@OomAdjReason int reason) {
mLastOomAdjChangeReason = reason;
}
@GuardedBy("mProcLock")
- @OomAdjuster.OomAdjReason
+ @OomAdjReason
int getLastOomAdjChangeReason() {
return mLastOomAdjChangeReason;
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index b1322ef..a237a07 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -19,6 +19,8 @@
import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE;
import static android.app.ActivityThread.PROC_START_SEQ_IDENT;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO;
import static android.net.NetworkPolicyManager.isProcStateAllowedWhileIdleOrPowerSaveMode;
@@ -2875,7 +2877,7 @@
reasonCode, subReason, reason, !doFreeze /* async */);
}
killAppZygotesLocked(packageName, appId, userId, false /* force */);
- mService.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_PROCESS_END);
+ mService.updateOomAdjLocked(OOM_ADJ_REASON_PROCESS_END);
if (doFreeze) {
freezePackageCgroup(packageUID, false);
}
@@ -5140,7 +5142,7 @@
}
});
/* Will be a no-op if nothing pending */
- mService.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_RESTRICTION_CHANGE);
}
}
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index afae623..7aae4d5 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -16,6 +16,8 @@
package com.android.server.am;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
+
import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -1450,7 +1452,7 @@
}
mService.updateLruProcessLocked(this, activityChange, null /* client */);
if (updateOomAdj) {
- mService.updateOomAdjLocked(this, OomAdjuster.OOM_ADJ_REASON_ACTIVITY);
+ mService.updateOomAdjLocked(this, OOM_ADJ_REASON_ACTIVITY);
}
}
}
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index 71d5d39..ab71acd 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -18,6 +18,7 @@
import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
import static com.android.server.am.ProcessProfileRecord.HOSTING_COMPONENT_TYPE_ACTIVITY;
@@ -613,7 +614,7 @@
void forceProcessStateUpTo(int newState) {
if (mRepProcState > newState) {
synchronized (mProcLock) {
- mRepProcState = newState;
+ setReportedProcState(newState);
setCurProcState(newState);
setCurRawProcState(newState);
}
@@ -766,7 +767,7 @@
Slog.i(TAG, "Setting runningRemoteAnimation=" + runningRemoteAnimation
+ " for pid=" + mApp.getPid());
}
- mService.updateOomAdjLocked(mApp, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+ mService.updateOomAdjLocked(mApp, OOM_ADJ_REASON_UI_VISIBILITY);
}
@GuardedBy({"mService", "mProcLock"})
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 22e2c9f..8c227f5 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -100,7 +100,6 @@
DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
DeviceConfig.NAMESPACE_SURFACE_FLINGER_NATIVE_BOOT,
DeviceConfig.NAMESPACE_SWCODEC_NATIVE,
- DeviceConfig.NAMESPACE_TETHERING,
DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE,
DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE_BOOT,
DeviceConfig.NAMESPACE_VIRTUALIZATION_FRAMEWORK_NATIVE,
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 81ba4b8..a110169 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -34,6 +34,7 @@
import static android.app.AppOpsManager.MODE_FOREGROUND;
import static android.app.AppOpsManager.MODE_IGNORED;
import static android.app.AppOpsManager.OP_CAMERA;
+import static android.app.AppOpsManager.OP_CAMERA_SANDBOXED;
import static android.app.AppOpsManager.OP_FLAGS_ALL;
import static android.app.AppOpsManager.OP_FLAG_SELF;
import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
@@ -42,6 +43,7 @@
import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
import static android.app.AppOpsManager.OP_RECORD_AUDIO;
import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
+import static android.app.AppOpsManager.OP_RECORD_AUDIO_SANDBOXED;
import static android.app.AppOpsManager.OP_VIBRATE;
import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED;
import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED;
@@ -3027,17 +3029,29 @@
packageName);
}
- // As a special case for OP_RECORD_AUDIO_HOTWORD, which we use only for attribution
- // purposes and not as a check, also make sure that the caller is allowed to access
- // the data gated by OP_RECORD_AUDIO.
+ // As a special case for OP_RECORD_AUDIO_HOTWORD, OP_RECEIVE_AMBIENT_TRIGGER_AUDIO and
+ // OP_RECORD_AUDIO_SANDBOXED which we use only for attribution purposes and not as a check,
+ // also make sure that the caller is allowed to access the data gated by OP_RECORD_AUDIO.
//
// TODO: Revert this change before Android 12.
- if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO) {
- int result = checkOperation(OP_RECORD_AUDIO, uid, packageName);
+ int result = MODE_DEFAULT;
+ if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO
+ || code == OP_RECORD_AUDIO_SANDBOXED) {
+ result = checkOperation(OP_RECORD_AUDIO, uid, packageName);
+ // Check result
if (result != AppOpsManager.MODE_ALLOWED) {
return new SyncNotedAppOp(result, code, attributionTag, packageName);
}
}
+ // As a special case for OP_CAMERA_SANDBOXED.
+ if (code == OP_CAMERA_SANDBOXED) {
+ result = checkOperation(OP_CAMERA, uid, packageName);
+ // Check result
+ if (result != AppOpsManager.MODE_ALLOWED) {
+ return new SyncNotedAppOp(result, code, attributionTag, packageName);
+ }
+ }
+
return startOperationUnchecked(clientId, code, uid, packageName, attributionTag,
Process.INVALID_UID, null, null, OP_FLAG_SELF, startIfModeDefault,
shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags,
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index 7c8e6df..5127d26 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -19,6 +19,8 @@
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR_BASE;
import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT;
import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE;
@@ -519,6 +521,9 @@
try {
mStatusBarService.onBiometricHelp(sensorIdToModality(sensorId), message);
+ final int aAcquiredInfo = acquiredInfo == FINGERPRINT_ACQUIRED_VENDOR
+ ? (vendorCode + FINGERPRINT_ACQUIRED_VENDOR_BASE) : acquiredInfo;
+ mClientReceiver.onAcquired(aAcquiredInfo, message);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 128ef0b..6c26e2b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -413,6 +413,11 @@
Slog.e(TAG, "Remote exception in onAuthenticationAcquired()", e);
}
}
+
+ @Override
+ public void onAuthenticationHelp(int acquireInfo, CharSequence helpString) {
+ onAuthenticationAcquired(acquireInfo);
+ }
};
return biometricPrompt.authenticateForOperation(
diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index 43e346a..2d40661 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -323,7 +323,7 @@
*/
void notifyInstallerOfAppWhoseLocaleChanged(String appPackageName, int userId,
LocaleList locales) {
- String installingPackageName = getInstallingPackageName(appPackageName);
+ String installingPackageName = getInstallingPackageName(appPackageName, userId);
if (installingPackageName != null) {
Intent intent = createBaseIntent(Intent.ACTION_APPLICATION_LOCALE_CHANGED,
appPackageName, locales);
@@ -464,7 +464,7 @@
* Checks if the calling app is the installer of the app whose locale changed.
*/
private boolean isCallerInstaller(String appPackageName, int userId) {
- String installingPackageName = getInstallingPackageName(appPackageName);
+ String installingPackageName = getInstallingPackageName(appPackageName, userId);
if (installingPackageName != null) {
// Get the uid of installer-on-record to compare with the calling uid.
int installerUid = getPackageUid(installingPackageName, userId);
@@ -513,10 +513,11 @@
}
@Nullable
- String getInstallingPackageName(String packageName) {
+ String getInstallingPackageName(String packageName, int userId) {
try {
- return mContext.getPackageManager()
- .getInstallSourceInfo(packageName).getInstallingPackageName();
+ return mContext.createContextAsUser(UserHandle.of(userId), /* flags= */
+ 0).getPackageManager().getInstallSourceInfo(
+ packageName).getInstallingPackageName();
} catch (PackageManager.NameNotFoundException e) {
Slog.w(TAG, "Package not found " + packageName);
}
diff --git a/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java b/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java
index 215c653..373d355 100644
--- a/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java
+++ b/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java
@@ -152,9 +152,10 @@
void onPackageUpdateFinished(String packageName, int uid) {
try {
if ((!mUpdatedApps.contains(packageName)) && isUpdatedSystemApp(packageName)) {
+ int userId = UserHandle.getUserId(uid);
// If a system app is updated, verify that it has an installer-on-record.
String installingPackageName = mLocaleManagerService.getInstallingPackageName(
- packageName);
+ packageName, userId);
if (installingPackageName == null) {
// We want to broadcast the locales info to the installer.
// If this app does not have an installer then do nothing.
@@ -162,7 +163,6 @@
}
try {
- int userId = UserHandle.getUserId(uid);
// Fetch the app-specific locales.
// If non-empty then send the info to the installer.
LocaleList appLocales = mLocaleManagerService.getApplicationLocales(
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index f0e8ede..94d5aab 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -357,12 +357,16 @@
} catch (NameNotFoundException e) {
throw new IllegalArgumentException("No package matching :" + packageName);
}
-
- projection = new MediaProjection(type, uid, packageName, ai.targetSdkVersion,
- ai.isPrivilegedApp());
- if (isPermanentGrant) {
- mAppOps.setMode(AppOpsManager.OP_PROJECT_MEDIA,
- projection.uid, projection.packageName, AppOpsManager.MODE_ALLOWED);
+ final long callingToken = Binder.clearCallingIdentity();
+ try {
+ projection = new MediaProjection(type, uid, packageName, ai.targetSdkVersion,
+ ai.isPrivilegedApp());
+ if (isPermanentGrant) {
+ mAppOps.setMode(AppOpsManager.OP_PROJECT_MEDIA,
+ projection.uid, projection.packageName, AppOpsManager.MODE_ALLOWED);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(callingToken);
}
return projection;
}
@@ -418,16 +422,9 @@
if (packageName == null || packageName.isEmpty()) {
throw new IllegalArgumentException("package name must not be empty");
}
- MediaProjection projection;
final UserHandle callingUser = Binder.getCallingUserHandle();
- final long callingToken = Binder.clearCallingIdentity();
- try {
- projection = createProjectionInternal(uid, packageName, type, isPermanentGrant,
- callingUser, false);
- } finally {
- Binder.restoreCallingIdentity(callingToken);
- }
- return projection;
+ return createProjectionInternal(uid, packageName, type, isPermanentGrant,
+ callingUser, false);
}
@Override // Binder call
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 6d27fe0..5d81dda 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -3815,6 +3815,28 @@
}
@Override
+ public boolean canUseFullScreenIntent(@NonNull AttributionSource attributionSource) {
+ final String packageName = attributionSource.getPackageName();
+ final int uid = attributionSource.getUid();
+ final int userId = UserHandle.getUserId(uid);
+ checkCallerIsSameApp(packageName, uid, userId);
+
+ final ApplicationInfo applicationInfo;
+ try {
+ applicationInfo = mPackageManagerClient.getApplicationInfoAsUser(
+ packageName, PackageManager.MATCH_DIRECT_BOOT_AUTO, userId);
+ } catch (NameNotFoundException e) {
+ Slog.e(TAG, "Failed to getApplicationInfo() in canUseFullScreenIntent()", e);
+ return false;
+ }
+ final boolean showStickyHunIfDenied = mFlagResolver.isEnabled(
+ SystemUiSystemPropertiesFlags.NotificationFlags
+ .SHOW_STICKY_HUN_FOR_DENIED_FSI);
+ return checkUseFullScreenIntentPermission(attributionSource, applicationInfo,
+ showStickyHunIfDenied /* isAppOpPermission */, false /* forDataDelivery */);
+ }
+
+ @Override
public void updateNotificationChannelGroupForPackage(String pkg, int uid,
NotificationChannelGroup group) throws RemoteException {
enforceSystemOrSystemUI("Caller not system or systemui");
@@ -6826,36 +6848,28 @@
notification.flags &= ~FLAG_FSI_REQUESTED_BUT_DENIED;
- if (notification.fullScreenIntent != null && ai.targetSdkVersion >= Build.VERSION_CODES.Q) {
+ if (notification.fullScreenIntent != null) {
final boolean forceDemoteFsiToStickyHun = mFlagResolver.isEnabled(
SystemUiSystemPropertiesFlags.NotificationFlags.FSI_FORCE_DEMOTE);
-
- final boolean showStickyHunIfDenied = mFlagResolver.isEnabled(
- SystemUiSystemPropertiesFlags.NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI);
-
if (forceDemoteFsiToStickyHun) {
makeStickyHun(notification, pkg, userId);
-
- } else if (showStickyHunIfDenied) {
- final AttributionSource source = new AttributionSource.Builder(notificationUid)
- .setPackageName(pkg)
- .build();
-
- final int permissionResult = mPermissionManager.checkPermissionForDataDelivery(
- Manifest.permission.USE_FULL_SCREEN_INTENT, source, /* message= */ null);
-
- if (permissionResult != PermissionManager.PERMISSION_GRANTED) {
- makeStickyHun(notification, pkg, userId);
- }
-
} else {
- int fullscreenIntentPermission = getContext().checkPermission(
- android.Manifest.permission.USE_FULL_SCREEN_INTENT, -1, notificationUid);
-
- if (fullscreenIntentPermission != PERMISSION_GRANTED) {
- notification.fullScreenIntent = null;
- Slog.w(TAG, "Package " + pkg + ": Use of fullScreenIntent requires the"
- + "USE_FULL_SCREEN_INTENT permission");
+ final AttributionSource attributionSource =
+ new AttributionSource.Builder(notificationUid).setPackageName(pkg).build();
+ final boolean showStickyHunIfDenied = mFlagResolver.isEnabled(
+ SystemUiSystemPropertiesFlags.NotificationFlags
+ .SHOW_STICKY_HUN_FOR_DENIED_FSI);
+ final boolean canUseFullScreenIntent = checkUseFullScreenIntentPermission(
+ attributionSource, ai, showStickyHunIfDenied /* isAppOpPermission */,
+ true /* forDataDelivery */);
+ if (!canUseFullScreenIntent) {
+ if (showStickyHunIfDenied) {
+ makeStickyHun(notification, pkg, userId);
+ } else {
+ notification.fullScreenIntent = null;
+ Slog.w(TAG, "Package " + pkg + ": Use of fullScreenIntent requires the"
+ + "USE_FULL_SCREEN_INTENT permission");
+ }
}
}
}
@@ -6951,6 +6965,30 @@
ai.packageName) == AppOpsManager.MODE_ALLOWED;
}
+ private boolean checkUseFullScreenIntentPermission(@NonNull AttributionSource attributionSource,
+ @NonNull ApplicationInfo applicationInfo, boolean isAppOpPermission,
+ boolean forDataDelivery) {
+ if (applicationInfo.targetSdkVersion < Build.VERSION_CODES.Q) {
+ return true;
+ }
+ if (isAppOpPermission) {
+ final int permissionResult;
+ if (forDataDelivery) {
+ permissionResult = mPermissionManager.checkPermissionForDataDelivery(
+ permission.USE_FULL_SCREEN_INTENT, attributionSource, /* message= */ null);
+ } else {
+ permissionResult = mPermissionManager.checkPermissionForPreflight(
+ permission.USE_FULL_SCREEN_INTENT, attributionSource);
+ }
+ return permissionResult == PermissionManager.PERMISSION_GRANTED;
+ } else {
+ final int permissionResult = getContext().checkPermission(
+ permission.USE_FULL_SCREEN_INTENT, attributionSource.getPid(),
+ attributionSource.getUid());
+ return permissionResult == PERMISSION_GRANTED;
+ }
+ }
+
private void checkRemoteViews(String pkg, String tag, int id, Notification notification) {
if (removeRemoteView(pkg, tag, id, notification.contentView)) {
notification.contentView = null;
diff --git a/services/core/java/com/android/server/notification/ZenLog.java b/services/core/java/com/android/server/notification/ZenLog.java
index 35b94e7..88d23ce 100644
--- a/services/core/java/com/android/server/notification/ZenLog.java
+++ b/services/core/java/com/android/server/notification/ZenLog.java
@@ -27,6 +27,7 @@
import android.service.notification.IConditionProvider;
import android.service.notification.NotificationListenerService;
import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeDiff;
import android.util.Log;
import android.util.Slog;
@@ -146,13 +147,13 @@
public static void traceConfig(String reason, ZenModeConfig oldConfig,
ZenModeConfig newConfig) {
- ZenModeConfig.Diff diff = ZenModeConfig.diff(oldConfig, newConfig);
- if (diff.isEmpty()) {
+ ZenModeDiff.ConfigDiff diff = new ZenModeDiff.ConfigDiff(oldConfig, newConfig);
+ if (diff == null || !diff.hasDiff()) {
append(TYPE_CONFIG, reason + " no changes");
} else {
append(TYPE_CONFIG, reason
+ ",\n" + (newConfig != null ? newConfig.toString() : null)
- + ",\n" + ZenModeConfig.diff(oldConfig, newConfig));
+ + ",\n" + diff);
}
}
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index ad77ef7..9127a93 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -187,6 +187,9 @@
if (changed) {
changedPackagesList.add(packageName);
changedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
+ } else {
+ Slog.w(TAG, "No change is needed for package: " + packageName
+ + ". Skipping suspending/un-suspending.");
}
}
diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java
index 401eac6..7a5664f 100644
--- a/services/core/java/com/android/server/policy/AppOpsPolicy.java
+++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java
@@ -316,6 +316,7 @@
private int resolveDatasourceOp(int code, int uid, @NonNull String packageName,
@Nullable String attributionTag) {
code = resolveRecordAudioOp(code, uid);
+ code = resolveSandboxedServiceOp(code, uid);
if (attributionTag == null) {
return code;
}
@@ -439,6 +440,28 @@
return code;
}
+ private int resolveSandboxedServiceOp(int code, int uid) {
+ if (!Process.isIsolated(uid) // simple check which fails-fast for the common case
+ || !(code == AppOpsManager.OP_RECORD_AUDIO || code == AppOpsManager.OP_CAMERA)) {
+ return code;
+ }
+ final HotwordDetectionServiceIdentity hotwordDetectionServiceIdentity =
+ mVoiceInteractionManagerInternal.getHotwordDetectionServiceIdentity();
+ if (hotwordDetectionServiceIdentity != null
+ && uid == hotwordDetectionServiceIdentity.getIsolatedUid()) {
+ // Upgrade the op such that no indicators is shown for camera or audio service. This
+ // will bypass the permission checking for the original OP_RECORD_AUDIO and OP_CAMERA.
+ switch (code) {
+ case AppOpsManager.OP_RECORD_AUDIO:
+ return AppOpsManager.OP_RECORD_AUDIO_SANDBOXED;
+ case AppOpsManager.OP_CAMERA:
+ return AppOpsManager.OP_CAMERA_SANDBOXED;
+ }
+ }
+ return code;
+ }
+
+
private int resolveUid(int code, int uid) {
// The HotwordDetectionService is an isolated service, which ordinarily cannot hold
// permissions. So we allow it to assume the owning package identity for certain
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index b1b0c55..4a03628 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -3569,6 +3569,7 @@
private void dumpWallpaper(WallpaperData wallpaper, PrintWriter pw) {
if (wallpaper == null) {
pw.println(" (null entry)");
+ return;
}
pw.print(" User "); pw.print(wallpaper.userId);
pw.print(": id="); pw.print(wallpaper.wallpaperId);
diff --git a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
index 83804f7..32f7b96 100644
--- a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
+++ b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
@@ -466,8 +466,7 @@
}
boolean isAnimatingByRecents(@NonNull Task task) {
- return task.isAnimatingByRecents()
- || mService.mAtmService.getTransitionController().inRecentsTransition(task);
+ return task.isAnimatingByRecents();
}
void dump(PrintWriter pw, String prefix) {
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index fa3a186..f300113 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -25,6 +25,8 @@
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
+import static com.android.internal.util.DumpUtils.dumpSparseArray;
+import static com.android.internal.util.DumpUtils.dumpSparseArrayValues;
import static com.android.server.accessibility.AccessibilityTraceFileProto.ENTRY;
import static com.android.server.accessibility.AccessibilityTraceFileProto.MAGIC_NUMBER;
import static com.android.server.accessibility.AccessibilityTraceFileProto.MAGIC_NUMBER_H;
@@ -542,15 +544,12 @@
}
void dump(PrintWriter pw, String prefix) {
- for (int i = 0; i < mDisplayMagnifiers.size(); i++) {
- final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.valueAt(i);
- if (displayMagnifier != null) {
- displayMagnifier.dump(pw, prefix
- + "Magnification display# " + mDisplayMagnifiers.keyAt(i));
- }
- }
- pw.println(prefix
- + "mWindowsForAccessibilityObserver=" + mWindowsForAccessibilityObserver);
+ dumpSparseArray(pw, prefix, mDisplayMagnifiers, "magnification display",
+ (index, key) -> pw.printf("%sDisplay #%d:", prefix + " ", key),
+ dm -> dm.dump(pw, ""));
+ dumpSparseArrayValues(pw, prefix, mWindowsForAccessibilityObserver,
+ "windows for accessibility observer");
+ mAccessibilityWindowsPopulator.dump(pw, prefix);
}
void onFocusChanged(InputTarget lastTarget, InputTarget newTarget) {
diff --git a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
index 21b241a..70f2007 100644
--- a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
+++ b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
@@ -16,6 +16,9 @@
package com.android.server.wm;
+import static com.android.internal.util.DumpUtils.KeyDumper;
+import static com.android.internal.util.DumpUtils.ValueDumper;
+import static com.android.internal.util.DumpUtils.dumpSparseArray;
import static com.android.server.wm.utils.RegionUtils.forEachRect;
import android.annotation.NonNull;
@@ -40,6 +43,7 @@
import com.android.internal.annotations.GuardedBy;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -562,6 +566,35 @@
notifyWindowsChanged(displayIdsForWindowsChanged);
}
+ void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix); pw.println("AccessibilityWindowsPopulator");
+ String prefix2 = prefix + " ";
+
+ pw.print(prefix2); pw.print("mWindowsNotificationEnabled: ");
+ pw.println(mWindowsNotificationEnabled);
+
+ if (mVisibleWindows.isEmpty()) {
+ pw.print(prefix2); pw.println("No visible windows");
+ } else {
+ pw.print(prefix2); pw.print(mVisibleWindows.size());
+ pw.print(" visible windows: "); pw.println(mVisibleWindows);
+ }
+ KeyDumper noKeyDumper = (i, k) -> {}; // display id is already shown on value;
+ KeyDumper displayDumper = (i, d) -> pw.printf("%sDisplay #%d: ", prefix, d);
+ // Ideally magnificationSpecDumper should use spec.dump(pw), but there is no such method
+ ValueDumper<MagnificationSpec> magnificationSpecDumper = spec -> pw.print(spec);
+
+ dumpSparseArray(pw, prefix2, mDisplayInfos, "display info", noKeyDumper, d -> pw.print(d));
+ dumpSparseArray(pw, prefix2, mInputWindowHandlesOnDisplays, "window handles on display",
+ displayDumper, list -> pw.print(list));
+ dumpSparseArray(pw, prefix2, mMagnificationSpecInverseMatrix, "magnification spec matrix",
+ noKeyDumper, matrix -> matrix.dump(pw));
+ dumpSparseArray(pw, prefix2, mCurrentMagnificationSpec, "current magnification spec",
+ noKeyDumper, magnificationSpecDumper);
+ dumpSparseArray(pw, prefix2, mPreviousMagnificationSpec, "previous magnification spec",
+ noKeyDumper, magnificationSpecDumper);
+ }
+
@GuardedBy("mLock")
private void releaseResources() {
mInputWindowHandlesOnDisplays.clear();
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index ff1c28a..6214440 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -78,8 +78,6 @@
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManagerInternal;
-import android.content.pm.ParceledListSlice;
-import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.os.Binder;
import android.os.Bundle;
@@ -1645,18 +1643,15 @@
launchedFromHome = root.isLaunchSourceType(ActivityRecord.LAUNCH_SOURCE_TYPE_HOME);
}
- // If the activity is one of the main entry points for the application, then we should
+ // If the activity was launched directly from the home screen, then we should
// refrain from finishing the activity and instead move it to the back to keep it in
// memory. The requirements for this are:
// 1. The activity is the last running activity in the task.
// 2. The current activity is the base activity for the task.
- // 3. a. If the activity was launched by the home process, we trust that its intent
- // was resolved, so we check if the it is a main intent for the application.
- // b. Otherwise, we query Package Manager to verify whether the activity is a
- // launcher activity for the application.
+ // 3. The activity was launched by the home process, and is one of the main entry
+ // points for the application.
if (baseActivityIntent != null && isLastRunningActivity
- && ((launchedFromHome && ActivityRecord.isMainIntent(baseActivityIntent))
- || isLauncherActivity(baseActivityIntent.getComponent()))) {
+ && launchedFromHome && ActivityRecord.isMainIntent(baseActivityIntent)) {
moveActivityTaskToBack(token, true /* nonRoot */);
return;
}
@@ -1668,31 +1663,6 @@
}
}
- /**
- * Queries PackageManager to see if the given activity is one of the main entry point for the
- * application. This should not be called with the WM lock held.
- */
- @SuppressWarnings("unchecked")
- private boolean isLauncherActivity(@NonNull ComponentName activity) {
- final Intent queryIntent = new Intent(Intent.ACTION_MAIN);
- queryIntent.addCategory(Intent.CATEGORY_LAUNCHER);
- queryIntent.setPackage(activity.getPackageName());
- try {
- final ParceledListSlice<ResolveInfo> resolved =
- mService.getPackageManager().queryIntentActivities(
- queryIntent, null, 0, mContext.getUserId());
- if (resolved == null) return false;
- for (final ResolveInfo ri : resolved.getList()) {
- if (ri.getComponentInfo().getComponentName().equals(activity)) {
- return true;
- }
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to query intent activities", e);
- }
- return false;
- }
-
@Override
public void enableTaskLocaleOverride(IBinder token) {
if (UserHandle.getAppId(Binder.getCallingUid()) != SYSTEM_UID) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index fb1f899..1bcc05e 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3544,8 +3544,7 @@
// the best capture timing (e.g. IME window capture),
// No need additional task capture while task is controlled by RecentsAnimation.
if (mAtmService.mWindowManager.mTaskSnapshotController != null
- && !(task.isAnimatingByRecents()
- || mTransitionController.inRecentsTransition(task))) {
+ && !task.isAnimatingByRecents()) {
final ArraySet<Task> tasks = Sets.newArraySet(task);
mAtmService.mWindowManager.mTaskSnapshotController.snapshotTasks(tasks);
mAtmService.mWindowManager.mTaskSnapshotController
@@ -10535,11 +10534,6 @@
@Override
boolean isSyncFinished() {
- if (task != null && mTransitionController.isTransientHide(task)) {
- // The activity keeps visibleRequested but may be hidden later, so no need to wait for
- // it to be drawn.
- return true;
- }
if (!super.isSyncFinished()) return false;
if (mDisplayContent != null && mDisplayContent.mUnknownAppVisibilityController
.isVisibilityUnknown(this)) {
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 7c1e907..bfe2986 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -577,7 +577,6 @@
final Transition transition = controller.getCollectingTransition();
if (transition != null) {
transition.setRemoteAnimationApp(r.app.getThread());
- controller.collect(task);
controller.setTransientLaunch(r, TaskDisplayArea.getRootTaskAbove(rootTask));
}
task.moveToFront("startExistingRecents");
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 38f13ec..c5e75fa 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2548,6 +2548,7 @@
mAvoidMoveToFront = false;
mFrozeTaskList = false;
mTransientLaunch = false;
+ mPriorAboveTask = null;
mDisplayLockAndOccluded = false;
mVoiceSession = null;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 12fe6a0..8123c07 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -831,7 +831,7 @@
private final Runnable mUpdateOomAdjRunnable = new Runnable() {
@Override
public void run() {
- mAmInternal.updateOomAdj();
+ mAmInternal.updateOomAdj(ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY);
}
};
diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
index a49ecce..d916a1b 100644
--- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -23,6 +23,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.os.Handler;
import android.os.Trace;
import android.util.ArraySet;
import android.util.Slog;
@@ -172,7 +173,7 @@
if (ran) {
return;
}
- mWm.mH.removeCallbacks(this);
+ mHandler.removeCallbacks(this);
ran = true;
SurfaceControl.Transaction t = new SurfaceControl.Transaction();
for (WindowContainer wc : wcAwaitingCommit) {
@@ -199,13 +200,13 @@
};
CommitCallback callback = new CommitCallback();
merged.addTransactionCommittedListener((r) -> { r.run(); }, callback::onCommitted);
- mWm.mH.postDelayed(callback, BLAST_TIMEOUT_DURATION);
+ mHandler.postDelayed(callback, BLAST_TIMEOUT_DURATION);
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "onTransactionReady");
mListener.onTransactionReady(mSyncId, merged);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
mActiveSyncs.remove(mSyncId);
- mWm.mH.removeCallbacks(mOnTimeout);
+ mHandler.removeCallbacks(mOnTimeout);
// Immediately start the next pending sync-transaction if there is one.
if (mActiveSyncs.size() == 0 && !mPendingSyncSets.isEmpty()) {
@@ -216,7 +217,7 @@
throw new IllegalStateException("Pending Sync Set didn't start a sync.");
}
// Post this so that the now-playing transition setup isn't interrupted.
- mWm.mH.post(() -> {
+ mHandler.post(() -> {
synchronized (mWm.mGlobalLock) {
pt.mApplySync.run();
}
@@ -269,6 +270,7 @@
}
private final WindowManagerService mWm;
+ private final Handler mHandler;
private int mNextSyncId = 0;
private final SparseArray<SyncGroup> mActiveSyncs = new SparseArray<>();
@@ -280,7 +282,13 @@
private final ArrayList<PendingSyncSet> mPendingSyncSets = new ArrayList<>();
BLASTSyncEngine(WindowManagerService wms) {
+ this(wms, wms.mH);
+ }
+
+ @VisibleForTesting
+ BLASTSyncEngine(WindowManagerService wms, Handler mainHandler) {
mWm = wms;
+ mHandler = mainHandler;
}
/**
@@ -325,7 +333,7 @@
@VisibleForTesting
void scheduleTimeout(SyncGroup s, long timeoutMs) {
- mWm.mH.postDelayed(s.mOnTimeout, timeoutMs);
+ mHandler.postDelayed(s.mOnTimeout, timeoutMs);
}
void addToSyncSet(int id, WindowContainer wc) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 73f4b5b..fb592e1 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3006,7 +3006,8 @@
/** Checking if self or its child tasks are animated by recents animation. */
boolean isAnimatingByRecents() {
- return isAnimating(CHILDREN, ANIMATION_TYPE_RECENTS);
+ return isAnimating(CHILDREN, ANIMATION_TYPE_RECENTS)
+ || mTransitionController.isTransientHide(this);
}
WindowState getTopVisibleAppMainWindow() {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index e209ef9..3a909ce 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -109,7 +109,7 @@
*/
class Transition implements BLASTSyncEngine.TransactionReadyListener {
private static final String TAG = "Transition";
- private static final String TRACE_NAME_PLAY_TRANSITION = "PlayTransition";
+ private static final String TRACE_NAME_PLAY_TRANSITION = "playing";
/** The default package for resources */
private static final String DEFAULT_PACKAGE = "android";
@@ -511,8 +511,10 @@
if (mParticipants.contains(wc)) return;
// Wallpaper is like in a static drawn state unless display may have changes, so exclude
// the case to reduce transition latency waiting for the unchanged wallpaper to redraw.
- final boolean needSyncDraw = !isWallpaper(wc) || mParticipants.contains(wc.mDisplayContent);
- if (needSyncDraw) {
+ final boolean needSync = (!isWallpaper(wc) || mParticipants.contains(wc.mDisplayContent))
+ // Transient-hide may be hidden later, so no need to request redraw.
+ && !isInTransientHide(wc);
+ if (needSync) {
mSyncEngine.addToSyncSet(mSyncId, wc);
}
ChangeInfo info = mChanges.get(wc);
@@ -881,8 +883,7 @@
*/
void finishTransition() {
if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER) && mIsPlayerEnabled) {
- Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_PLAY_TRANSITION,
- System.identityHashCode(this));
+ asyncTraceEnd(System.identityHashCode(this));
}
mLogger.mFinishTimeNs = SystemClock.elapsedRealtimeNanos();
mController.mLoggerHandler.post(mLogger::logOnFinish);
@@ -918,6 +919,8 @@
final WindowContainer<?> participant = mParticipants.valueAt(i);
final ActivityRecord ar = participant.asActivityRecord();
if (ar != null) {
+ final Task task = ar.getTask();
+ if (task == null) continue;
boolean visibleAtTransitionEnd = mVisibleAtTransitionEndTokens.contains(ar);
// We need both the expected visibility AND current requested-visibility to be
// false. If it is expected-visible but not currently visible, it means that
@@ -936,9 +939,7 @@
if (commitVisibility) {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
" Commit activity becoming invisible: %s", ar);
- final Task task = ar.getTask();
- if (task != null && !task.isVisibleRequested()
- && mTransientLaunches != null) {
+ if (mTransientLaunches != null && !task.isVisibleRequested()) {
// If transition is transient, then snapshots are taken at end of
// transition.
mController.mSnapshotController.mTaskSnapshotController
@@ -963,8 +964,9 @@
// Since transient launches don't automatically take focus, make sure we
// synchronize focus since we committed to the launch.
- if (ar.isTopRunningActivity()) {
- ar.moveFocusableActivityToTop("transitionFinished");
+ if (!task.isFocused() && ar.isTopRunningActivity()) {
+ mController.mAtm.setLastResumedActivityUncheckLocked(ar,
+ "transitionFinished");
}
}
continue;
@@ -1332,8 +1334,7 @@
mController.getTransitionPlayer().onTransitionReady(
mToken, info, transaction, mFinishTransaction);
if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
- Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_PLAY_TRANSITION,
- System.identityHashCode(this));
+ asyncTraceBegin(TRACE_NAME_PLAY_TRANSITION, System.identityHashCode(this));
}
} catch (RemoteException e) {
// If there's an exception when trying to send the mergedTransaction to the
@@ -2320,6 +2321,14 @@
return isCollecting() && mSyncId >= 0;
}
+ static void asyncTraceBegin(@NonNull String name, int cookie) {
+ Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_WINDOW_MANAGER, TAG, name, cookie);
+ }
+
+ static void asyncTraceEnd(int cookie) {
+ Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_WINDOW_MANAGER, TAG, cookie);
+ }
+
@VisibleForTesting
static class ChangeInfo {
private static final int FLAG_NONE = 0;
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 8d56607..3bf8969 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -37,7 +37,6 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemProperties;
-import android.os.Trace;
import android.util.ArrayMap;
import android.util.Slog;
import android.util.TimeUtils;
@@ -334,28 +333,6 @@
return inCollectingTransition(wc) || inPlayingTransition(wc);
}
- boolean inRecentsTransition(@NonNull WindowContainer wc) {
- for (WindowContainer p = wc; p != null; p = p.getParent()) {
- // TODO(b/221417431): replace this with deterministic snapshots
- if (mCollectingTransition == null) break;
- if ((mCollectingTransition.getFlags() & TRANSIT_FLAG_IS_RECENTS) != 0
- && mCollectingTransition.mParticipants.contains(wc)) {
- return true;
- }
- }
-
- for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
- for (WindowContainer p = wc; p != null; p = p.getParent()) {
- // TODO(b/221417431): replace this with deterministic snapshots
- if ((mPlayingTransitions.get(i).getFlags() & TRANSIT_FLAG_IS_RECENTS) != 0
- && mPlayingTransitions.get(i).mParticipants.contains(p)) {
- return true;
- }
- }
- }
- return false;
- }
-
/** @return {@code true} if wc is in a participant subtree */
boolean isTransitionOnDisplay(@NonNull DisplayContent dc) {
if (mCollectingTransition != null && mCollectingTransition.isOnDisplay(dc)) {
@@ -773,12 +750,12 @@
// happening in app), so pause task snapshot persisting to not increase the load.
mAtm.mWindowManager.mSnapshotController.setPause(true);
mAnimatingState = true;
- Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "transitAnim", 0);
+ Transition.asyncTraceBegin("animating", 0x41bfaf1 /* hashcode of TAG */);
} else if (!animatingState && mAnimatingState) {
t.setEarlyWakeupEnd();
mAtm.mWindowManager.mSnapshotController.setPause(false);
mAnimatingState = false;
- Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER, "transitAnim", 0);
+ Transition.asyncTraceEnd(0x41bfaf1 /* hashcode of TAG */);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index cd4d6e4..8fecf11 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -6697,9 +6697,8 @@
mInputManagerCallback.dump(pw, " ");
mSnapshotController.dump(pw, " ");
- if (mAccessibilityController.hasCallbacks()) {
- mAccessibilityController.dump(pw, " ");
- }
+
+ dumpAccessibilityController(pw, /* force= */ false);
if (dumpAll) {
final WindowState imeWindow = mRoot.getCurrentInputMethodWindow();
@@ -6736,6 +6735,23 @@
}
}
+ private void dumpAccessibilityController(PrintWriter pw, boolean force) {
+ boolean hasCallbacks = mAccessibilityController.hasCallbacks();
+ if (!hasCallbacks && !force) {
+ return;
+ }
+ if (!hasCallbacks) {
+ pw.println("AccessibilityController doesn't have callbacks, but printing it anways:");
+ } else {
+ pw.println("AccessibilityController:");
+ }
+ mAccessibilityController.dump(pw, " ");
+ }
+
+ private void dumpAccessibilityLocked(PrintWriter pw) {
+ dumpAccessibilityController(pw, /* force= */ true);
+ }
+
private boolean dumpWindows(PrintWriter pw, String name, boolean dumpAll) {
final ArrayList<WindowState> windows = new ArrayList();
if ("apps".equals(name) || "visible".equals(name) || "visible-apps".equals(name)) {
@@ -6855,6 +6871,7 @@
pw.println(" d[isplays]: active display contents");
pw.println(" t[okens]: token list");
pw.println(" w[indows]: window list");
+ pw.println(" a11y[accessibility]: accessibility-related state");
pw.println(" package-config: installed packages having app-specific config");
pw.println(" trace: print trace status and write Winscope trace to file");
pw.println(" cmd may also be a NAME to dump windows. NAME may");
@@ -6918,6 +6935,11 @@
dumpWindowsLocked(pw, true, null);
}
return;
+ } else if ("accessibility".equals(cmd) || "a11y".equals(cmd)) {
+ synchronized (mGlobalLock) {
+ dumpAccessibilityLocked(pw);
+ }
+ return;
} else if ("all".equals(cmd)) {
synchronized (mGlobalLock) {
dumpWindowsLocked(pw, true, null);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 6e3924b..e5a49c3 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5647,7 +5647,7 @@
@Override
boolean isSyncFinished() {
- if (!isVisibleRequested()) {
+ if (!isVisibleRequested() || isFullyTransparent()) {
// Don't wait for invisible windows. However, we don't alter the state in case the
// window becomes visible while the sync group is still active.
return true;
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index aeb4801..208a4be 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -161,7 +161,8 @@
@Override
public void onProviderStatusChanged(ProviderSession.Status status,
ComponentName componentName, ProviderSession.CredentialsSource source) {
- Slog.d(TAG, "in onStatusChanged with status: " + status + ", and source: " + source);
+ Slog.d(TAG, "in onStatusChanged for: " + componentName + ", with status: "
+ + status + ", and source: " + source);
// Auth entry was selected, and it did not have any underlying credentials
if (status == ProviderSession.Status.NO_CREDENTIALS_FROM_AUTH_ENTRY) {
diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
index 0c3d3f4..9ec0ecd 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
@@ -26,7 +26,6 @@
import android.os.ICancellationSignal;
import android.service.credentials.CallingAppInfo;
import android.service.credentials.ClearCredentialStateRequest;
-import android.util.Log;
import android.util.Slog;
/**
@@ -81,7 +80,7 @@
@Override
public void onProviderResponseSuccess(@Nullable Void response) {
- Log.i(TAG, "in onProviderResponseSuccess");
+ Slog.d(TAG, "Remote provider responded with a valid response: " + mComponentName);
mProviderResponseSet = true;
updateStatusAndInvokeCallback(Status.COMPLETE,
/*source=*/ CredentialsSource.REMOTE_PROVIDER);
@@ -105,7 +104,7 @@
updateStatusAndInvokeCallback(Status.SERVICE_DEAD,
/*source=*/ CredentialsSource.REMOTE_PROVIDER);
} else {
- Slog.i(TAG, "Component names different in onProviderServiceDied - "
+ Slog.w(TAG, "Component names different in onProviderServiceDied - "
+ "this should not happen");
}
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index 8b9255a..09433db 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -37,7 +37,6 @@
import android.service.credentials.CreateEntry;
import android.service.credentials.CredentialProviderService;
import android.service.credentials.RemoteEntry;
-import android.util.Log;
import android.util.Pair;
import android.util.Slog;
@@ -93,7 +92,8 @@
createRequestSession.mHybridService
);
}
- Log.i(TAG, "Unable to create provider session");
+ Slog.d(TAG, "Unable to create provider session for: "
+ + providerInfo.getComponentName());
return null;
}
@@ -122,7 +122,6 @@
return new CreateCredentialRequest(callingAppInfo, capability,
clientRequest.getCredentialData());
}
- Log.i(TAG, "Unable to create provider request - capabilities do not match");
return null;
}
@@ -146,7 +145,7 @@
@Override
public void onProviderResponseSuccess(
@Nullable BeginCreateCredentialResponse response) {
- Log.i(TAG, "in onProviderResponseSuccess");
+ Slog.d(TAG, "Remote provider responded with a valid response: " + mComponentName);
onSetInitialRemoteResponse(response);
}
@@ -169,7 +168,7 @@
updateStatusAndInvokeCallback(Status.SERVICE_DEAD,
/*source=*/ CredentialsSource.REMOTE_PROVIDER);
} else {
- Slog.i(TAG, "Component names different in onProviderServiceDied - "
+ Slog.w(TAG, "Component names different in onProviderServiceDied - "
+ "this should not happen");
}
}
@@ -180,7 +179,6 @@
}
private void onSetInitialRemoteResponse(BeginCreateCredentialResponse response) {
- Log.i(TAG, "onSetInitialRemoteResponse with save entries");
mProviderResponse = response;
mProviderResponseDataHandler.addResponseContent(response.getCreateEntries(),
response.getRemoteCreateEntry());
@@ -199,14 +197,12 @@
@Nullable
protected CreateCredentialProviderData prepareUiData()
throws IllegalArgumentException {
- Log.i(TAG, "In prepareUiData");
if (!ProviderSession.isUiInvokingStatus(getStatus())) {
- Log.i(TAG, "In prepareUiData not in uiInvokingStatus");
+ Slog.d(TAG, "No data for UI from: " + mComponentName.flattenToString());
return null;
}
if (mProviderResponse != null && !mProviderResponseDataHandler.isEmptyResponse()) {
- Log.i(TAG, "In prepareUiData save entries not null");
return mProviderResponseDataHandler.toCreateCredentialProviderData();
}
return null;
@@ -218,7 +214,7 @@
switch (entryType) {
case SAVE_ENTRY_KEY:
if (mProviderResponseDataHandler.getCreateEntry(entryKey) == null) {
- Log.i(TAG, "Unexpected save entry key");
+ Slog.w(TAG, "Unexpected save entry key");
invokeCallbackOnInternalInvalidState();
return;
}
@@ -226,14 +222,14 @@
break;
case REMOTE_ENTRY_KEY:
if (mProviderResponseDataHandler.getRemoteEntry(entryKey) == null) {
- Log.i(TAG, "Unexpected remote entry key");
+ Slog.w(TAG, "Unexpected remote entry key");
invokeCallbackOnInternalInvalidState();
return;
}
onRemoteEntrySelected(providerPendingIntentResponse);
break;
default:
- Log.i(TAG, "Unsupported entry type selected");
+ Slog.w(TAG, "Unsupported entry type selected");
invokeCallbackOnInternalInvalidState();
}
}
@@ -268,7 +264,7 @@
if (credentialResponse != null) {
mCallbacks.onFinalResponseReceived(mComponentName, credentialResponse);
} else {
- Log.i(TAG, "onSaveEntrySelected - no response or error found in pending "
+ Slog.w(TAG, "onSaveEntrySelected - no response or error found in pending "
+ "intent response");
invokeCallbackOnInternalInvalidState();
}
@@ -284,14 +280,14 @@
private CreateCredentialException maybeGetPendingIntentException(
ProviderPendingIntentResponse pendingIntentResponse) {
if (pendingIntentResponse == null) {
- Log.i(TAG, "pendingIntentResponse is null");
+ Slog.w(TAG, "pendingIntentResponse is null");
return new CreateCredentialException(CreateCredentialException.TYPE_NO_CREATE_OPTIONS);
}
if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) {
CreateCredentialException exception = PendingIntentResultHandler
.extractCreateCredentialException(pendingIntentResponse.getResultData());
if (exception != null) {
- Log.i(TAG, "Pending intent contains provider exception");
+ Slog.d(TAG, "Pending intent contains provider exception");
return exception;
}
} else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) {
@@ -343,7 +339,7 @@
public void setRemoteEntry(@Nullable RemoteEntry remoteEntry) {
if (!enforceRemoteEntryRestrictions(mExpectedRemoteEntryProviderService)) {
- Log.i(TAG, "Remote entry being dropped as it does not meet the restriction"
+ Slog.w(TAG, "Remote entry being dropped as it does not meet the restriction"
+ "checks.");
return;
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index 8d3d064..0c2b563 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -115,7 +115,8 @@
getRequestSession.mHybridService
);
}
- Log.i(TAG, "Unable to create provider session");
+ Slog.d(TAG, "Unable to create provider session for: "
+ + providerInfo.getComponentName());
return null;
}
@@ -146,17 +147,15 @@
android.credentials.GetCredentialRequest clientRequest,
CredentialProviderInfo info
) {
+ Slog.d(TAG, "Filtering request options for: " + info.getComponentName());
List<CredentialOption> filteredOptions = new ArrayList<>();
for (CredentialOption option : clientRequest.getCredentialOptions()) {
if (providerCapabilities.contains(option.getType())
&& isProviderAllowed(option, info.getComponentName())
&& checkSystemProviderRequirement(option, info.isSystemProvider())) {
- Log.i(TAG, "In createProviderRequest - capability found : "
- + option.getType());
+ Slog.d(TAG, "Option of type: " + option.getType() + " meets all filtering"
+ + "conditions");
filteredOptions.add(option);
- } else {
- Log.i(TAG, "In createProviderRequest - capability not "
- + "found, or provider not allowed : " + option.getType());
}
}
if (!filteredOptions.isEmpty()) {
@@ -165,15 +164,14 @@
.setCredentialOptions(
filteredOptions).build();
}
- Log.i(TAG, "In createProviderRequest - returning null");
+ Slog.d(TAG, "No options filtered");
return null;
}
private static boolean isProviderAllowed(CredentialOption option, ComponentName componentName) {
if (!option.getAllowedProviders().isEmpty() && !option.getAllowedProviders().contains(
componentName)) {
- Log.d(TAG, "Provider allow list specified but does not contain this provider: "
- + componentName.flattenToString());
+ Slog.d(TAG, "Provider allow list specified but does not contain this provider");
return false;
}
return true;
@@ -182,7 +180,7 @@
private static boolean checkSystemProviderRequirement(CredentialOption option,
boolean isSystemProvider) {
if (option.isSystemProviderRequired() && !isSystemProvider) {
- Log.d(TAG, "System provider required, but this service is not a system provider");
+ Slog.d(TAG, "System provider required, but this service is not a system provider");
return false;
}
return true;
@@ -210,6 +208,7 @@
/** Called when the provider response has been updated by an external source. */
@Override // Callback from the remote provider
public void onProviderResponseSuccess(@Nullable BeginGetCredentialResponse response) {
+ Slog.d(TAG, "Remote provider responded with a valid response: " + mComponentName);
onSetInitialRemoteResponse(response);
}
@@ -231,7 +230,7 @@
updateStatusAndInvokeCallback(Status.SERVICE_DEAD,
/*source=*/ CredentialsSource.REMOTE_PROVIDER);
} else {
- Slog.i(TAG, "Component names different in onProviderServiceDied - "
+ Slog.w(TAG, "Component names different in onProviderServiceDied - "
+ "this should not happen");
}
}
@@ -244,13 +243,14 @@
@Override // Selection call from the request provider
protected void onUiEntrySelected(String entryType, String entryKey,
ProviderPendingIntentResponse providerPendingIntentResponse) {
- Log.i(TAG, "onUiEntrySelected with entryKey: " + entryKey);
+ Slog.d(TAG, "onUiEntrySelected with entryType: " + entryType + ", and entryKey: "
+ + entryKey);
switch (entryType) {
case CREDENTIAL_ENTRY_KEY:
CredentialEntry credentialEntry = mProviderResponseDataHandler
.getCredentialEntry(entryKey);
if (credentialEntry == null) {
- Log.i(TAG, "Unexpected credential entry key");
+ Slog.w(TAG, "Unexpected credential entry key");
invokeCallbackOnInternalInvalidState();
return;
}
@@ -259,7 +259,7 @@
case ACTION_ENTRY_KEY:
Action actionEntry = mProviderResponseDataHandler.getActionEntry(entryKey);
if (actionEntry == null) {
- Log.i(TAG, "Unexpected action entry key");
+ Slog.w(TAG, "Unexpected action entry key");
invokeCallbackOnInternalInvalidState();
return;
}
@@ -269,21 +269,21 @@
Action authenticationEntry = mProviderResponseDataHandler
.getAuthenticationAction(entryKey);
if (authenticationEntry == null) {
- Log.i(TAG, "Unexpected authenticationEntry key");
+ Slog.w(TAG, "Unexpected authenticationEntry key");
invokeCallbackOnInternalInvalidState();
return;
}
boolean additionalContentReceived =
onAuthenticationEntrySelected(providerPendingIntentResponse);
if (additionalContentReceived) {
- Log.i(TAG, "Additional content received - removing authentication entry");
+ Slog.d(TAG, "Additional content received - removing authentication entry");
mProviderResponseDataHandler.removeAuthenticationAction(entryKey);
if (!mProviderResponseDataHandler.isEmptyResponse()) {
updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED,
/*source=*/ CredentialsSource.AUTH_ENTRY);
}
} else {
- Log.i(TAG, "Additional content not received");
+ Slog.d(TAG, "Additional content not received from authentication entry");
mProviderResponseDataHandler
.updateAuthEntryWithNoCredentialsReceived(entryKey);
updateStatusAndInvokeCallback(Status.NO_CREDENTIALS_FROM_AUTH_ENTRY,
@@ -294,12 +294,12 @@
if (mProviderResponseDataHandler.getRemoteEntry(entryKey) != null) {
onRemoteEntrySelected(providerPendingIntentResponse);
} else {
- Log.i(TAG, "Unexpected remote entry key");
+ Slog.d(TAG, "Unexpected remote entry key");
invokeCallbackOnInternalInvalidState();
}
break;
default:
- Log.i(TAG, "Unsupported entry type selected");
+ Slog.w(TAG, "Unsupported entry type selected");
invokeCallbackOnInternalInvalidState();
}
}
@@ -320,26 +320,24 @@
@Override // Call from request session to data to be shown on the UI
@Nullable
protected GetCredentialProviderData prepareUiData() throws IllegalArgumentException {
- Log.i(TAG, "In prepareUiData");
if (!ProviderSession.isUiInvokingStatus(getStatus())) {
- Log.i(TAG, "In prepareUiData - provider does not want to show UI: "
- + mComponentName.flattenToString());
+ Slog.d(TAG, "No data for UI from: " + mComponentName.flattenToString());
return null;
}
if (mProviderResponse != null && !mProviderResponseDataHandler.isEmptyResponse()) {
return mProviderResponseDataHandler.toGetCredentialProviderData();
}
- Log.i(TAG, "In prepareUiData response null");
+ Slog.d(TAG, "In prepareUiData response null");
return null;
}
- private Intent setUpFillInIntent(@NonNull String id) {
+ private Intent setUpFillInIntentWithFinalRequest(@NonNull String id) {
// TODO: Determine if we should skip this entry if entry id is not set, or is set
// but does not resolve to a valid option. For now, not skipping it because
// it may be possible that the provider adds their own extras and expects to receive
// those and complete the flow.
if (mBeginGetOptionToCredentialOptionMap.get(id) == null) {
- Log.i(TAG, "Id from Credential Entry does not resolve to a valid option");
+ Slog.w(TAG, "Id from Credential Entry does not resolve to a valid option");
return new Intent();
}
return new Intent().putExtra(CredentialProviderService.EXTRA_GET_CREDENTIAL_REQUEST,
@@ -382,7 +380,8 @@
getCredentialResponse);
return;
}
- Log.i(TAG, "Pending intent response contains no credential, or error");
+ Slog.d(TAG, "Pending intent response contains no credential, or error "
+ + "for a credential entry");
invokeCallbackOnInternalInvalidState();
}
@@ -390,14 +389,12 @@
private GetCredentialException maybeGetPendingIntentException(
ProviderPendingIntentResponse pendingIntentResponse) {
if (pendingIntentResponse == null) {
- Log.i(TAG, "pendingIntentResponse is null");
return null;
}
if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) {
GetCredentialException exception = PendingIntentResultHandler
.extractGetCredentialException(pendingIntentResponse.getResultData());
if (exception != null) {
- Log.i(TAG, "Pending intent contains provider exception");
return exception;
}
} else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) {
@@ -463,7 +460,7 @@
/** Returns true if either an exception or a response is found. */
private void onActionEntrySelected(ProviderPendingIntentResponse
providerPendingIntentResponse) {
- Log.i(TAG, "onActionEntrySelected");
+ Slog.d(TAG, "onActionEntrySelected");
onCredentialEntrySelected(providerPendingIntentResponse);
}
@@ -559,7 +556,8 @@
String id = generateUniqueId();
Entry entry = new Entry(CREDENTIAL_ENTRY_KEY,
id, credentialEntry.getSlice(),
- setUpFillInIntent(credentialEntry.getBeginGetCredentialOptionId()));
+ setUpFillInIntentWithFinalRequest(credentialEntry
+ .getBeginGetCredentialOptionId()));
mUiCredentialEntries.put(id, new Pair<>(credentialEntry, entry));
mCredentialEntryTypes.add(credentialEntry.getType());
}
@@ -574,9 +572,7 @@
public void addAuthenticationAction(Action authenticationAction,
@AuthenticationEntry.Status int status) {
- Log.i(TAG, "In addAuthenticationAction");
String id = generateUniqueId();
- Log.i(TAG, "In addAuthenticationAction, id : " + id);
AuthenticationEntry entry = new AuthenticationEntry(
AUTHENTICATION_ACTION_ENTRY_KEY,
id, authenticationAction.getSlice(),
@@ -591,7 +587,7 @@
public void setRemoteEntry(@Nullable RemoteEntry remoteEntry) {
if (!enforceRemoteEntryRestrictions(mExpectedRemoteEntryProviderService)) {
- Log.i(TAG, "Remote entry being dropped as it does not meet the restriction"
+ Slog.w(TAG, "Remote entry being dropped as it does not meet the restriction"
+ " checks.");
return;
}
@@ -715,7 +711,6 @@
== AuthenticationEntry.STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT)
.findFirst();
if (previousMostRecentAuthEntry.isEmpty()) {
- Log.i(TAG, "In updatePreviousMostRecentAuthEntry - previous entry not found");
return;
}
String id = previousMostRecentAuthEntry.get().getKey();
diff --git a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
index 24292ef2..c10f564 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
@@ -33,7 +33,7 @@
import android.service.credentials.CallingAppInfo;
import android.service.credentials.CredentialEntry;
import android.service.credentials.CredentialProviderService;
-import android.telecom.Log;
+import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
@@ -145,14 +145,12 @@
private List<Entry> prepareUiCredentialEntries(
@NonNull List<CredentialEntry> credentialEntries) {
- Log.i(TAG, "in prepareUiProviderDataWithCredentials");
List<Entry> credentialUiEntries = new ArrayList<>();
// Populate the credential entries
for (CredentialEntry credentialEntry : credentialEntries) {
String entryId = generateUniqueId();
mUiCredentialEntries.put(entryId, credentialEntry);
- Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId);
credentialUiEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId,
credentialEntry.getSlice(),
setUpFillInIntent()));
@@ -172,15 +170,13 @@
@Override
protected ProviderData prepareUiData() {
- Log.i(TAG, "In prepareUiData");
if (!ProviderSession.isUiInvokingStatus(getStatus())) {
- Log.i(TAG, "In prepareUiData - provider does not want to show UI: "
- + mComponentName.flattenToString());
+ Slog.d(TAG, "No date for UI coming from: " + mComponentName.flattenToString());
return null;
}
if (mProviderResponse == null) {
- Log.i(TAG, "In prepareUiData response null");
- throw new IllegalStateException("Response must be in completion mode");
+ Slog.w(TAG, "In prepareUiData but response is null. This is strange.");
+ return null;
}
return new GetCredentialProviderData.Builder(
mComponentName.flattenToString()).setActionChips(null)
@@ -200,13 +196,13 @@
case CREDENTIAL_ENTRY_KEY:
CredentialEntry credentialEntry = mUiCredentialEntries.get(entryKey);
if (credentialEntry == null) {
- Log.i(TAG, "Unexpected credential entry key");
+ Slog.w(TAG, "Unexpected credential entry key");
return;
}
onCredentialEntrySelected(credentialEntry, providerPendingIntentResponse);
break;
default:
- Log.i(TAG, "Unsupported entry type selected");
+ Slog.w(TAG, "Unsupported entry type selected");
}
}
@@ -233,10 +229,8 @@
}
return;
}
-
- Log.i(TAG, "Pending intent response contains no credential, or error");
}
- Log.i(TAG, "CredentialEntry does not have a credential or a pending intent result");
+ Slog.w(TAG, "CredentialEntry does not have a credential or a pending intent result");
}
@Override
@@ -279,14 +273,13 @@
protected GetCredentialException maybeGetPendingIntentException(
ProviderPendingIntentResponse pendingIntentResponse) {
if (pendingIntentResponse == null) {
- android.util.Log.i(TAG, "pendingIntentResponse is null");
return null;
}
if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) {
GetCredentialException exception = PendingIntentResultHandler
.extractGetCredentialException(pendingIntentResponse.getResultData());
if (exception != null) {
- android.util.Log.i(TAG, "Pending intent contains provider exception");
+ Slog.d(TAG, "Pending intent contains provider exception");
return exception;
}
} else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) {
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index d165756..d02a8c1 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -29,7 +29,6 @@
import android.credentials.ui.ProviderPendingIntentResponse;
import android.os.ICancellationSignal;
import android.os.RemoteException;
-import android.util.Log;
import android.util.Slog;
import com.android.server.credentials.metrics.ProviderSessionMetric;
@@ -253,7 +252,7 @@
@Nullable ComponentName expectedRemoteEntryProviderService) {
// Check if the service is the one set by the OEM. If not silently reject this entry
if (!mComponentName.equals(expectedRemoteEntryProviderService)) {
- Log.i(TAG, "Remote entry being dropped as it is not from the service "
+ Slog.w(TAG, "Remote entry being dropped as it is not from the service "
+ "configured by the OEM.");
return false;
}
@@ -270,15 +269,12 @@
return true;
}
} catch (SecurityException e) {
- Log.i(TAG, "Error getting info for "
- + mComponentName.flattenToString() + ": " + e.getMessage());
+ Slog.e(TAG, "Error getting info for " + mComponentName.flattenToString(), e);
return false;
} catch (PackageManager.NameNotFoundException e) {
- Log.i(TAG, "Error getting info for "
- + mComponentName.flattenToString() + ": " + e.getMessage());
+ Slog.i(TAG, "Error getting info for " + mComponentName.flattenToString(), e);
return false;
}
- Log.i(TAG, "In enforceRemoteEntryRestrictions - remote entry checks fail");
return false;
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 492d477..b1d6131 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -125,6 +125,7 @@
import com.android.server.connectivity.PacProxyService;
import com.android.server.contentcapture.ContentCaptureManagerInternal;
import com.android.server.coverage.CoverageService;
+import com.android.server.cpu.CpuMonitorService;
import com.android.server.devicepolicy.DevicePolicyManagerService;
import com.android.server.devicestate.DeviceStateManagerService;
import com.android.server.display.DisplayManagerService;
@@ -1405,6 +1406,15 @@
mSystemServiceManager.startService(RemoteProvisioningService.class);
t.traceEnd();
+ // TODO(b/277600174): Start CpuMonitorService on all builds and not just on debuggable
+ // builds once the Android JobScheduler starts using this service.
+ if (Build.IS_DEBUGGABLE || Build.IS_ENG) {
+ // Service for CPU monitor.
+ t.traceBegin("CpuMonitorService");
+ mSystemServiceManager.startService(CpuMonitorService.class);
+ t.traceEnd();
+ }
+
t.traceEnd(); // startCoreServices
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 8211d6f..ec177c9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -463,7 +463,8 @@
assertEquals(BroadcastProcessQueue.REASON_BLOCKED, queue.getRunnableAtReason());
// Bumping past barrier makes us now runnable
- airplaneRecord.terminalCount++;
+ airplaneRecord.setDeliveryState(0, BroadcastRecord.DELIVERY_DELIVERED,
+ "testRunnableAt_Ordered");
queue.invalidateRunnableAt();
assertTrue(queue.isRunnable());
assertNotEquals(BroadcastProcessQueue.REASON_BLOCKED, queue.getRunnableAtReason());
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index b6bc02a..d7ba3df 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -16,6 +16,8 @@
package com.android.server.am;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
import static android.os.UserHandle.USER_SYSTEM;
import static com.android.server.am.ActivityManagerDebugConfig.LOG_WRITER_INFO;
@@ -39,7 +41,6 @@
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -463,7 +464,7 @@
doAnswer((invocation) -> {
Log.v(TAG, "Intercepting scheduleReceiver() for "
- + Arrays.toString(invocation.getArguments()));
+ + Arrays.toString(invocation.getArguments()) + " package " + ai.packageName);
assertHealth();
final Intent intent = invocation.getArgument(0);
final Bundle extras = invocation.getArgument(5);
@@ -485,7 +486,7 @@
doAnswer((invocation) -> {
Log.v(TAG, "Intercepting scheduleRegisteredReceiver() for "
- + Arrays.toString(invocation.getArguments()));
+ + Arrays.toString(invocation.getArguments()) + " package " + ai.packageName);
assertHealth();
final Intent intent = invocation.getArgument(1);
final Bundle extras = invocation.getArgument(4);
@@ -961,7 +962,7 @@
} else {
// Confirm that app was thawed
verify(mAms.mOomAdjuster, atLeastOnce()).unfreezeTemporarily(
- eq(receiverApp), eq(OomAdjuster.OOM_ADJ_REASON_START_RECEIVER));
+ eq(receiverApp), eq(OOM_ADJ_REASON_START_RECEIVER));
// Confirm that we added package to process
verify(receiverApp, atLeastOnce()).addPackage(eq(receiverApp.info.packageName),
@@ -1404,7 +1405,7 @@
// Finally, verify that we thawed the final receiver
verify(mAms.mOomAdjuster).unfreezeTemporarily(eq(callerApp),
- eq(OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER));
+ eq(OOM_ADJ_REASON_FINISH_RECEIVER));
}
/**
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
index 2b6f217..08952ea 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
@@ -24,7 +24,12 @@
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_ALL;
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_BACKGROUND_RESTRICTED_ONLY;
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE;
-import static com.android.server.am.BroadcastRecord.calculateBlockedUntilTerminalCount;
+import static com.android.server.am.BroadcastRecord.DELIVERY_DEFERRED;
+import static com.android.server.am.BroadcastRecord.DELIVERY_DELIVERED;
+import static com.android.server.am.BroadcastRecord.DELIVERY_PENDING;
+import static com.android.server.am.BroadcastRecord.DELIVERY_SKIPPED;
+import static com.android.server.am.BroadcastRecord.DELIVERY_TIMEOUT;
+import static com.android.server.am.BroadcastRecord.calculateBlockedUntilBeyondCount;
import static com.android.server.am.BroadcastRecord.calculateDeferUntilActive;
import static com.android.server.am.BroadcastRecord.calculateUrgent;
import static com.android.server.am.BroadcastRecord.isReceiverEquals;
@@ -58,7 +63,6 @@
import com.android.server.am.BroadcastDispatcher.DeferredBootCompletedBroadcastPerUser;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -79,6 +83,7 @@
@SmallTest
@RunWith(MockitoJUnitRunner.class)
public class BroadcastRecordTest {
+ private static final String TAG = "BroadcastRecordTest";
private static final int USER0 = UserHandle.USER_SYSTEM;
private static final int USER1 = USER0 + 1;
@@ -120,13 +125,13 @@
assertFalse(isPrioritized(List.of(createResolveInfo(PACKAGE1, getAppId(1), 10))));
assertArrayEquals(new int[] {-1},
- calculateBlockedUntilTerminalCount(List.of(
+ calculateBlockedUntilBeyondCount(List.of(
createResolveInfo(PACKAGE1, getAppId(1), 0)), false));
assertArrayEquals(new int[] {-1},
- calculateBlockedUntilTerminalCount(List.of(
+ calculateBlockedUntilBeyondCount(List.of(
createResolveInfo(PACKAGE1, getAppId(1), -10)), false));
assertArrayEquals(new int[] {-1},
- calculateBlockedUntilTerminalCount(List.of(
+ calculateBlockedUntilBeyondCount(List.of(
createResolveInfo(PACKAGE1, getAppId(1), 10)), false));
}
@@ -142,12 +147,12 @@
createResolveInfo(PACKAGE3, getAppId(3), 10))));
assertArrayEquals(new int[] {-1,-1,-1},
- calculateBlockedUntilTerminalCount(List.of(
+ calculateBlockedUntilBeyondCount(List.of(
createResolveInfo(PACKAGE1, getAppId(1), 0),
createResolveInfo(PACKAGE2, getAppId(2), 0),
createResolveInfo(PACKAGE3, getAppId(3), 0)), false));
assertArrayEquals(new int[] {-1,-1,-1},
- calculateBlockedUntilTerminalCount(List.of(
+ calculateBlockedUntilBeyondCount(List.of(
createResolveInfo(PACKAGE1, getAppId(1), 10),
createResolveInfo(PACKAGE2, getAppId(2), 10),
createResolveInfo(PACKAGE3, getAppId(3), 10)), false));
@@ -156,26 +161,176 @@
@Test
public void testIsPrioritized_Yes() {
assertTrue(isPrioritized(List.of(
- createResolveInfo(PACKAGE1, getAppId(1), -10),
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
createResolveInfo(PACKAGE2, getAppId(2), 0),
- createResolveInfo(PACKAGE3, getAppId(3), 10))));
+ createResolveInfo(PACKAGE3, getAppId(3), -10))));
assertTrue(isPrioritized(List.of(
- createResolveInfo(PACKAGE1, getAppId(1), 0),
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
createResolveInfo(PACKAGE2, getAppId(2), 0),
- createResolveInfo(PACKAGE3, getAppId(3), 10))));
+ createResolveInfo(PACKAGE3, getAppId(3), 0))));
assertArrayEquals(new int[] {0,1,2},
- calculateBlockedUntilTerminalCount(List.of(
- createResolveInfo(PACKAGE1, getAppId(1), -10),
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
createResolveInfo(PACKAGE2, getAppId(2), 0),
- createResolveInfo(PACKAGE3, getAppId(3), 10)), false));
+ createResolveInfo(PACKAGE3, getAppId(3), -10)), false));
assertArrayEquals(new int[] {0,0,2,3,3},
- calculateBlockedUntilTerminalCount(List.of(
- createResolveInfo(PACKAGE1, getAppId(1), 0),
- createResolveInfo(PACKAGE2, getAppId(2), 0),
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 20),
+ createResolveInfo(PACKAGE2, getAppId(2), 20),
createResolveInfo(PACKAGE3, getAppId(3), 10),
- createResolveInfo(PACKAGE3, getAppId(3), 20),
- createResolveInfo(PACKAGE3, getAppId(3), 20)), false));
+ createResolveInfo(PACKAGE3, getAppId(3), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), 0)), false));
+ }
+
+ @Test
+ public void testSetDeliveryState_Single() {
+ final BroadcastRecord r = createBroadcastRecord(
+ new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of(
+ createResolveInfoWithPriority(0)));
+ assertEquals(DELIVERY_PENDING, r.getDeliveryState(0));
+ assertBlocked(r, false);
+ assertTerminalDeferredBeyond(r, 0, 0, 0);
+
+ r.setDeliveryState(0, DELIVERY_DEFERRED, TAG);
+ assertEquals(DELIVERY_DEFERRED, r.getDeliveryState(0));
+ assertBlocked(r, false);
+ assertTerminalDeferredBeyond(r, 0, 1, 1);
+
+ // Identical state change has no effect
+ r.setDeliveryState(0, DELIVERY_DEFERRED, TAG);
+ assertEquals(DELIVERY_DEFERRED, r.getDeliveryState(0));
+ assertBlocked(r, false);
+ assertTerminalDeferredBeyond(r, 0, 1, 1);
+
+ // Moving to terminal state updates counters
+ r.setDeliveryState(0, DELIVERY_DELIVERED, TAG);
+ assertEquals(DELIVERY_DELIVERED, r.getDeliveryState(0));
+ assertBlocked(r, false);
+ assertTerminalDeferredBeyond(r, 1, 0, 1);
+
+ // Trying to change terminal state has no effect
+ r.setDeliveryState(0, DELIVERY_TIMEOUT, TAG);
+ assertEquals(DELIVERY_DELIVERED, r.getDeliveryState(0));
+ assertBlocked(r, false);
+ assertTerminalDeferredBeyond(r, 1, 0, 1);
+ }
+
+ @Test
+ public void testSetDeliveryState_Unordered() {
+ final BroadcastRecord r = createBroadcastRecord(
+ new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of(
+ createResolveInfoWithPriority(0),
+ createResolveInfoWithPriority(0),
+ createResolveInfoWithPriority(0)));
+ assertBlocked(r, false, false, false);
+ assertTerminalDeferredBeyond(r, 0, 0, 0);
+
+ // Even though we finish a middle item in the tranche, we're not
+ // "beyond" it because there is still unfinished work before it
+ r.setDeliveryState(1, DELIVERY_DELIVERED, TAG);
+ assertBlocked(r, false, false, false);
+ assertTerminalDeferredBeyond(r, 1, 0, 0);
+
+ r.setDeliveryState(0, DELIVERY_DELIVERED, TAG);
+ assertBlocked(r, false, false, false);
+ assertTerminalDeferredBeyond(r, 2, 0, 2);
+
+ r.setDeliveryState(2, DELIVERY_DELIVERED, TAG);
+ assertBlocked(r, false, false, false);
+ assertTerminalDeferredBeyond(r, 3, 0, 3);
+ }
+
+ @Test
+ public void testSetDeliveryState_Ordered() {
+ final BroadcastRecord r = createOrderedBroadcastRecord(
+ new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of(
+ createResolveInfoWithPriority(0),
+ createResolveInfoWithPriority(0),
+ createResolveInfoWithPriority(0)));
+ assertBlocked(r, false, true, true);
+ assertTerminalDeferredBeyond(r, 0, 0, 0);
+
+ r.setDeliveryState(0, DELIVERY_DELIVERED, TAG);
+ assertBlocked(r, false, false, true);
+ assertTerminalDeferredBeyond(r, 1, 0, 1);
+
+ r.setDeliveryState(1, DELIVERY_DELIVERED, TAG);
+ assertBlocked(r, false, false, false);
+ assertTerminalDeferredBeyond(r, 2, 0, 2);
+
+ r.setDeliveryState(2, DELIVERY_DELIVERED, TAG);
+ assertBlocked(r, false, false, false);
+ assertTerminalDeferredBeyond(r, 3, 0, 3);
+ }
+
+ @Test
+ public void testSetDeliveryState_DeferUntilActive() {
+ final BroadcastRecord r = createBroadcastRecord(
+ new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of(
+ createResolveInfoWithPriority(10),
+ createResolveInfoWithPriority(10),
+ createResolveInfoWithPriority(10),
+ createResolveInfoWithPriority(0),
+ createResolveInfoWithPriority(0),
+ createResolveInfoWithPriority(0),
+ createResolveInfoWithPriority(-10),
+ createResolveInfoWithPriority(-10),
+ createResolveInfoWithPriority(-10)));
+ assertBlocked(r, false, false, false, true, true, true, true, true, true);
+ assertTerminalDeferredBeyond(r, 0, 0, 0);
+
+ r.setDeliveryState(0, DELIVERY_PENDING, TAG);
+ r.setDeliveryState(1, DELIVERY_DEFERRED, TAG);
+ r.setDeliveryState(2, DELIVERY_PENDING, TAG);
+ r.setDeliveryState(3, DELIVERY_DEFERRED, TAG);
+ r.setDeliveryState(4, DELIVERY_DEFERRED, TAG);
+ r.setDeliveryState(5, DELIVERY_DEFERRED, TAG);
+ r.setDeliveryState(6, DELIVERY_DEFERRED, TAG);
+ r.setDeliveryState(7, DELIVERY_PENDING, TAG);
+ r.setDeliveryState(8, DELIVERY_DEFERRED, TAG);
+
+ // Verify deferred counts ratchet up, but we're not "beyond" the first
+ // still-pending receiver
+ assertBlocked(r, false, false, false, true, true, true, true, true, true);
+ assertTerminalDeferredBeyond(r, 0, 6, 0);
+
+ // We're still not "beyond" the first still-pending receiver, even when
+ // we finish a receiver later in the first tranche
+ r.setDeliveryState(2, DELIVERY_DELIVERED, TAG);
+ assertBlocked(r, false, false, false, true, true, true, true, true, true);
+ assertTerminalDeferredBeyond(r, 1, 6, 0);
+
+ // Completing that last item in first tranche means we now unblock the
+ // second tranche, and since it's entirely deferred, the third traunche
+ // is unblocked too
+ r.setDeliveryState(0, DELIVERY_DELIVERED, TAG);
+ assertBlocked(r, false, false, false, false, false, false, false, false, false);
+ assertTerminalDeferredBeyond(r, 2, 6, 7);
+
+ // Moving a deferred item in an earlier tranche back to being pending
+ // doesn't change the fact that we've already moved beyond it
+ r.setDeliveryState(1, DELIVERY_PENDING, TAG);
+ assertBlocked(r, false, false, false, false, false, false, false, false, false);
+ assertTerminalDeferredBeyond(r, 2, 5, 7);
+ r.setDeliveryState(1, DELIVERY_DELIVERED, TAG);
+ assertBlocked(r, false, false, false, false, false, false, false, false, false);
+ assertTerminalDeferredBeyond(r, 3, 5, 7);
+
+ // Completing middle pending item is enough to fast-forward to end
+ r.setDeliveryState(7, DELIVERY_DELIVERED, TAG);
+ assertBlocked(r, false, false, false, false, false, false, false, false, false);
+ assertTerminalDeferredBeyond(r, 4, 5, 9);
+
+ // Moving everyone else directly into a finished state updates all the
+ // terminal counters
+ r.setDeliveryState(3, DELIVERY_SKIPPED, TAG);
+ r.setDeliveryState(4, DELIVERY_SKIPPED, TAG);
+ r.setDeliveryState(5, DELIVERY_SKIPPED, TAG);
+ r.setDeliveryState(6, DELIVERY_SKIPPED, TAG);
+ r.setDeliveryState(8, DELIVERY_SKIPPED, TAG);
+ assertBlocked(r, false, false, false, false, false, false, false, false, false);
+ assertTerminalDeferredBeyond(r, 9, 0, 9);
}
@Test
@@ -688,6 +843,10 @@
: errorMsg.insert(0, "Contains unexpected receiver: ").toString();
}
+ private static ResolveInfo createResolveInfoWithPriority(int priority) {
+ return createResolveInfo(PACKAGE1, getAppId(1), priority);
+ }
+
private static ResolveInfo createResolveInfo(String packageName, int uid) {
return createResolveInfo(packageName, uid, 0);
}
@@ -738,21 +897,40 @@
return excludedList;
}
+ private BroadcastRecord createBroadcastRecord(Intent intent,
+ List<ResolveInfo> receivers) {
+ return createBroadcastRecord(receivers, USER0, intent, null /* filterExtrasForReceiver */,
+ null /* options */, false);
+ }
+
+ private BroadcastRecord createOrderedBroadcastRecord(Intent intent,
+ List<ResolveInfo> receivers) {
+ return createBroadcastRecord(receivers, USER0, intent, null /* filterExtrasForReceiver */,
+ null /* options */, true);
+ }
+
private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId,
Intent intent) {
return createBroadcastRecord(receivers, userId, intent, null /* filterExtrasForReceiver */,
- null /* options */);
+ null /* options */, false);
}
private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId,
Intent intent, BroadcastOptions options) {
return createBroadcastRecord(receivers, userId, intent, null /* filterExtrasForReceiver */,
- options);
+ options, false);
}
private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId,
Intent intent, BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
BroadcastOptions options) {
+ return createBroadcastRecord(receivers, userId, intent, filterExtrasForReceiver,
+ options, false);
+ }
+
+ private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId,
+ Intent intent, BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
+ BroadcastOptions options, boolean ordered) {
return new BroadcastRecord(
mQueue /* queue */,
intent,
@@ -774,7 +952,7 @@
0 /* resultCode */,
null /* resultData */,
null /* resultExtras */,
- false /* serialized */,
+ ordered /* serialized */,
false /* sticky */,
false /* initialSticky */,
userId,
@@ -789,6 +967,20 @@
private static boolean isPrioritized(List<Object> receivers) {
return BroadcastRecord.isPrioritized(
- calculateBlockedUntilTerminalCount(receivers, false), false);
+ calculateBlockedUntilBeyondCount(receivers, false), false);
+ }
+
+ private static void assertBlocked(BroadcastRecord r, boolean... blocked) {
+ assertEquals(r.receivers.size(), blocked.length);
+ for (int i = 0; i < blocked.length; i++) {
+ assertEquals("blocked " + i, blocked[i], r.isBlocked(i));
+ }
+ }
+
+ private static void assertTerminalDeferredBeyond(BroadcastRecord r,
+ int expectedTerminalCount, int expectedDeferredCount, int expectedBeyondCount) {
+ assertEquals("terminal", expectedTerminalCount, r.terminalCount);
+ assertEquals("deferred", expectedDeferredCount, r.deferredCount);
+ assertEquals("beyond", expectedBeyondCount, r.beyondCount);
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 485ce33..cda5456 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -38,11 +38,12 @@
import static android.app.ActivityManager.PROCESS_STATE_TOP;
import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_NONE;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_ACTIVITY;
import static com.android.server.am.ProcessList.BACKUP_APP_ADJ;
import static com.android.server.am.ProcessList.CACHED_APP_MAX_ADJ;
import static com.android.server.am.ProcessList.CACHED_APP_MIN_ADJ;
@@ -254,12 +255,13 @@
* - If there's only one process, then it calls updateOomAdjLocked(ProcessRecord, int).
* - Otherwise, sets the processes to the LRU and run updateOomAdjLocked(int).
*/
+ @SuppressWarnings("GuardedBy")
private void updateOomAdj(ProcessRecord... apps) {
if (apps.length == 1) {
- sService.mOomAdjuster.updateOomAdjLocked(apps[0], OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(apps[0], OOM_ADJ_REASON_NONE);
} else {
setProcessesToLru(apps);
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
sService.mProcessList.getLruProcessesLOSP().clear();
}
}
@@ -658,7 +660,7 @@
ServiceRecord s = bindService(app, system,
null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class));
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND,
PERCEPTIBLE_APP_ADJ + 1, SCHED_GROUP_DEFAULT);
@@ -1226,7 +1228,7 @@
mock(IBinder.class));
client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
assertEquals(PERCEPTIBLE_APP_ADJ + 1, app.mState.getSetAdj());
}
@@ -1243,7 +1245,7 @@
mock(IBinder.class));
client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
doReturn(false).when(wpc).isHeavyWeightProcess();
assertEquals(PERCEPTIBLE_APP_ADJ + 1, app.mState.getSetAdj());
@@ -1497,7 +1499,7 @@
client2.mServices.setHasForegroundServices(false, 0, /* hasNoneType=*/false);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(client2, OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(client2, OOM_ADJ_REASON_NONE);
assertEquals(PROCESS_STATE_CACHED_EMPTY, client2.mState.getSetProcState());
assertEquals(PROCESS_STATE_CACHED_EMPTY, client.mState.getSetProcState());
@@ -1919,7 +1921,7 @@
doReturn(client2).when(sService).getTopApp();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(app2, OOM_ADJ_REASON_NONE);
assertProcStates(app2, PROCESS_STATE_BOUND_TOP, VISIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
}
@@ -2029,7 +2031,7 @@
setServiceMap(s3, MOCKAPP5_UID, cn3);
setServiceMap(c2s, MOCKAPP3_UID, cn4);
app2UidRecord.setIdle(false);
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -2055,7 +2057,7 @@
anyInt(), anyBoolean(), anyBoolean(), anyBoolean());
doNothing().when(sService.mServices)
.scheduleServiceTimeoutLocked(any(ProcessRecord.class));
- sService.mOomAdjuster.updateOomAdjLocked(client1, OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(client1, OOM_ADJ_REASON_NONE);
assertEquals(PROCESS_STATE_CACHED_EMPTY, client1.mState.getSetProcState());
assertEquals(PROCESS_STATE_SERVICE, app1.mState.getSetProcState());
@@ -2427,7 +2429,7 @@
app2.mState.setHasShownUi(false);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-ui-services");
assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj2, "cch-started-services");
@@ -2436,7 +2438,7 @@
app.mState.setAdjType(null);
app.mState.setSetAdj(UNKNOWN_ADJ);
app.mState.setHasShownUi(false);
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
@@ -2445,7 +2447,7 @@
app.mState.setAdjType(null);
app.mState.setSetAdj(UNKNOWN_ADJ);
s.lastActivity = now - sService.mConstants.MAX_SERVICE_INACTIVITY - 1;
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
@@ -2463,7 +2465,7 @@
s.lastActivity = now;
app.mServices.startService(s);
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
@@ -2474,7 +2476,7 @@
app.mState.setSetAdj(UNKNOWN_ADJ);
app.mState.setHasShownUi(false);
s.lastActivity = now - sService.mConstants.MAX_SERVICE_INACTIVITY - 1;
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
@@ -2482,7 +2484,7 @@
doReturn(userOther).when(sService.mUserController).getCurrentUserId();
sService.mOomAdjuster.handleUserSwitchedLocked();
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
assertProcStates(app2, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssAntennaInfoProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssAntennaInfoProviderTest.java
new file mode 100644
index 0000000..e1fa8f52
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssAntennaInfoProviderTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.gnss;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.location.LocationManager;
+import android.location.LocationManagerInternal;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+import com.android.server.location.gnss.hal.FakeGnssHal;
+import com.android.server.location.gnss.hal.GnssNative;
+import com.android.server.location.injector.Injector;
+import com.android.server.location.injector.TestInjector;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Objects;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class GnssAntennaInfoProviderTest {
+ private @Mock Context mContext;
+ private @Mock LocationManagerInternal mInternal;
+ private @Mock GnssConfiguration mMockConfiguration;
+ private @Mock IBinder mBinder;
+ private GnssNative mGnssNative;
+
+ private GnssAntennaInfoProvider mTestProvider;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ doReturn(true).when(mInternal).isProviderEnabledForUser(eq(LocationManager.GPS_PROVIDER),
+ anyInt());
+ LocalServices.addService(LocationManagerInternal.class, mInternal);
+ FakeGnssHal fakeGnssHal = new FakeGnssHal();
+ GnssNative.setGnssHalForTest(fakeGnssHal);
+ Injector injector = new TestInjector(mContext);
+ mGnssNative = spy(Objects.requireNonNull(GnssNative.create(injector, mMockConfiguration)));
+ mTestProvider = new GnssAntennaInfoProvider(mGnssNative);
+ mGnssNative.register();
+ }
+
+ @After
+ public void tearDown() {
+ LocalServices.removeServiceForTest(LocationManagerInternal.class);
+ }
+
+ @Test
+ public void testOnHalStarted() {
+ verify(mGnssNative, times(1)).startAntennaInfoListening();
+ }
+
+ @Test
+ public void testOnHalRestarted() {
+ mTestProvider.onHalRestarted();
+ verify(mGnssNative, times(2)).startAntennaInfoListening();
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java
index fd9dfe8..bf96b1d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java
@@ -74,7 +74,6 @@
private @Mock Context mContext;
private @Mock LocationManagerInternal mInternal;
private @Mock GnssConfiguration mMockConfiguration;
- private @Mock GnssNative.GeofenceCallbacks mGeofenceCallbacks;
private @Mock IGnssMeasurementsListener mListener1;
private @Mock IGnssMeasurementsListener mListener2;
private @Mock IBinder mBinder1;
@@ -98,7 +97,6 @@
Injector injector = new TestInjector(mContext);
mGnssNative = spy(Objects.requireNonNull(
GnssNative.create(injector, mMockConfiguration)));
- mGnssNative.setGeofenceCallbacks(mGeofenceCallbacks);
mTestProvider = new GnssMeasurementsProvider(injector, mGnssNative);
mGnssNative.register();
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNavigationMessageProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNavigationMessageProviderTest.java
new file mode 100644
index 0000000..64aa4b3
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNavigationMessageProviderTest.java
@@ -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.server.location.gnss;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.location.IGnssNavigationMessageListener;
+import android.location.LocationManager;
+import android.location.LocationManagerInternal;
+import android.location.util.identity.CallerIdentity;
+import android.os.IBinder;
+
+import com.android.server.LocalServices;
+import com.android.server.location.gnss.hal.FakeGnssHal;
+import com.android.server.location.gnss.hal.GnssNative;
+import com.android.server.location.injector.FakeUserInfoHelper;
+import com.android.server.location.injector.Injector;
+import com.android.server.location.injector.TestInjector;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Objects;
+
+public class GnssNavigationMessageProviderTest {
+ private static final int CURRENT_USER = FakeUserInfoHelper.DEFAULT_USERID;
+ private static final CallerIdentity IDENTITY = CallerIdentity.forTest(CURRENT_USER, 1000,
+ "mypackage", "attribution", "listener");
+ private @Mock Context mContext;
+ private @Mock LocationManagerInternal mInternal;
+ private @Mock GnssConfiguration mMockConfiguration;
+ private @Mock IGnssNavigationMessageListener mListener;
+ private @Mock IBinder mBinder;
+
+ private GnssNative mGnssNative;
+
+ private GnssNavigationMessageProvider mTestProvider;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ doReturn(mBinder).when(mListener).asBinder();
+ doReturn(true).when(mInternal).isProviderEnabledForUser(eq(LocationManager.GPS_PROVIDER),
+ anyInt());
+ LocalServices.addService(LocationManagerInternal.class, mInternal);
+ FakeGnssHal fakeGnssHal = new FakeGnssHal();
+ GnssNative.setGnssHalForTest(fakeGnssHal);
+ Injector injector = new TestInjector(mContext);
+ mGnssNative = spy(Objects.requireNonNull(GnssNative.create(injector, mMockConfiguration)));
+ mTestProvider = new GnssNavigationMessageProvider(injector, mGnssNative);
+ mGnssNative.register();
+ }
+
+ @After
+ public void tearDown() {
+ LocalServices.removeServiceForTest(LocationManagerInternal.class);
+ }
+
+ @Test
+ public void testAddListener() {
+ // add a request
+ mTestProvider.addListener(IDENTITY, mListener);
+ verify(mGnssNative, times(1)).startNavigationMessageCollection();
+
+ // remove a request
+ mTestProvider.removeListener(mListener);
+ verify(mGnssNative, times(1)).stopNavigationMessageCollection();
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNmeaProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNmeaProviderTest.java
new file mode 100644
index 0000000..49e5e69
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNmeaProviderTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.gnss;
+
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.location.IGnssNmeaListener;
+import android.location.LocationManager;
+import android.location.LocationManagerInternal;
+import android.location.util.identity.CallerIdentity;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.LocalServices;
+import com.android.server.location.gnss.hal.FakeGnssHal;
+import com.android.server.location.gnss.hal.GnssNative;
+import com.android.server.location.injector.FakeUserInfoHelper;
+import com.android.server.location.injector.Injector;
+import com.android.server.location.injector.TestInjector;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Objects;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class GnssNmeaProviderTest {
+
+ private static final int CURRENT_USER = FakeUserInfoHelper.DEFAULT_USERID;
+ private static final CallerIdentity IDENTITY = CallerIdentity.forTest(CURRENT_USER, 1000,
+ "mypackage", "attribution", "listener");
+ private @Mock Context mContext;
+ private @Mock LocationManagerInternal mInternal;
+ private @Mock GnssConfiguration mMockConfiguration;
+ private @Mock IGnssNmeaListener mListener;
+ private @Mock IBinder mBinder;
+
+ private GnssNative mGnssNative;
+
+ private GnssNmeaProvider mTestProvider;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ doReturn(mBinder).when(mListener).asBinder();
+ doReturn(true).when(mInternal).isProviderEnabledForUser(eq(LocationManager.GPS_PROVIDER),
+ anyInt());
+ LocalServices.addService(LocationManagerInternal.class, mInternal);
+ FakeGnssHal fakeGnssHal = new FakeGnssHal();
+ GnssNative.setGnssHalForTest(fakeGnssHal);
+ Injector injector = new TestInjector(mContext);
+ mGnssNative = spy(Objects.requireNonNull(GnssNative.create(injector, mMockConfiguration)));
+ mTestProvider = new GnssNmeaProvider(injector, mGnssNative);
+ mGnssNative.register();
+ }
+
+ @After
+ public void tearDown() {
+ LocalServices.removeServiceForTest(LocationManagerInternal.class);
+ }
+
+ @Test
+ public void testAddListener() {
+ // add a request
+ mTestProvider.addListener(IDENTITY, mListener);
+ verify(mGnssNative, times(1)).startNmeaMessageCollection();
+
+ // remove a request
+ mTestProvider.removeListener(mListener);
+ verify(mGnssNative, times(1)).stopNmeaMessageCollection();
+ }
+
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssStatusProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssStatusProviderTest.java
new file mode 100644
index 0000000..ce2aec7f
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssStatusProviderTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.gnss;
+
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.location.IGnssStatusListener;
+import android.location.LocationManager;
+import android.location.LocationManagerInternal;
+import android.location.util.identity.CallerIdentity;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.LocalServices;
+import com.android.server.location.gnss.hal.FakeGnssHal;
+import com.android.server.location.gnss.hal.GnssNative;
+import com.android.server.location.injector.FakeUserInfoHelper;
+import com.android.server.location.injector.Injector;
+import com.android.server.location.injector.TestInjector;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Objects;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class GnssStatusProviderTest {
+ private static final int CURRENT_USER = FakeUserInfoHelper.DEFAULT_USERID;
+ private static final CallerIdentity IDENTITY = CallerIdentity.forTest(CURRENT_USER, 1000,
+ "mypackage", "attribution", "listener");
+ private @Mock Context mContext;
+ private @Mock LocationManagerInternal mInternal;
+ private @Mock GnssConfiguration mMockConfiguration;
+ private @Mock IGnssStatusListener mListener;
+ private @Mock IBinder mBinder;
+
+ private GnssNative mGnssNative;
+
+ private GnssStatusProvider mTestProvider;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ doReturn(mBinder).when(mListener).asBinder();
+ doReturn(true).when(mInternal).isProviderEnabledForUser(eq(LocationManager.GPS_PROVIDER),
+ anyInt());
+ LocalServices.addService(LocationManagerInternal.class, mInternal);
+ FakeGnssHal fakeGnssHal = new FakeGnssHal();
+ GnssNative.setGnssHalForTest(fakeGnssHal);
+ Injector injector = new TestInjector(mContext);
+ mGnssNative = spy(Objects.requireNonNull(GnssNative.create(injector, mMockConfiguration)));
+ mTestProvider = new GnssStatusProvider(injector, mGnssNative);
+ mGnssNative.register();
+ }
+
+ @After
+ public void tearDown() {
+ LocalServices.removeServiceForTest(LocationManagerInternal.class);
+ }
+
+ @Test
+ public void testAddListener() {
+ // add a request
+ mTestProvider.addListener(IDENTITY, mListener);
+ verify(mGnssNative, times(1)).startSvStatusCollection();
+
+ // remove a request
+ mTestProvider.removeListener(mListener);
+ verify(mGnssNative, times(1)).stopSvStatusCollection();
+ }
+
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java
index b7ab6f80..2d962ac 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java
@@ -562,6 +562,26 @@
}
@Override
+ protected boolean startSvStatusCollection() {
+ return true;
+ }
+
+ @Override
+ protected boolean stopSvStatusCollection() {
+ return true;
+ }
+
+ @Override
+ public boolean startNmeaMessageCollection() {
+ return true;
+ }
+
+ @Override
+ public boolean stopNmeaMessageCollection() {
+ return true;
+ }
+
+ @Override
protected int getBatchSize() {
return mBatchSize;
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index dbf5021..26a3ae1 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -43,6 +43,7 @@
import android.app.admin.DevicePolicyManager;
import android.app.trust.ITrustManager;
import android.content.Context;
+import android.content.res.Resources;
import android.hardware.biometrics.BiometricManager.Authenticators;
import android.hardware.biometrics.IBiometricAuthenticator;
import android.hardware.biometrics.IBiometricSensorReceiver;
@@ -52,6 +53,7 @@
import android.hardware.biometrics.SensorProperties;
import android.hardware.face.FaceSensorProperties;
import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Binder;
@@ -83,6 +85,7 @@
private static final long TEST_REQUEST_ID = 22;
@Mock private Context mContext;
+ @Mock private Resources mResources;
@Mock private BiometricContext mBiometricContext;
@Mock private ITrustManager mTrustManager;
@Mock private DevicePolicyManager mDevicePolicyManager;
@@ -104,6 +107,7 @@
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ when(mContext.getResources()).thenReturn(mResources);
when(mClientReceiver.asBinder()).thenReturn(mock(Binder.class));
when(mBiometricContext.updateContext(any(), anyBoolean()))
.thenAnswer(invocation -> invocation.getArgument(0));
@@ -342,6 +346,33 @@
testInvokesCancel(session -> session.onDialogDismissed(DISMISSED_REASON_NEGATIVE, null));
}
+ @Test
+ public void testCallbackOnAcquired() throws RemoteException {
+ final String acquiredStr = "test_acquired_info_callback";
+ final String acquiredStrVendor = "test_acquired_info_callback_vendor";
+ setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_REAR);
+
+ final AuthSession session = createAuthSession(mSensors,
+ false /* checkDevicePolicyManager */,
+ Authenticators.BIOMETRIC_STRONG,
+ TEST_REQUEST_ID,
+ 0 /* operationId */,
+ 0 /* userId */);
+
+ when(mContext.getString(com.android.internal.R.string.fingerprint_acquired_partial))
+ .thenReturn(acquiredStr);
+ session.onAcquired(0, FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL, 0);
+ verify(mStatusBarService).onBiometricHelp(anyInt(), eq(acquiredStr));
+ verify(mClientReceiver).onAcquired(eq(1), eq(acquiredStr));
+
+ when(mResources.getStringArray(com.android.internal.R.array.fingerprint_acquired_vendor))
+ .thenReturn(new String[]{acquiredStrVendor});
+ session.onAcquired(0, FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR, 0);
+ verify(mStatusBarService).onBiometricHelp(anyInt(), eq(acquiredStrVendor));
+ verify(mClientReceiver).onAcquired(
+ eq(FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR_BASE), eq(acquiredStrVendor));
+ }
+
// TODO (b/208484275) : Enable these tests
// @Test
// public void testPreAuth_canAuthAndPrivacyDisabled() throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
index 550204b..4cfbb95 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
@@ -105,6 +105,7 @@
mMockPackageManager = mock(PackageManager.class);
mMockPackageMonitor = mock(PackageMonitor.class);
+ doReturn(mMockContext).when(mMockContext).createContextAsUser(any(), anyInt());
// For unit tests, set the default installer info
doReturn(DEFAULT_INSTALL_SOURCE_INFO).when(mMockPackageManager)
.getInstallSourceInfo(anyString());
diff --git a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
index da9de25..e20f1e7 100644
--- a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
@@ -131,6 +131,7 @@
doReturn(mMockPackageManager).when(mMockContext).getPackageManager();
doReturn(InstrumentationRegistry.getContext().getContentResolver())
.when(mMockContext).getContentResolver();
+ doReturn(mMockContext).when(mMockContext).createContextAsUser(any(), anyInt());
mStoragefile = new AtomicFile(new File(
Environment.getExternalStorageDirectory(), "systemUpdateUnitTests.xml"));
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
new file mode 100644
index 0000000..bcd807a
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
+import android.content.ComponentName;
+import android.net.Uri;
+import android.provider.Settings;
+import android.service.notification.Condition;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeDiff;
+import android.service.notification.ZenPolicy;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.ArrayMap;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.UiServiceTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class ZenModeDiffTest extends UiServiceTestCase {
+ // version is not included in the diff; manual & automatic rules have special handling
+ public static final Set<String> ZEN_MODE_CONFIG_EXEMPT_FIELDS =
+ Set.of("version", "manualRule", "automaticRules");
+
+ @Test
+ public void testRuleDiff_addRemoveSame() {
+ // Test add, remove, and both sides same
+ ZenModeConfig.ZenRule r = makeRule();
+
+ // Both sides same rule
+ ZenModeDiff.RuleDiff dSame = new ZenModeDiff.RuleDiff(r, r);
+ assertFalse(dSame.hasDiff());
+
+ // from existent rule to null: expect deleted
+ ZenModeDiff.RuleDiff deleted = new ZenModeDiff.RuleDiff(r, null);
+ assertTrue(deleted.hasDiff());
+ assertTrue(deleted.wasRemoved());
+
+ // from null to new rule: expect added
+ ZenModeDiff.RuleDiff added = new ZenModeDiff.RuleDiff(null, r);
+ assertTrue(added.hasDiff());
+ assertTrue(added.wasAdded());
+ }
+
+ @Test
+ public void testRuleDiff_fieldDiffs() throws Exception {
+ // Start these the same
+ ZenModeConfig.ZenRule r1 = makeRule();
+ ZenModeConfig.ZenRule r2 = makeRule();
+
+ // maps mapping field name -> expected output value as we set diffs
+ ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
+ ArrayMap<String, Object> expectedTo = new ArrayMap<>();
+ List<Field> fieldsForDiff = getFieldsForDiffCheck(
+ ZenModeConfig.ZenRule.class, Set.of()); // actually no exempt fields for ZenRule
+ generateFieldDiffs(r1, r2, fieldsForDiff, expectedFrom, expectedTo);
+
+ ZenModeDiff.RuleDiff d = new ZenModeDiff.RuleDiff(r1, r2);
+ assertTrue(d.hasDiff());
+
+ // Now diff them and check that each of the fields has a diff
+ for (Field f : fieldsForDiff) {
+ String name = f.getName();
+ assertNotNull("diff not found for field: " + name, d.getDiffForField(name));
+ assertTrue(d.getDiffForField(name).hasDiff());
+ assertTrue("unexpected field: " + name, expectedFrom.containsKey(name));
+ assertTrue("unexpected field: " + name, expectedTo.containsKey(name));
+ assertEquals(expectedFrom.get(name), d.getDiffForField(name).from());
+ assertEquals(expectedTo.get(name), d.getDiffForField(name).to());
+ }
+ }
+
+ @Test
+ public void testConfigDiff_addRemoveSame() {
+ // Default config, will test add, remove, and no change
+ ZenModeConfig c = new ZenModeConfig();
+
+ ZenModeDiff.ConfigDiff dSame = new ZenModeDiff.ConfigDiff(c, c);
+ assertFalse(dSame.hasDiff());
+
+ ZenModeDiff.ConfigDiff added = new ZenModeDiff.ConfigDiff(null, c);
+ assertTrue(added.hasDiff());
+ assertTrue(added.wasAdded());
+
+ ZenModeDiff.ConfigDiff removed = new ZenModeDiff.ConfigDiff(c, null);
+ assertTrue(removed.hasDiff());
+ assertTrue(removed.wasRemoved());
+ }
+
+ @Test
+ public void testConfigDiff_fieldDiffs() throws Exception {
+ // these two start the same
+ ZenModeConfig c1 = new ZenModeConfig();
+ ZenModeConfig c2 = new ZenModeConfig();
+
+ // maps mapping field name -> expected output value as we set diffs
+ ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
+ ArrayMap<String, Object> expectedTo = new ArrayMap<>();
+ List<Field> fieldsForDiff = getFieldsForDiffCheck(
+ ZenModeConfig.class, ZEN_MODE_CONFIG_EXEMPT_FIELDS);
+ generateFieldDiffs(c1, c2, fieldsForDiff, expectedFrom, expectedTo);
+
+ ZenModeDiff.ConfigDiff d = new ZenModeDiff.ConfigDiff(c1, c2);
+ assertTrue(d.hasDiff());
+
+ // Now diff them and check that each of the fields has a diff
+ for (Field f : fieldsForDiff) {
+ String name = f.getName();
+ assertNotNull("diff not found for field: " + name, d.getDiffForField(name));
+ assertTrue(d.getDiffForField(name).hasDiff());
+ assertTrue("unexpected field: " + name, expectedFrom.containsKey(name));
+ assertTrue("unexpected field: " + name, expectedTo.containsKey(name));
+ assertEquals(expectedFrom.get(name), d.getDiffForField(name).from());
+ assertEquals(expectedTo.get(name), d.getDiffForField(name).to());
+ }
+ }
+
+ @Test
+ public void testConfigDiff_specialSenders() {
+ // these two start the same
+ ZenModeConfig c1 = new ZenModeConfig();
+ ZenModeConfig c2 = new ZenModeConfig();
+
+ // set c1 and c2 to have some different senders
+ c1.allowMessagesFrom = ZenModeConfig.SOURCE_STAR;
+ c2.allowMessagesFrom = ZenModeConfig.SOURCE_CONTACT;
+ c1.allowConversationsFrom = ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
+ c2.allowConversationsFrom = ZenPolicy.CONVERSATION_SENDERS_NONE;
+
+ ZenModeDiff.ConfigDiff d = new ZenModeDiff.ConfigDiff(c1, c2);
+ assertTrue(d.hasDiff());
+
+ // Diff in top-level fields
+ assertTrue(d.getDiffForField("allowMessagesFrom").hasDiff());
+ assertTrue(d.getDiffForField("allowConversationsFrom").hasDiff());
+
+ // Bonus testing of stringification of people senders and conversation senders
+ assertTrue(d.toString().contains("allowMessagesFrom:stars->contacts"));
+ assertTrue(d.toString().contains("allowConversationsFrom:important->none"));
+ }
+
+ @Test
+ public void testConfigDiff_hasRuleDiffs() {
+ // two default configs
+ ZenModeConfig c1 = new ZenModeConfig();
+ ZenModeConfig c2 = new ZenModeConfig();
+
+ // two initially-identical rules
+ ZenModeConfig.ZenRule r1 = makeRule();
+ ZenModeConfig.ZenRule r2 = makeRule();
+
+ // one that will become a manual rule
+ ZenModeConfig.ZenRule m = makeRule();
+
+ // Add r1 to c1, but not r2 to c2 yet -- expect a rule to be deleted
+ c1.automaticRules.put(r1.id, r1);
+ ZenModeDiff.ConfigDiff deleteRule = new ZenModeDiff.ConfigDiff(c1, c2);
+ assertTrue(deleteRule.hasDiff());
+ assertNotNull(deleteRule.getAllAutomaticRuleDiffs());
+ assertTrue(deleteRule.getAllAutomaticRuleDiffs().containsKey("ruleId"));
+ assertTrue(deleteRule.getAllAutomaticRuleDiffs().get("ruleId").wasRemoved());
+
+ // Change r2 a little, add r2 to c2 as an automatic rule and m as a manual rule
+ r2.component = null;
+ r2.pkg = "different";
+ c2.manualRule = m;
+ c2.automaticRules.put(r2.id, r2);
+
+ // Expect diffs in both manual rule (added) and automatic rule (changed)
+ ZenModeDiff.ConfigDiff changed = new ZenModeDiff.ConfigDiff(c1, c2);
+ assertTrue(changed.hasDiff());
+ assertTrue(changed.getManualRuleDiff().hasDiff());
+
+ ArrayMap<String, ZenModeDiff.RuleDiff> automaticDiffs = changed.getAllAutomaticRuleDiffs();
+ assertNotNull(automaticDiffs);
+ assertTrue(automaticDiffs.containsKey("ruleId"));
+ assertNotNull(automaticDiffs.get("ruleId").getDiffForField("component"));
+ assertNull(automaticDiffs.get("ruleId").getDiffForField("component").to());
+ assertNotNull(automaticDiffs.get("ruleId").getDiffForField("pkg"));
+ assertEquals("different", automaticDiffs.get("ruleId").getDiffForField("pkg").to());
+ }
+
+ // Helper methods for working with configs, policies, rules
+ // Just makes a zen rule with fields filled in
+ private ZenModeConfig.ZenRule makeRule() {
+ ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
+ rule.configurationActivity = new ComponentName("a", "a");
+ rule.component = new ComponentName("b", "b");
+ rule.conditionId = new Uri.Builder().scheme("hello").build();
+ rule.condition = new Condition(rule.conditionId, "", Condition.STATE_TRUE);
+ rule.enabled = true;
+ rule.creationTime = 123;
+ rule.id = "ruleId";
+ rule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ rule.modified = false;
+ rule.name = "name";
+ rule.snoozing = true;
+ rule.pkg = "a";
+ return rule;
+ }
+
+ // Get the fields on which we would want to check a diff. The requirements are: not final or/
+ // static (as these should/can never change), and not in a specific list that's exempted.
+ private List<Field> getFieldsForDiffCheck(Class c, Set<String> exemptNames)
+ throws SecurityException {
+ Field[] fields = c.getDeclaredFields();
+ ArrayList<Field> out = new ArrayList<>();
+
+ for (Field field : fields) {
+ // Check for exempt reasons
+ int m = field.getModifiers();
+ if (Modifier.isFinal(m)
+ || Modifier.isStatic(m)
+ || exemptNames.contains(field.getName())) {
+ continue;
+ }
+ out.add(field);
+ }
+ return out;
+ }
+
+ // Generate a set of generic diffs for the specified two objects and the fields to generate
+ // diffs for, and store the results in the provided expectation maps to be able to check the
+ // output later.
+ private void generateFieldDiffs(Object a, Object b, List<Field> fields,
+ ArrayMap<String, Object> expectedA, ArrayMap<String, Object> expectedB)
+ throws Exception {
+ // different classes passed in means bad input
+ assertEquals(a.getClass(), b.getClass());
+
+ // Loop through fields for which we want to check diffs, set a diff and keep track of
+ // what we set.
+ for (Field f : fields) {
+ f.setAccessible(true);
+ // Just double-check also that the fields actually are for the class declared
+ assertEquals(f.getDeclaringClass(), a.getClass());
+ Class t = f.getType();
+ // handle the full set of primitive types first
+ if (boolean.class.equals(t)) {
+ f.setBoolean(a, true);
+ expectedA.put(f.getName(), true);
+ f.setBoolean(b, false);
+ expectedB.put(f.getName(), false);
+ } else if (int.class.equals(t)) {
+ // these are not actually valid going to be valid for arbitrary int enum fields, but
+ // we just put something in there regardless.
+ f.setInt(a, 2);
+ expectedA.put(f.getName(), 2);
+ f.setInt(b, 1);
+ expectedB.put(f.getName(), 1);
+ } else if (long.class.equals(t)) {
+ f.setLong(a, 200L);
+ expectedA.put(f.getName(), 200L);
+ f.setLong(b, 100L);
+ expectedB.put(f.getName(), 100L);
+ } else if (t.isPrimitive()) {
+ // This method doesn't yet handle other primitive types. If the relevant diff
+ // classes gain new fields of these types, please add another clause here.
+ fail("primitive type not handled by generateFieldDiffs: " + t.getName());
+ } else if (String.class.equals(t)) {
+ f.set(a, "string1");
+ expectedA.put(f.getName(), "string1");
+ f.set(b, "string2");
+ expectedB.put(f.getName(), "string2");
+ } else {
+ // catch-all for other types: have the field be "added"
+ f.set(a, null);
+ expectedA.put(f.getName(), null);
+ try {
+ f.set(b, t.getDeclaredConstructor().newInstance());
+ expectedB.put(f.getName(), t.getDeclaredConstructor().newInstance());
+ } catch (Exception e) {
+ // No default constructor, or blithely attempting to construct something doesn't
+ // work for some reason. If the default value isn't null, then keep it.
+ if (f.get(b) != null) {
+ expectedB.put(f.getName(), f.get(b));
+ } else {
+ // If we can't even rely on that, fail. Have the test-writer special case
+ // something, as this is not able to be genericized.
+ fail("could not generically construct value for field: " + f.getName());
+ }
+ }
+ }
+ }
+ }
+}
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 6f9798e..b2a5401 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -69,8 +69,6 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.AutomaticZenRule;
@@ -78,7 +76,6 @@
import android.app.NotificationManager.Policy;
import android.content.ComponentName;
import android.content.ContentResolver;
-import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@@ -90,7 +87,6 @@
import android.media.AudioSystem;
import android.media.VolumePolicy;
import android.net.Uri;
-import android.os.Binder;
import android.os.Process;
import android.os.UserHandle;
import android.provider.Settings;
@@ -98,6 +94,7 @@
import android.service.notification.Condition;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenModeConfig.ScheduleInfo;
+import android.service.notification.ZenModeDiff;
import android.service.notification.ZenPolicy;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
@@ -877,7 +874,8 @@
mZenModeHelperSpy.readXml(parser, false, UserHandle.USER_ALL);
assertEquals("Config mismatch: current vs expected: "
- + mZenModeHelperSpy.mConfig.diff(expected), expected, mZenModeHelperSpy.mConfig);
+ + new ZenModeDiff.ConfigDiff(mZenModeHelperSpy.mConfig, expected), expected,
+ mZenModeHelperSpy.mConfig);
}
@Test
@@ -1046,7 +1044,8 @@
ZenModeConfig actual = mZenModeHelperSpy.mConfigs.get(10);
assertEquals(
- "Config mismatch: current vs expected: " + actual.diff(config10), config10, actual);
+ "Config mismatch: current vs expected: "
+ + new ZenModeDiff.ConfigDiff(actual, config10), config10, actual);
assertNotEquals("Expected config mismatch", config11, mZenModeHelperSpy.mConfigs.get(11));
}
@@ -1062,7 +1061,8 @@
mZenModeHelperSpy.readXml(parser, true, UserHandle.USER_SYSTEM);
assertEquals("Config mismatch: current vs original: "
- + mZenModeHelperSpy.mConfig.diff(original), original, mZenModeHelperSpy.mConfig);
+ + new ZenModeDiff.ConfigDiff(mZenModeHelperSpy.mConfig, original),
+ original, mZenModeHelperSpy.mConfig);
assertEquals(original.hashCode(), mZenModeHelperSpy.mConfig.hashCode());
}
@@ -1083,8 +1083,9 @@
ZenModeConfig actual = mZenModeHelperSpy.mConfigs.get(10);
expected.user = 10;
- assertEquals(
- "Config mismatch: current vs original: " + actual.diff(expected), expected, actual);
+ assertEquals("Config mismatch: current vs original: "
+ + new ZenModeDiff.ConfigDiff(actual, expected),
+ expected, actual);
assertEquals(expected.hashCode(), actual.hashCode());
expected.user = 0;
assertNotEquals(expected, mZenModeHelperSpy.mConfig);
diff --git a/services/tests/voiceinteractiontests/Android.bp b/services/tests/voiceinteractiontests/Android.bp
index 986fb71..e704ebf 100644
--- a/services/tests/voiceinteractiontests/Android.bp
+++ b/services/tests/voiceinteractiontests/Android.bp
@@ -40,6 +40,7 @@
"platform-test-annotations",
"services.core",
"services.voiceinteraction",
+ "services.soundtrigger",
"servicestests-core-utils",
"servicestests-utils-mockito-extended",
"truth-prebuilt",
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 341b331..8f2b470 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -83,6 +83,7 @@
import static com.android.server.wm.ActivityRecord.FINISH_RESULT_CANCELLED;
import static com.android.server.wm.ActivityRecord.FINISH_RESULT_REMOVED;
import static com.android.server.wm.ActivityRecord.FINISH_RESULT_REQUESTED;
+import static com.android.server.wm.ActivityRecord.LAUNCH_SOURCE_TYPE_HOME;
import static com.android.server.wm.ActivityRecord.State.DESTROYED;
import static com.android.server.wm.ActivityRecord.State.DESTROYING;
import static com.android.server.wm.ActivityRecord.State.FINISHING;
@@ -3688,6 +3689,23 @@
assertTrue(activity.inTransition());
}
+ /**
+ * Verifies the task is moved to back when back pressed if the root activity was originally
+ * started from Launcher.
+ */
+ @Test
+ public void testMoveTaskToBackWhenStartedFromLauncher() {
+ final Task task = createTask(mDisplayContent);
+ final ActivityRecord ar = createActivityRecord(task);
+ task.realActivity = ar.mActivityComponent;
+ ar.intent.setAction(Intent.ACTION_MAIN);
+ ar.intent.addCategory(Intent.CATEGORY_LAUNCHER);
+ doReturn(true).when(ar).isLaunchSourceType(eq(LAUNCH_SOURCE_TYPE_HOME));
+
+ mAtm.mActivityClientController.onBackPressed(ar.token, null /* callback */);
+ verify(task).moveTaskToBack(any());
+ }
+
private ICompatCameraControlCallback getCompatCameraControlCallback() {
return new ICompatCameraControlCallback.Stub() {
@Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
index 8e91ca2..77efc4b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
@@ -42,6 +42,8 @@
import androidx.test.filters.SmallTest;
+import com.android.server.testutils.TestHandler;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -371,6 +373,49 @@
mAppWindow.removeImmediately();
}
+ @Test
+ public void testQueueSyncSet() {
+ final TestHandler testHandler = new TestHandler(null);
+ TestWindowContainer mockWC = new TestWindowContainer(mWm, true /* waiter */);
+ TestWindowContainer mockWC2 = new TestWindowContainer(mWm, true /* waiter */);
+
+ final BLASTSyncEngine bse = createTestBLASTSyncEngine(testHandler);
+
+ BLASTSyncEngine.TransactionReadyListener listener = mock(
+ BLASTSyncEngine.TransactionReadyListener.class);
+
+ int id = startSyncSet(bse, listener);
+ bse.addToSyncSet(id, mockWC);
+ bse.setReady(id);
+ bse.onSurfacePlacement();
+ verify(listener, times(0)).onTransactionReady(eq(id), notNull());
+
+ final int[] nextId = new int[]{-1};
+ bse.queueSyncSet(
+ () -> nextId[0] = startSyncSet(bse, listener),
+ () -> {
+ bse.setReady(nextId[0]);
+ bse.addToSyncSet(nextId[0], mockWC2);
+ });
+
+ // Make sure it is queued
+ assertEquals(-1, nextId[0]);
+
+ // Finish the original sync and see that we've started a new sync-set immediately but
+ // that the readiness was posted.
+ mockWC.onSyncFinishedDrawing();
+ verify(mWm.mWindowPlacerLocked).requestTraversal();
+ bse.onSurfacePlacement();
+ verify(listener, times(1)).onTransactionReady(eq(id), notNull());
+
+ assertTrue(nextId[0] != -1);
+ assertFalse(bse.isReady(nextId[0]));
+
+ // now make sure the applySync callback was posted.
+ testHandler.flush();
+ assertTrue(bse.isReady(nextId[0]));
+ }
+
static int startSyncSet(BLASTSyncEngine engine,
BLASTSyncEngine.TransactionReadyListener listener) {
return engine.startSyncSet(listener, BLAST_TIMEOUT_DURATION, "Test");
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 90506d4..1e2fdec 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -1404,8 +1404,6 @@
// We are now going to simulate closing task1 to return back to (open) task2.
final Transition closeTransition = controller.createTransition(TRANSIT_CLOSE);
- closeTransition.collectExistenceChange(task1);
- closeTransition.collectExistenceChange(activity1);
closeTransition.collectExistenceChange(task2);
closeTransition.collectExistenceChange(activity2);
closeTransition.setTransientLaunch(activity2, task1);
@@ -1416,7 +1414,7 @@
assertNotNull(activity1ChangeInfo);
assertTrue(activity1ChangeInfo.hasChanged());
// No need to wait for the activity in transient hide task.
- assertTrue(activity1.isSyncFinished());
+ assertEquals(WindowContainer.SYNC_STATE_NONE, activity1.mSyncState);
activity1.setVisibleRequested(false);
activity2.setVisibleRequested(true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 7e3ec55..f85cdf0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -77,6 +77,7 @@
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
+import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -886,7 +887,11 @@
}
BLASTSyncEngine createTestBLASTSyncEngine() {
- return new BLASTSyncEngine(mWm) {
+ return createTestBLASTSyncEngine(mWm.mH);
+ }
+
+ BLASTSyncEngine createTestBLASTSyncEngine(Handler handler) {
+ return new BLASTSyncEngine(mWm, handler) {
@Override
void scheduleTimeout(SyncGroup s, long timeoutMs) {
// Disable timeout.
diff --git a/services/voiceinteraction/Android.bp b/services/voiceinteraction/Android.bp
index 7332d2d..de8d144 100644
--- a/services/voiceinteraction/Android.bp
+++ b/services/voiceinteraction/Android.bp
@@ -9,11 +9,60 @@
filegroup {
name: "services.voiceinteraction-sources",
- srcs: ["java/**/*.java"],
+ srcs: ["java/com/android/server/voiceinteraction/*.java"],
path: "java",
visibility: ["//frameworks/base/services"],
}
+filegroup {
+ name: "services.soundtrigger_middleware-sources",
+ srcs: ["java/com/android/server/soundtrigger_middleware/*.java"],
+ path: "java",
+ visibility: ["//visibility:private"],
+}
+
+filegroup {
+ name: "services.soundtrigger_service-sources",
+ srcs: ["java/com/android/server/soundtrigger/*.java"],
+ path: "java",
+ visibility: ["//visibility:private"],
+}
+
+filegroup {
+ name: "services.soundtrigger-sources",
+ srcs: [
+ ":services.soundtrigger_service-sources",
+ ":services.soundtrigger_middleware-sources",
+ ],
+ path: "java",
+ visibility: ["//frameworks/base/services"],
+}
+
+java_library_static {
+ name: "services.soundtrigger_middleware",
+ defaults: ["platform_service_defaults"],
+ srcs: [":services.soundtrigger_middleware-sources"],
+ libs: [
+ "services.core",
+ ],
+ static_libs: [
+ "android.hardware.soundtrigger-V2.3-java",
+ ],
+ visibility: ["//visibility/base/services/tests/voiceinteraction"],
+}
+
+java_library_static {
+ name: "services.soundtrigger",
+ defaults: ["platform_service_defaults"],
+ srcs: [":services.soundtrigger_service-sources"],
+ libs: [
+ "services.core",
+ ],
+ static_libs: [
+ "services.soundtrigger_middleware",
+ ],
+}
+
java_library_static {
name: "services.voiceinteraction",
defaults: ["platform_service_defaults"],
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 04c1c04..203a3e7 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -39,12 +39,13 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.PermissionChecker;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.hardware.soundtrigger.ConversionUtil;
import android.hardware.soundtrigger.IRecognitionStatusCallback;
import android.hardware.soundtrigger.ModelParams;
-import android.hardware.soundtrigger.ConversionUtil;
import android.hardware.soundtrigger.SoundTrigger;
import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
@@ -64,6 +65,7 @@
import android.media.soundtrigger.ISoundTriggerDetectionService;
import android.media.soundtrigger.ISoundTriggerDetectionServiceClient;
import android.media.soundtrigger.SoundTriggerDetectionService;
+import android.media.soundtrigger_middleware.ISoundTriggerInjection;
import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
import android.os.Binder;
import android.os.Bundle;
@@ -74,8 +76,8 @@
import android.os.ParcelUuid;
import android.os.PowerManager;
import android.os.RemoteException;
-import android.os.ServiceSpecificException;
import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
@@ -86,6 +88,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.ISoundTriggerService;
import com.android.internal.app.ISoundTriggerSession;
+import com.android.server.SoundTriggerInternal;
import com.android.server.SystemService;
import com.android.server.utils.EventLogger;
@@ -98,8 +101,8 @@
import java.util.Objects;
import java.util.TreeMap;
import java.util.UUID;
-import java.util.stream.Collectors;
import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
/**
* A single SystemService to manage all sound/voice-based sound models on the DSP.
@@ -296,6 +299,23 @@
return listUnderlyingModuleProperties(originatorIdentity);
}
}
+
+ @Override
+ public void attachInjection(@NonNull ISoundTriggerInjection injection) {
+ if (PermissionChecker.checkCallingPermissionForPreflight(mContext,
+ android.Manifest.permission.MANAGE_SOUND_TRIGGER, null)
+ != PermissionChecker.PERMISSION_GRANTED) {
+ throw new SecurityException();
+ }
+ try {
+ ISoundTriggerMiddlewareService.Stub
+ .asInterface(ServiceManager
+ .waitForService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE))
+ .attachFakeHalInjection(injection);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
class SoundTriggerSessionStub extends ISoundTriggerSession.Stub {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
index aaf7a9e..5846ff6 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
@@ -39,7 +39,7 @@
*
* @hide
*/
-public class DatabaseHelper extends SQLiteOpenHelper {
+public class DatabaseHelper extends SQLiteOpenHelper implements IEnrolledModelDb {
static final String TAG = "SoundModelDBHelper";
static final boolean DBG = false;
@@ -153,11 +153,7 @@
}
}
- /**
- * Updates the given keyphrase model, adds it, if it doesn't already exist.
- *
- * TODO: We only support one keyphrase currently.
- */
+ @Override
public boolean updateKeyphraseSoundModel(KeyphraseSoundModel soundModel) {
synchronized(this) {
SQLiteDatabase db = getWritableDatabase();
@@ -193,9 +189,7 @@
}
}
- /**
- * Deletes the sound model and associated keyphrases.
- */
+ @Override
public boolean deleteKeyphraseSoundModel(int keyphraseId, int userHandle, String bcp47Locale) {
// Normalize the locale to guard against SQL injection.
bcp47Locale = Locale.forLanguageTag(bcp47Locale).toLanguageTag();
@@ -218,12 +212,7 @@
}
}
- /**
- * Returns a matching {@link KeyphraseSoundModel} for the keyphrase ID.
- * Returns null if a match isn't found.
- *
- * TODO: We only support one keyphrase currently.
- */
+ @Override
public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, int userHandle,
String bcp47Locale) {
// Sanitize the locale to guard against SQL injection.
@@ -237,12 +226,7 @@
}
}
- /**
- * Returns a matching {@link KeyphraseSoundModel} for the keyphrase string.
- * Returns null if a match isn't found.
- *
- * TODO: We only support one keyphrase currently.
- */
+ @Override
public KeyphraseSoundModel getKeyphraseSoundModel(String keyphrase, int userHandle,
String bcp47Locale) {
// Sanitize the locale to guard against SQL injection.
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/IEnrolledModelDb.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/IEnrolledModelDb.java
new file mode 100644
index 0000000..f10c2f6
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/IEnrolledModelDb.java
@@ -0,0 +1,90 @@
+/**
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.voiceinteraction;
+
+import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
+import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
+
+import java.io.PrintWriter;
+
+/**
+ * Interface for registering and querying the enrolled keyphrase model database for
+ * {@link VoiceInteractionManagerService}.
+ * This interface only supports one keyphrase per {@link KeyphraseSoundModel}.
+ * The non-update methods are uniquely keyed on fields of the first keyphrase
+ * {@link KeyphraseSoundModel#getKeyphrases()}.
+ * @hide
+ */
+public interface IEnrolledModelDb {
+
+ //TODO(273286174): We only support one keyphrase currently.
+ /**
+ * Register the given {@link KeyphraseSoundModel}, or updates it if it already exists.
+ *
+ * @param soundModel - The sound model to register in the database.
+ * Updates the sound model if the keyphrase id, users, locale match an existing entry.
+ * Must have one and only one associated {@link Keyphrase}.
+ * @return - {@code true} if successful, {@code false} if unsuccessful
+ */
+ boolean updateKeyphraseSoundModel(KeyphraseSoundModel soundModel);
+
+ /**
+ * Deletes the previously registered keyphrase sound model from the database.
+ *
+ * @param keyphraseId - The (first) keyphrase ID of the KeyphraseSoundModel to delete.
+ * @param userHandle - The user handle making this request. Must be included in the user
+ * list of the registered sound model.
+ * @param bcp47Locale - The locale of the (first) keyphrase associated with this model.
+ * @return - {@code true} if successful, {@code false} if unsuccessful
+ */
+ boolean deleteKeyphraseSoundModel(int keyphraseId, int userHandle, String bcp47Locale);
+
+ //TODO(273286174): We only support one keyphrase currently.
+ /**
+ * Returns the first matching {@link KeyphraseSoundModel} for the keyphrase ID, locale pair,
+ * contingent on the userHandle existing in the user list for the model.
+ * Returns null if a match isn't found.
+ *
+ * @param keyphraseId - The (first) keyphrase ID of the KeyphraseSoundModel to query.
+ * @param userHandle - The user handle making this request. Must be included in the user
+ * list of the registered sound model.
+ * @param bcp47Locale - The locale of the (first) keyphrase associated with this model.
+ * @return - {@code true} if successful, {@code false} if unsuccessful
+ */
+ KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, int userHandle,
+ String bcp47Locale);
+
+ //TODO(273286174): We only support one keyphrase currently.
+ /**
+ * Returns the first matching {@link KeyphraseSoundModel} for the keyphrase ID, locale pair,
+ * contingent on the userHandle existing in the user list for the model.
+ * Returns null if a match isn't found.
+ *
+ * @param keyphrase - The text of (the first) keyphrase of the KeyphraseSoundModel to query.
+ * @param userHandle - The user handle making this request. Must be included in the user
+ * list of the registered sound model.
+ * @param bcp47Locale - The locale of the (first) keyphrase associated with this model.
+ * @return - {@code true} if successful, {@code false} if unsuccessful
+ */
+ KeyphraseSoundModel getKeyphraseSoundModel(String keyphrase, int userHandle,
+ String bcp47Locale);
+
+ /**
+ * Dumps contents of database for dumpsys
+ */
+ void dump(PrintWriter pw);
+}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/TestModelEnrollmentDatabase.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/TestModelEnrollmentDatabase.java
new file mode 100644
index 0000000..9bbaf8e
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/TestModelEnrollmentDatabase.java
@@ -0,0 +1,148 @@
+/**
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.voiceinteraction;
+
+import android.annotation.NonNull;
+import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
+import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.StringJoiner;
+
+/**
+ * In memory model enrollment database for testing purposes.
+ * @hide
+ */
+public class TestModelEnrollmentDatabase implements IEnrolledModelDb {
+
+ // Record representing the primary key used in the real model database.
+ private static final class EnrollmentKey {
+ private final int mKeyphraseId;
+ private final List<Integer> mUserIds;
+ private final String mLocale;
+
+ EnrollmentKey(int keyphraseId,
+ @NonNull List<Integer> userIds, @NonNull String locale) {
+ mKeyphraseId = keyphraseId;
+ mUserIds = Objects.requireNonNull(userIds);
+ mLocale = Objects.requireNonNull(locale);
+ }
+
+ int keyphraseId() {
+ return mKeyphraseId;
+ }
+
+ List<Integer> userIds() {
+ return mUserIds;
+ }
+
+ String locale() {
+ return mLocale;
+ }
+
+ @Override
+ public String toString() {
+ StringJoiner sj = new StringJoiner(", ", "{", "}");
+ sj.add("keyphraseId: " + mKeyphraseId);
+ sj.add("userIds: " + mUserIds.toString());
+ sj.add("locale: " + mLocale.toString());
+ return "EnrollmentKey: " + sj.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int res = 1;
+ res = prime * res + mKeyphraseId;
+ res = prime * res + mUserIds.hashCode();
+ res = prime * res + mLocale.hashCode();
+ return res;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (other == null) return false;
+ if (!(other instanceof EnrollmentKey)) return false;
+ EnrollmentKey that = (EnrollmentKey) other;
+ if (mKeyphraseId != that.mKeyphraseId) return false;
+ if (!mUserIds.equals(that.mUserIds)) return false;
+ if (!mLocale.equals(that.mLocale)) return false;
+ return true;
+ }
+
+ }
+
+ private final Map<EnrollmentKey, KeyphraseSoundModel> mModelMap = new HashMap<>();
+
+ @Override
+ public boolean updateKeyphraseSoundModel(KeyphraseSoundModel soundModel) {
+ final Keyphrase keyphrase = soundModel.getKeyphrases()[0];
+ mModelMap.put(new EnrollmentKey(keyphrase.getId(),
+ Arrays.stream(keyphrase.getUsers()).boxed().toList(),
+ keyphrase.getLocale().toLanguageTag()),
+ soundModel);
+ return true;
+ }
+
+ @Override
+ public boolean deleteKeyphraseSoundModel(int keyphraseId, int userHandle, String bcp47Locale) {
+ return mModelMap.keySet().removeIf(key -> (key.keyphraseId() == keyphraseId)
+ && key.locale().equals(bcp47Locale)
+ && key.userIds().contains(userHandle));
+ }
+
+ @Override
+ public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, int userHandle,
+ String bcp47Locale) {
+ return mModelMap.entrySet()
+ .stream()
+ .filter((entry) -> (entry.getKey().keyphraseId() == keyphraseId)
+ && entry.getKey().locale().equals(bcp47Locale)
+ && entry.getKey().userIds().contains(userHandle))
+ .findFirst()
+ .map((entry) -> entry.getValue())
+ .orElse(null);
+ }
+
+ @Override
+ public KeyphraseSoundModel getKeyphraseSoundModel(String keyphrase, int userHandle,
+ String bcp47Locale) {
+ return mModelMap.entrySet()
+ .stream()
+ .filter((entry) -> (entry.getValue().getKeyphrases()[0].getText().equals(keyphrase)
+ && entry.getKey().locale().equals(bcp47Locale)
+ && entry.getKey().userIds().contains(userHandle)))
+ .findFirst()
+ .map((entry) -> entry.getValue())
+ .orElse(null);
+ }
+
+
+ /**
+ * Dumps contents of database for dumpsys
+ */
+ public void dump(PrintWriter pw) {
+ pw.println("Using test enrollment database, with enrolled models:");
+ pw.println(mModelMap);
+ }
+}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index e1da2ca..1d7b966 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -99,11 +99,11 @@
import com.android.internal.util.DumpUtils;
import com.android.server.FgThread;
import com.android.server.LocalServices;
+import com.android.server.SoundTriggerInternal;
import com.android.server.SystemService;
import com.android.server.UiThread;
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.permission.LegacyPermissionManagerInternal;
-import com.android.server.soundtrigger.SoundTriggerInternal;
import com.android.server.utils.Slogf;
import com.android.server.utils.TimingsTraceAndSlog;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -125,7 +125,9 @@
final Context mContext;
final ContentResolver mResolver;
- final DatabaseHelper mDbHelper;
+ // Can be overridden for testing purposes
+ private IEnrolledModelDb mDbHelper;
+ private final IEnrolledModelDb mRealDbHelper;
final ActivityManagerInternal mAmInternal;
final ActivityTaskManagerInternal mAtmInternal;
final UserManagerInternal mUserManagerInternal;
@@ -143,7 +145,7 @@
mResolver = context.getContentResolver();
mUserManagerInternal = Objects.requireNonNull(
LocalServices.getService(UserManagerInternal.class));
- mDbHelper = new DatabaseHelper(context);
+ mDbHelper = mRealDbHelper = new DatabaseHelper(context);
mServiceStub = new VoiceInteractionManagerServiceStub();
mAmInternal = Objects.requireNonNull(
LocalServices.getService(ActivityManagerInternal.class));
@@ -1605,6 +1607,42 @@
}
}
+ @Override
+ @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_VOICE_KEYPHRASES)
+ public void setModelDatabaseForTestEnabled(boolean enabled, IBinder token) {
+ super.setModelDatabaseForTestEnabled_enforcePermission();
+ enforceCallerAllowedToEnrollVoiceModel();
+ synchronized (this) {
+ if (enabled) {
+ // Replace the dbhelper with a new test db
+ final var db = new TestModelEnrollmentDatabase();
+ try {
+ // Listen to our caller death, and make sure we revert to the real
+ // db if they left the model in a test state.
+ token.linkToDeath(() -> {
+ synchronized (this) {
+ if (mDbHelper == db) {
+ mDbHelper = mRealDbHelper;
+ mImpl.notifySoundModelsChangedLocked();
+ }
+ }
+ }, 0);
+ } catch (RemoteException e) {
+ // If the caller is already dead, nothing to do.
+ return;
+ }
+ mDbHelper = db;
+ mImpl.notifySoundModelsChangedLocked();
+ } else {
+ // Nothing to do if the db is already set to the real impl.
+ if (mDbHelper != mRealDbHelper) {
+ mDbHelper = mRealDbHelper;
+ mImpl.notifySoundModelsChangedLocked();
+ }
+ }
+ }
+ }
+
//----------------- SoundTrigger APIs --------------------------------//
@Override
public boolean isEnrolledForKeyphrase(int keyphraseId, String bcp47Locale) {
@@ -1712,28 +1750,27 @@
final long caller = Binder.clearCallingIdentity();
try {
KeyphraseSoundModel soundModel =
- mDbHelper.getKeyphraseSoundModel(keyphraseId, callingUserId, bcp47Locale);
+ mDbHelper.getKeyphraseSoundModel(keyphraseId,
+ callingUserId, bcp47Locale);
if (soundModel == null
|| soundModel.getUuid() == null
|| soundModel.getKeyphrases() == null) {
Slog.w(TAG, "No matching sound model found in startRecognition");
return SoundTriggerInternal.STATUS_ERROR;
- } else {
- // Regardless of the status of the start recognition, we need to make sure
- // that we unload this model if needed later.
- synchronized (VoiceInteractionManagerServiceStub.this) {
- mLoadedKeyphraseIds.put(keyphraseId, this);
- if (mSessionExternalCallback == null
- || mSessionInternalCallback == null
- || callback.asBinder() != mSessionExternalCallback.asBinder()) {
- mSessionInternalCallback = createSoundTriggerCallbackLocked(
- callback);
- mSessionExternalCallback = callback;
- }
- }
- return mSession.startRecognition(keyphraseId, soundModel,
- mSessionInternalCallback, recognitionConfig, runInBatterySaverMode);
}
+ // Regardless of the status of the start recognition, we need to make sure
+ // that we unload this model if needed later.
+ synchronized (VoiceInteractionManagerServiceStub.this) {
+ mLoadedKeyphraseIds.put(keyphraseId, this);
+ if (mSessionExternalCallback == null
+ || mSessionInternalCallback == null
+ || callback.asBinder() != mSessionExternalCallback.asBinder()) {
+ mSessionInternalCallback = createSoundTriggerCallbackLocked(callback);
+ mSessionExternalCallback = callback;
+ }
+ }
+ return mSession.startRecognition(keyphraseId, soundModel,
+ mSessionInternalCallback, recognitionConfig, runInBatterySaverMode);
} finally {
Binder.restoreCallingIdentity(caller);
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 62be2a55..0ad86c1 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -71,7 +71,6 @@
import android.util.Slog;
import android.view.IWindowManager;
-import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IHotwordRecognitionStatusCallback;
import com.android.internal.app.IVisualQueryDetectionAttentionListener;
import com.android.internal.app.IVoiceActionCheckCallback;
@@ -248,7 +247,6 @@
Context.RECEIVER_EXPORTED);
}
- @GuardedBy("this")
public void grantImplicitAccessLocked(int grantRecipientUid, @Nullable Intent intent) {
final int grantRecipientAppId = UserHandle.getAppId(grantRecipientUid);
final int grantRecipientUserId = UserHandle.getUserId(grantRecipientUid);
@@ -258,7 +256,6 @@
/* direct= */ true);
}
- @GuardedBy("this")
public boolean showSessionLocked(@Nullable Bundle args, int flags,
@Nullable String attributionTag,
@Nullable IVoiceInteractionSessionShowCallback showCallback,
@@ -331,7 +328,6 @@
}
}
- @GuardedBy("this")
public boolean hideSessionLocked() {
if (mActiveSession != null) {
return mActiveSession.hideLocked();
@@ -339,7 +335,6 @@
return false;
}
- @GuardedBy("this")
public boolean deliverNewSessionLocked(IBinder token,
IVoiceInteractionSession session, IVoiceInteractor interactor) {
if (mActiveSession == null || token != mActiveSession.mToken) {
@@ -350,7 +345,6 @@
return true;
}
- @GuardedBy("this")
public int startVoiceActivityLocked(@Nullable String callingFeatureId, int callingPid,
int callingUid, IBinder token, Intent intent, String resolvedType) {
try {
@@ -373,7 +367,6 @@
}
}
- @GuardedBy("this")
public int startAssistantActivityLocked(@Nullable String callingFeatureId, int callingPid,
int callingUid, IBinder token, Intent intent, String resolvedType,
@NonNull Bundle bundle) {
@@ -397,7 +390,6 @@
}
}
- @GuardedBy("this")
public void requestDirectActionsLocked(@NonNull IBinder token, int taskId,
@NonNull IBinder assistToken, @Nullable RemoteCallback cancellationCallback,
@NonNull RemoteCallback callback) {
@@ -453,7 +445,6 @@
}
}
- @GuardedBy("this")
void performDirectActionLocked(@NonNull IBinder token, @NonNull String actionId,
@Nullable Bundle arguments, int taskId, IBinder assistToken,
@Nullable RemoteCallback cancellationCallback,
@@ -480,7 +471,6 @@
}
}
- @GuardedBy("this")
public void setKeepAwakeLocked(IBinder token, boolean keepAwake) {
try {
if (mActiveSession == null || token != mActiveSession.mToken) {
@@ -493,7 +483,6 @@
}
}
- @GuardedBy("this")
public void closeSystemDialogsLocked(IBinder token) {
try {
if (mActiveSession == null || token != mActiveSession.mToken) {
@@ -506,7 +495,6 @@
}
}
- @GuardedBy("this")
public void finishLocked(IBinder token, boolean finishTask) {
if (mActiveSession == null || (!finishTask && token != mActiveSession.mToken)) {
Slog.w(TAG, "finish does not match active session");
@@ -516,7 +504,6 @@
mActiveSession = null;
}
- @GuardedBy("this")
public void setDisabledShowContextLocked(int callingUid, int flags) {
int activeUid = mInfo.getServiceInfo().applicationInfo.uid;
if (callingUid != activeUid) {
@@ -526,7 +513,6 @@
mDisabledShowContext = flags;
}
- @GuardedBy("this")
public int getDisabledShowContextLocked(int callingUid) {
int activeUid = mInfo.getServiceInfo().applicationInfo.uid;
if (callingUid != activeUid) {
@@ -536,7 +522,6 @@
return mDisabledShowContext;
}
- @GuardedBy("this")
public int getUserDisabledShowContextLocked(int callingUid) {
int activeUid = mInfo.getServiceInfo().applicationInfo.uid;
if (callingUid != activeUid) {
@@ -550,7 +535,6 @@
return mInfo.getSupportsLocalInteraction();
}
- @GuardedBy("this")
public void startListeningVisibleActivityChangedLocked(@NonNull IBinder token) {
if (DEBUG) {
Slog.d(TAG, "startListeningVisibleActivityChangedLocked: token=" + token);
@@ -563,7 +547,6 @@
mActiveSession.startListeningVisibleActivityChangedLocked();
}
- @GuardedBy("this")
public void stopListeningVisibleActivityChangedLocked(@NonNull IBinder token) {
if (DEBUG) {
Slog.d(TAG, "stopListeningVisibleActivityChangedLocked: token=" + token);
@@ -576,7 +559,6 @@
mActiveSession.stopListeningVisibleActivityChangedLocked();
}
- @GuardedBy("this")
public void notifyActivityDestroyedLocked(@NonNull IBinder activityToken) {
if (DEBUG) {
Slog.d(TAG, "notifyActivityDestroyedLocked activityToken=" + activityToken);
@@ -591,7 +573,6 @@
mActiveSession.notifyActivityDestroyedLocked(activityToken);
}
- @GuardedBy("this")
public void notifyActivityEventChangedLocked(@NonNull IBinder activityToken, int type) {
if (DEBUG) {
Slog.d(TAG, "notifyActivityEventChangedLocked type=" + type);
@@ -606,7 +587,6 @@
mActiveSession.notifyActivityEventChangedLocked(activityToken, type);
}
- @GuardedBy("this")
public void updateStateLocked(
@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory,
@@ -627,7 +607,6 @@
}
}
- @GuardedBy("this")
private void verifyDetectorForHotwordDetectionLocked(
@Nullable SharedMemory sharedMemory,
IHotwordRecognitionStatusCallback callback,
@@ -685,7 +664,6 @@
voiceInteractionServiceUid);
}
- @GuardedBy("this")
private void verifyDetectorForVisualQueryDetectionLocked(@Nullable SharedMemory sharedMemory) {
Slog.v(TAG, "verifyDetectorForVisualQueryDetectionLocked");
@@ -724,7 +702,6 @@
}
}
- @GuardedBy("this")
public void initAndVerifyDetectorLocked(
@NonNull Identity voiceInteractorIdentity,
@Nullable PersistableBundle options,
@@ -769,7 +746,6 @@
detectorType);
}
- @GuardedBy("this")
public void destroyDetectorLocked(IBinder token) {
Slog.v(TAG, "destroyDetectorLocked");
@@ -788,7 +764,6 @@
}
}
- @GuardedBy("this")
public void shutdownHotwordDetectionServiceLocked() {
if (DEBUG) {
Slog.d(TAG, "shutdownHotwordDetectionServiceLocked");
@@ -801,7 +776,6 @@
mHotwordDetectionConnection = null;
}
- @GuardedBy("this")
public void setVisualQueryDetectionAttentionListenerLocked(
@Nullable IVisualQueryDetectionAttentionListener listener) {
if (mHotwordDetectionConnection == null) {
@@ -810,7 +784,6 @@
mHotwordDetectionConnection.setVisualQueryDetectionAttentionListenerLocked(listener);
}
- @GuardedBy("this")
public void startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
if (DEBUG) {
Slog.d(TAG, "startPerceivingLocked");
@@ -824,7 +797,6 @@
mHotwordDetectionConnection.startPerceivingLocked(callback);
}
- @GuardedBy("this")
public void stopPerceivingLocked() {
if (DEBUG) {
Slog.d(TAG, "stopPerceivingLocked");
@@ -838,7 +810,6 @@
mHotwordDetectionConnection.stopPerceivingLocked();
}
- @GuardedBy("this")
public void startListeningFromMicLocked(
AudioFormat audioFormat,
IMicrophoneHotwordDetectionVoiceInteractionCallback callback) {
@@ -854,7 +825,6 @@
mHotwordDetectionConnection.startListeningFromMicLocked(audioFormat, callback);
}
- @GuardedBy("this")
public void startListeningFromExternalSourceLocked(
ParcelFileDescriptor audioStream,
AudioFormat audioFormat,
@@ -879,7 +849,6 @@
options, token, callback);
}
- @GuardedBy("this")
public void stopListeningFromMicLocked() {
if (DEBUG) {
Slog.d(TAG, "stopListeningFromMicLocked");
@@ -893,7 +862,6 @@
mHotwordDetectionConnection.stopListeningFromMicLocked();
}
- @GuardedBy("this")
public void triggerHardwareRecognitionEventForTestLocked(
SoundTrigger.KeyphraseRecognitionEvent event,
IHotwordRecognitionStatusCallback callback) {
@@ -908,7 +876,6 @@
mHotwordDetectionConnection.triggerHardwareRecognitionEventForTestLocked(event, callback);
}
- @GuardedBy("this")
public IRecognitionStatusCallback createSoundTriggerCallbackLocked(
IHotwordRecognitionStatusCallback callback) {
if (DEBUG) {
@@ -933,12 +900,11 @@
return null;
}
- @GuardedBy("this")
boolean isIsolatedProcessLocked(@NonNull ServiceInfo serviceInfo) {
return (serviceInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0
&& (serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) == 0;
}
- @GuardedBy("this")
+
boolean verifyProcessSharingLocked() {
// only check this if both VQDS and HDS are declared in the app
ServiceInfo hotwordInfo = getServiceInfoLocked(mHotwordDetectionComponentName, mUser);
@@ -960,7 +926,6 @@
mHotwordDetectionConnection.forceRestart();
}
- @GuardedBy("this")
void setDebugHotwordLoggingLocked(boolean logging) {
if (mHotwordDetectionConnection == null) {
Slog.w(TAG, "Failed to set temporary debug logging: no hotword detection active");
@@ -969,7 +934,6 @@
mHotwordDetectionConnection.setDebugHotwordLoggingLocked(logging);
}
- @GuardedBy("this")
void resetHotwordDetectionConnectionLocked() {
if (DEBUG) {
Slog.d(TAG, "resetHotwordDetectionConnectionLocked");
@@ -984,7 +948,6 @@
mHotwordDetectionConnection = null;
}
- @GuardedBy("this")
public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!mValid) {
pw.print(" NOT VALID: ");
@@ -1023,7 +986,6 @@
}
}
- @GuardedBy("this")
void startLocked() {
Intent intent = new Intent(VoiceInteractionService.SERVICE_INTERFACE);
intent.setComponent(mComponent);
@@ -1048,7 +1010,6 @@
}
}
- @GuardedBy("this")
void shutdownLocked() {
// If there is an active session, cancel it to allow it to clean up its window and other
// state.
@@ -1076,7 +1037,6 @@
}
}
- @GuardedBy("this")
void notifySoundModelsChangedLocked() {
if (mService == null) {
Slog.w(TAG, "Not bound to voice interaction service " + mComponent);
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index e39af5a..9dd2a61 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -2682,71 +2682,76 @@
}
/**
- * Reports a new call with the specified {@link CallAttributes} to the telecom service. This
- * method can be used to report both incoming and outgoing calls. By reporting the call, the
- * system is aware of the call and can provide updates on services (ex. Another device wants to
- * disconnect the call) or events (ex. a new Bluetooth route became available).
- *
+ * Add a call to the Android system service Telecom. This allows the system to start tracking an
+ * incoming or outgoing call with the specified {@link CallAttributes}. Once the call is ready
+ * to be disconnected, use the {@link CallControl#disconnect(DisconnectCause, Executor,
+ * OutcomeReceiver)} which is provided by the {@code pendingControl#onResult(CallControl)}.
* <p>
- * The difference between this API call and {@link TelecomManager#placeCall(Uri, Bundle)} or
- * {@link TelecomManager#addNewIncomingCall(PhoneAccountHandle, Bundle)} is that this API
- * will asynchronously provide an update on whether the new call was added successfully via
- * an {@link OutcomeReceiver}. Additionally, callbacks will run on the executor thread that was
- * passed in.
- *
* <p>
- * Note: Only packages that register with
+ * <p>
+ * <b>Call Lifecycle</b>: Your app is given foreground execution priority as long as you have a
+ * valid call and are posting a {@link android.app.Notification.CallStyle} notification.
+ * When your application is given foreground execution priority, your app is treated as a
+ * foreground service. Foreground execution priority will prevent the
+ * {@link android.app.ActivityManager} from killing your application when it is placed the
+ * background. Foreground execution priority is removed from your app when all of your app's
+ * calls terminate or your app no longer posts a valid notification.
+ * <p>
+ * <p>
+ * <p>
+ * <b>Note</b>: Only packages that register with
* {@link PhoneAccount#CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS}
* can utilize this API. {@link PhoneAccount}s that set the capabilities
* {@link PhoneAccount#CAPABILITY_SIM_SUBSCRIPTION},
* {@link PhoneAccount#CAPABILITY_CALL_PROVIDER},
* {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}
* are not supported and will cause an exception to be thrown.
- *
* <p>
- * Usage example:
+ * <p>
+ * <p>
+ * <b>Usage example:</b>
* <pre>
- *
- * // An app should first define their own construct of a Call that overrides all the
- * // {@link CallControlCallback}s and {@link CallEventCallback}s
- * private class MyVoipCall {
- * public String callId = "";
- *
- * public CallControlCallEventCallback handshakes = new
- * CallControlCallEventCallback() {
- * // override/ implement all {@link CallControlCallback}s
+ * // Its up to your app on how you want to wrap the objects. One such implementation can be:
+ * class MyVoipCall {
+ * ...
+ * public CallControlCallEventCallback handshakes = new CallControlCallback() {
+ * ...
* }
- * public CallEventCallback events = new
- * CallEventCallback() {
- * // override/ implement all {@link CallEventCallback}s
+ *
+ * public CallEventCallback events = new CallEventCallback() {
+ * ...
* }
- * public MyVoipCall(String id){
- * callId = id;
+ *
+ * public MyVoipCall(String id){
+ * ...
+ * }
* }
*
- * PhoneAccountHandle handle = new PhoneAccountHandle(
- * new ComponentName("com.example.voip.app",
- * "com.example.voip.app.NewCallActivity"), "123");
- *
- * CallAttributes callAttributes = new CallAttributes.Builder(handle,
- * CallAttributes.DIRECTION_OUTGOING,
- * "John Smith", Uri.fromParts("tel", "123", null))
- * .build();
- *
* MyVoipCall myFirstOutgoingCall = new MyVoipCall("1");
*
- * telecomManager.addCall(callAttributes, Runnable::run, new OutcomeReceiver() {
+ * telecomManager.addCall(callAttributes,
+ * Runnable::run,
+ * new OutcomeReceiver() {
* public void onResult(CallControl callControl) {
- * // The call has been added successfully
+ * // The call has been added successfully. For demonstration
+ * // purposes, the call is disconnected immediately ...
+ * callControl.disconnect(
+ * new DisconnectCause(DisconnectCause.LOCAL) )
* }
- * }, myFirstOutgoingCall.handshakes, myFirstOutgoingCall.events);
+ * },
+ * myFirstOutgoingCall.handshakes,
+ * myFirstOutgoingCall.events);
* </pre>
*
- * @param callAttributes attributes of the new call (incoming or outgoing, address, etc. )
- * @param executor thread to run background CallEventCallback updates on
- * @param pendingControl OutcomeReceiver that receives the result of addCall transaction
- * @param handshakes object that overrides {@link CallControlCallback}s
- * @param events object that overrides {@link CallEventCallback}s
+ * @param callAttributes attributes of the new call (incoming or outgoing, address, etc.)
+ * @param executor execution context to run {@link CallControlCallback} updates on
+ * @param pendingControl Receives the result of addCall transaction. Upon success, a
+ * CallControl object is provided which can be used to do things like
+ * disconnect the call that was added.
+ * @param handshakes callback that receives <b>actionable</b> updates that originate from
+ * Telecom.
+ * @param events callback that receives <b>non</b>-actionable updates that originate
+ * from Telecom.
*/
@RequiresPermission(android.Manifest.permission.MANAGE_OWN_CALLS)
@SuppressLint("SamShouldBeLast")
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index cbdf38ae..ee9d6c1 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2989,4 +2989,14 @@
* {@code false} otherwise.
*/
boolean setSatelliteServicePackageName(in String servicePackageName);
+
+ /**
+ * This API can be used by only CTS to update the timeout duration in milliseconds that
+ * satellite should stay at listening mode to wait for the next incoming page before disabling
+ * listening mode.
+ *
+ * @param timeoutMillis The timeout duration in millisecond.
+ * @return {@code true} if the timeout duration is set successfully, {@code false} otherwise.
+ */
+ boolean setSatelliteListeningTimeoutDuration(in long timeoutMillis);
}
diff --git a/tests/SilkFX/res/drawable-nodpi/dark_gradient.xml b/tests/SilkFX/res/drawable-nodpi/dark_gradient.xml
index d2653d0..f20dd42 100644
--- a/tests/SilkFX/res/drawable-nodpi/dark_gradient.xml
+++ b/tests/SilkFX/res/drawable-nodpi/dark_gradient.xml
@@ -18,6 +18,6 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:startColor="#000000"
- android:endColor="#181818"
+ android:endColor="#222222"
android:angle="0"/>
</shape>
\ No newline at end of file
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
index 25fbabc..166fbdd 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
@@ -163,11 +163,11 @@
/**
* Sets the displayed connection strength of the remote device to the internet.
*
- * @param connectionStrength Connection strength in range 0 to 3.
+ * @param connectionStrength Connection strength in range 0 to 4.
* @return Returns the Builder object.
*/
@NonNull
- public Builder setConnectionStrength(@IntRange(from = 0, to = 3) int connectionStrength) {
+ public Builder setConnectionStrength(@IntRange(from = 0, to = 4) int connectionStrength) {
mConnectionStrength = connectionStrength;
return this;
}
@@ -205,8 +205,8 @@
if (batteryPercentage < 0 || batteryPercentage > 100) {
throw new IllegalArgumentException("BatteryPercentage must be in range 0-100");
}
- if (connectionStrength < 0 || connectionStrength > 3) {
- throw new IllegalArgumentException("ConnectionStrength must be in range 0-3");
+ if (connectionStrength < 0 || connectionStrength > 4) {
+ throw new IllegalArgumentException("ConnectionStrength must be in range 0-4");
}
}
@@ -265,9 +265,9 @@
/**
* Gets the displayed connection strength of the remote device to the internet.
*
- * @return Returns the connection strength in range 0 to 3.
+ * @return Returns the connection strength in range 0 to 4.
*/
- @IntRange(from = 0, to = 3)
+ @IntRange(from = 0, to = 4)
public int getConnectionStrength() {
return mConnectionStrength;
}
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
index e5ef62b..feef049 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
@@ -35,6 +35,7 @@
import android.os.IBinder;
import android.os.IInterface;
import android.os.RemoteException;
+import android.text.TextUtils;
import android.util.Log;
import com.android.internal.R;
@@ -173,10 +174,15 @@
R.string.config_sharedConnectivityServicePackage);
String serviceIntentAction = resources.getString(
R.string.config_sharedConnectivityServiceIntentAction);
+ if (TextUtils.isEmpty(servicePackageName) || TextUtils.isEmpty(serviceIntentAction)) {
+ Log.e(TAG, "To support shared connectivity service on this device, the"
+ + " service's package name and intent action strings must not be empty");
+ return null;
+ }
return new SharedConnectivityManager(context, servicePackageName, serviceIntentAction);
} catch (Resources.NotFoundException e) {
Log.e(TAG, "To support shared connectivity service on this device, the service's"
- + " package name and intent action string must be defined");
+ + " package name and intent action strings must be defined");
}
return null;
}
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
index b585bd5..a03a6c2 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
@@ -105,6 +105,13 @@
}
@Test
+ public void resourceStringsAreEmpty_createShouldReturnNull() {
+ when(mResources.getString(anyInt())).thenReturn("");
+
+ assertThat(SharedConnectivityManager.create(mContext)).isNull();
+ }
+
+ @Test
public void bindingToServiceOnFirstCallbackRegistration() {
SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
manager.registerCallback(mExecutor, mClientCallback);