Merge "Log ACCOUNT_MANAGER_EVENT__EVENT_TYPE__AUTHENTICATOR_ADDED" into main
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 1fdb698..c8a15e0 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -4637,7 +4637,7 @@
@FlaggedApi("android.content.pm.verification_service") public final class VerificationSession implements android.os.Parcelable {
method public int describeContents();
- method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public long extendTimeRemaining(long);
+ method public long extendTimeRemaining(long);
method @NonNull public java.util.List<android.content.pm.SharedLibraryInfo> getDeclaredLibraries();
method @NonNull public android.os.PersistableBundle getExtensionParams();
method public int getId();
@@ -4645,12 +4645,12 @@
method @NonNull public String getPackageName();
method @NonNull public android.content.pm.SigningInfo getSigningInfo();
method @NonNull public android.net.Uri getStagedPackageUri();
- method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public long getTimeoutTime();
+ method public long getTimeoutTime();
method public int getVerificationPolicy();
- method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public void reportVerificationComplete(@NonNull android.content.pm.verify.pkg.VerificationStatus);
- method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public void reportVerificationComplete(@NonNull android.content.pm.verify.pkg.VerificationStatus, @NonNull android.os.PersistableBundle);
- method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public void reportVerificationIncomplete(int);
- method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public boolean setVerificationPolicy(int);
+ method public void reportVerificationComplete(@NonNull android.content.pm.verify.pkg.VerificationStatus);
+ method public void reportVerificationComplete(@NonNull android.content.pm.verify.pkg.VerificationStatus, @NonNull android.os.PersistableBundle);
+ method public void reportVerificationIncomplete(int);
+ method public boolean setVerificationPolicy(int);
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.pkg.VerificationSession> CREATOR;
field public static final int VERIFICATION_INCOMPLETE_NETWORK_UNAVAILABLE = 1; // 0x1
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index b0a8b1b..5225174 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -7681,7 +7681,7 @@
});
// Register callback to report native memory metrics post GC cleanup
- if (Flags.reportPostgcMemoryMetrics() &&
+ if (Flags.reportPostgcMemoryMetricsReadonly() &&
com.android.libcore.readonly.Flags.postCleanupApis()) {
VMRuntime.addPostCleanupCallback(new Runnable() {
@Override public void run() {
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 2e3d226..56538d9 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -8755,21 +8755,9 @@
* Do a quick check for whether an application might be able to perform an operation.
* This is <em>not</em> a security check; you must use {@link #noteOp(String, int, String,
* String, String)} or {@link #startOp(String, int, String, String, String)} for your actual
- * security checks, which also ensure that the given uid and package name are consistent. This
- * function can just be used for a quick check to see if an operation has been disabled for the
- * application, as an early reject of some work. This does not modify the time stamp or other
- * data about the operation.
- *
- * <p>Important things this will not do (which you need to ultimate use
- * {@link #noteOp(String, int, String, String, String)} or
- * {@link #startOp(String, int, String, String, String)} to cover):</p>
- * <ul>
- * <li>Verifying the uid and package are consistent, so callers can't spoof
- * their identity.</li>
- * <li>Taking into account the current foreground/background state of the
- * app; apps whose mode varies by this state will always be reported
- * as {@link #MODE_ALLOWED}.</li>
- * </ul>
+ * security checks. This function can just be used for a quick check to see if an operation has
+ * been disabled for the application, as an early reject of some work. This does not modify the
+ * time stamp or other data about the operation.
*
* @param op The operation to check. One of the OPSTR_* constants.
* @param uid The user id of the application attempting to perform the operation.
diff --git a/core/java/android/app/appfunctions/AppFunctionManager.java b/core/java/android/app/appfunctions/AppFunctionManager.java
index dca4336..a82c6ba7 100644
--- a/core/java/android/app/appfunctions/AppFunctionManager.java
+++ b/core/java/android/app/appfunctions/AppFunctionManager.java
@@ -47,7 +47,6 @@
* <p>App function is a specific piece of functionality that an app offers to the system. These
* functionalities can be integrated into various system features.
*/
-// TODO(b/357551503): Implement get and set enabled app function APIs.
@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
@SystemService(Context.APP_FUNCTION_SERVICE)
public final class AppFunctionManager {
@@ -111,17 +110,19 @@
* @param request the request to execute the app function
* @param executor the executor to run the callback
* @param cancellationSignal the cancellation signal to cancel the execution.
- * @param callback the callback to receive the function execution result. if the calling app
- * does not own the app function or does not have {@code
+ * @param callback the callback to receive the function execution result.
+ * <p>If the calling app does not own the app function or does not have {@code
* android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} or {@code
* android.permission.EXECUTE_APP_FUNCTIONS}, the execution result will contain {@code
* ExecuteAppFunctionResponse.RESULT_DENIED}.
+ * <p>If the caller only has {@code android.permission.EXECUTE_APP_FUNCTIONS} but the
+ * function requires {@code android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED}, the execution
+ * result will contain {@code ExecuteAppFunctionResponse.RESULT_DENIED}
+ * <p>If the function requested for execution is disabled, then the execution result will
+ * contain {@code ExecuteAppFunctionResponse.RESULT_DISABLED}
+ * <p>If the cancellation signal is issued, the operation is cancelled and no response is
+ * returned to the caller.
*/
- // TODO(b/357551503): Document the behavior when the cancellation signal is issued.
- // TODO(b/360864791): Document that apps can opt-out from being executed by callers with
- // EXECUTE_APP_FUNCTIONS and how a caller knows whether a function is opted out.
- // TODO(b/357551503): Update documentation when get / set APIs are implemented that this will
- // also return RESULT_DENIED if the app function is disabled.
@RequiresPermission(
anyOf = {
Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java
index fe7fd88..4c5e8c1 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java
@@ -50,12 +50,12 @@
}
};
- /** Returns the package name of the app that hosts the function. */
+ /** Returns the package name of the app that hosts/owns the function. */
@NonNull private final String mTargetPackageName;
/**
- * Returns the unique string identifier of the app function to be executed. TODO(b/357551503):
- * Document how callers can get the available function identifiers.
+ * The unique string identifier of the app function to be executed. This identifier is used to
+ * execute a specific app function.
*/
@NonNull private final String mFunctionIdentifier;
@@ -69,8 +69,6 @@
*
* <p>The document may have missing parameters. Developers are advised to implement defensive
* handling measures.
- *
- * <p>TODO(b/357551503): Document how function parameters can be obtained for function execution
*/
@NonNull private final GenericDocumentWrapper mParameters;
@@ -91,7 +89,19 @@
return mTargetPackageName;
}
- /** Returns the unique string identifier of the app function to be executed. */
+ /**
+ * Returns the unique string identifier of the app function to be executed.
+ *
+ * <p>When there is a package change or the device starts up, the metadata of available
+ * functions is indexed by AppSearch. AppSearch stores the indexed information as {@code
+ * AppFunctionStaticMetadata} document.
+ *
+ * <p>The ID can be obtained by querying the {@code AppFunctionStaticMetadata} documents from
+ * AppSearch.
+ *
+ * <p>If the {@code functionId} provided is invalid, the caller will get an invalid argument
+ * response.
+ */
@NonNull
public String getFunctionIdentifier() {
return mFunctionIdentifier;
@@ -103,6 +113,12 @@
*
* <p>The bundle may have missing parameters. Developers are advised to implement defensive
* handling measures.
+ *
+ * <p>Similar to {@link #getFunctionIdentifier()} the parameters required by a function can be
+ * obtained by querying AppSearch for the corresponding {@code AppFunctionStaticMetadata}. This
+ * metadata will contain enough information for the caller to resolve the required parameters
+ * either using information from the metadata itself or using the AppFunction SDK for function
+ * callers.
*/
@NonNull
public GenericDocument getParameters() {
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
index a879b1b..c907ef1 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
@@ -79,14 +79,15 @@
/** The caller does not have the permission to execute an app function. */
public static final int RESULT_DENIED = 1;
- /** An unknown error occurred while processing the call in the AppFunctionService. */
+ /**
+ * An unknown error occurred while processing the call in the AppFunctionService.
+ *
+ * <p>This error is thrown when the service is connected in the remote application but an
+ * unexpected error is thrown from the bound application.
+ */
public static final int RESULT_APP_UNKNOWN_ERROR = 2;
- /**
- * An internal error occurred within AppFunctionManagerService.
- *
- * <p>This error may be considered similar to {@link IllegalStateException}
- */
+ /** An internal unexpected error coming from the system. */
public static final int RESULT_INTERNAL_ERROR = 3;
/**
diff --git a/core/java/android/app/metrics.aconfig b/core/java/android/app/metrics.aconfig
index 488f1c7..55d9c2d 100644
--- a/core/java/android/app/metrics.aconfig
+++ b/core/java/android/app/metrics.aconfig
@@ -8,3 +8,12 @@
description: "Controls whether to report memory metrics post GC cleanup"
bug: "331243037"
}
+
+flag {
+ namespace: "system_performance"
+ name: "report_postgc_memory_metrics_readonly"
+ is_exported: false
+ description: "Controls whether to report memory metrics post GC cleanup (readonly)"
+ bug: "331243037"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index 933c336..df1028e 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -716,8 +716,8 @@
mCurrentSize);
} else {
applyContent(null, false, e);
+ mLastExecutionSignal = null;
}
- mLastExecutionSignal = null;
}
}
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index c911326..ecea479 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -94,9 +94,9 @@
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES,android.Manifest.permission.REQUEST_INSTALL_PACKAGES})")
void reportUnarchivalStatus(int unarchiveId, int status, long requiredStorageBytes, in PendingIntent userActionIntent, in UserHandle userHandle);
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
+ @EnforcePermission("VERIFICATION_AGENT")
int getVerificationPolicy();
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
+ @EnforcePermission("VERIFICATION_AGENT")
boolean setVerificationPolicy(int policy);
}
diff --git a/core/java/android/content/pm/verify/pkg/IVerificationSessionInterface.aidl b/core/java/android/content/pm/verify/pkg/IVerificationSessionInterface.aidl
index 66caf2d..2ab7452 100644
--- a/core/java/android/content/pm/verify/pkg/IVerificationSessionInterface.aidl
+++ b/core/java/android/content/pm/verify/pkg/IVerificationSessionInterface.aidl
@@ -24,16 +24,9 @@
* @hide
*/
interface IVerificationSessionInterface {
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
long getTimeoutTime(int verificationId);
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
long extendTimeRemaining(int verificationId, long additionalMs);
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
boolean setVerificationPolicy(int verificationId, int policy);
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
void reportVerificationIncomplete(int verificationId, int reason);
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
- void reportVerificationComplete(int verificationId, in VerificationStatus status);
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
- void reportVerificationCompleteWithExtensionResponse(int verificationId, in VerificationStatus status, in PersistableBundle response);
+ void reportVerificationComplete(int verificationId, in VerificationStatus status, in @nullable PersistableBundle extensionResponse);
}
\ No newline at end of file
diff --git a/core/java/android/content/pm/verify/pkg/VerificationSession.java b/core/java/android/content/pm/verify/pkg/VerificationSession.java
index 4ade211..97f78e0 100644
--- a/core/java/android/content/pm/verify/pkg/VerificationSession.java
+++ b/core/java/android/content/pm/verify/pkg/VerificationSession.java
@@ -19,7 +19,6 @@
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.content.pm.Flags;
import android.content.pm.PackageInstaller;
@@ -166,8 +165,8 @@
/**
* Get the value of Clock.elapsedRealtime() at which time this verification
* will timeout as incomplete if no other verification response is provided.
+ * @throws SecurityException if the caller is not the current verifier bound by the system.
*/
- @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)
public long getTimeoutTime() {
try {
return mSession.getTimeoutTime(mId);
@@ -190,8 +189,8 @@
/**
* Override the verification policy for this session.
* @return True if the override was successful, False otherwise.
+ * @throws SecurityException if the caller is not the current verifier bound by the system.
*/
- @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)
public boolean setVerificationPolicy(@PackageInstaller.VerificationPolicy int policy) {
if (mVerificationPolicy == policy) {
// No effective policy change
@@ -215,8 +214,8 @@
* This may be called multiple times. If the request would bypass any max
* duration by the system, the method will return a lower value than the
* requested amount that indicates how much the time was extended.
+ * @throws SecurityException if the caller is not the current verifier bound by the system.
*/
- @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)
public long extendTimeRemaining(long additionalMs) {
try {
return mSession.extendTimeRemaining(mId, additionalMs);
@@ -227,9 +226,9 @@
/**
* Report to the system that verification could not be completed along
- * with an approximate reason to pass on to the installer.
+ * with an approximate reason to pass on to the installer.]
+ * @throws SecurityException if the caller is not the current verifier bound by the system.
*/
- @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)
public void reportVerificationIncomplete(@VerificationIncompleteReason int reason) {
try {
mSession.reportVerificationIncomplete(mId, reason);
@@ -242,11 +241,11 @@
* Report to the system that the verification has completed and the
* install process may act on that status to either block in the case
* of failure or continue to process the install in the case of success.
+ * @throws SecurityException if the caller is not the current verifier bound by the system.
*/
- @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)
public void reportVerificationComplete(@NonNull VerificationStatus status) {
try {
- mSession.reportVerificationComplete(mId, status);
+ mSession.reportVerificationComplete(mId, status, /* extensionResponse= */ null);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -256,12 +255,12 @@
* Same as {@link #reportVerificationComplete(VerificationStatus)}, but also provide
* a result to the extension params provided in the request, which will be passed to the
* installer in the installation result.
+ * @throws SecurityException if the caller is not the current verifier bound by the system.
*/
- @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)
public void reportVerificationComplete(@NonNull VerificationStatus status,
- @NonNull PersistableBundle response) {
+ @NonNull PersistableBundle extensionResponse) {
try {
- mSession.reportVerificationCompleteWithExtensionResponse(mId, status, response);
+ mSession.reportVerificationComplete(mId, status, extensionResponse);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java
index e68c4ca..6325b00 100644
--- a/core/java/android/os/BatteryUsageStatsQuery.java
+++ b/core/java/android/os/BatteryUsageStatsQuery.java
@@ -370,7 +370,10 @@
* and most battery stats resets.
*/
public Builder accumulated() {
- mFlags |= FLAG_BATTERY_USAGE_STATS_ACCUMULATED;
+ mFlags |= FLAG_BATTERY_USAGE_STATS_ACCUMULATED
+ | FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_STATE
+ | FLAG_BATTERY_USAGE_STATS_INCLUDE_SCREEN_STATE
+ | FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA;
return this;
}
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 25f3f96..1d654e13 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -299,3 +299,11 @@
description: "Allow host device permission dialogs (i.e., dialogs for non device-aware permissions) to be shown on virtual devices"
bug: "371173672"
}
+
+flag {
+ name: "appop_mode_caching_enabled"
+ is_fixed_read_only: true
+ namespace: "permissions"
+ description: "Enable AppOp mode caching in AppOpsManager"
+ bug: "366013082"
+}
diff --git a/core/java/android/security/forensic/ForensicEvent.java b/core/java/android/security/forensic/ForensicEvent.java
index 9cbc5ec..90906ed 100644
--- a/core/java/android/security/forensic/ForensicEvent.java
+++ b/core/java/android/security/forensic/ForensicEvent.java
@@ -61,6 +61,14 @@
in.readMap(mKeyValuePairs, getClass().getClassLoader(), String.class, String.class);
}
+ public String getType() {
+ return mType;
+ }
+
+ public Map<String, String> getKeyValuePairs() {
+ return mKeyValuePairs;
+ }
+
@Override
public void writeToParcel(@NonNull Parcel out, int flags) {
out.writeString(mType);
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index e6de478..94f415b 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -412,41 +412,28 @@
*/
public static class JankData {
- /** @hide */
- @IntDef(flag = true, value = {JANK_NONE,
- DISPLAY_HAL,
- JANK_SURFACEFLINGER_DEADLINE_MISSED,
- JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED,
- JANK_APP_DEADLINE_MISSED,
- PREDICTION_ERROR,
- SURFACE_FLINGER_SCHEDULING})
+ /**
+ * Needs to be kept in sync with android_view_SurfaceControl.cpp's
+ * JankDataListenerWrapper::onJankDataAvailable.
+ * @hide
+ */
+ @IntDef(flag = true, value = {
+ JANK_NONE,
+ JANK_COMPOSER,
+ JANK_APPLICATION,
+ JANK_OTHER,
+ })
@Retention(RetentionPolicy.SOURCE)
public @interface JankType {}
- // Needs to be kept in sync with frameworks/native/libs/gui/include/gui/JankInfo.h
-
// No Jank
- public static final int JANK_NONE = 0x0;
-
- // Jank not related to SurfaceFlinger or the App
- public static final int DISPLAY_HAL = 0x1;
- // SF took too long on the CPU
- public static final int JANK_SURFACEFLINGER_DEADLINE_MISSED = 0x2;
- // SF took too long on the GPU
- public static final int JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED = 0x4;
- // Either App or GPU took too long on the frame
- public static final int JANK_APP_DEADLINE_MISSED = 0x8;
- // Vsync predictions have drifted beyond the threshold from the actual HWVsync
- public static final int PREDICTION_ERROR = 0x10;
- // Latching a buffer early might cause an early present of the frame
- public static final int SURFACE_FLINGER_SCHEDULING = 0x20;
- // A buffer is said to be stuffed if it was expected to be presented on a vsync but was
- // presented later because the previous buffer was presented in its expected vsync. This
- // usually happens if there is an unexpectedly long frame causing the rest of the buffers
- // to enter a stuffed state.
- public static final int BUFFER_STUFFING = 0x40;
- // Jank due to unknown reasons.
- public static final int UNKNOWN = 0x80;
+ public static final int JANK_NONE = 0;
+ // Jank caused by the composer missing a deadline
+ public static final int JANK_COMPOSER = 1 << 0;
+ // Jank caused by the application missing the composer's deadline
+ public static final int JANK_APPLICATION = 1 << 1;
+ // Jank due to other unknown reasons
+ public static final int JANK_OTHER = 1 << 2;
public JankData(long frameVsyncId, @JankType int jankType, long frameIntervalNs,
long scheduledAppFrameTimeNs, long actualAppFrameTimeNs) {
diff --git a/core/java/android/window/flags/responsible_apis.aconfig b/core/java/android/window/flags/responsible_apis.aconfig
index cd31850..57aacff 100644
--- a/core/java/android/window/flags/responsible_apis.aconfig
+++ b/core/java/android/window/flags/responsible_apis.aconfig
@@ -72,6 +72,13 @@
}
flag {
+ name: "bal_strict_mode"
+ namespace: "responsible_apis"
+ description: "Strict mode flag"
+ bug: "324089586"
+}
+
+flag {
name: "bal_reduce_grace_period"
namespace: "responsible_apis"
description: "Changes to reduce or ideally remove the grace period exemption."
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index 003393c..0af4bea 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -16,13 +16,9 @@
package com.android.internal.jank;
-import static android.view.SurfaceControl.JankData.DISPLAY_HAL;
-import static android.view.SurfaceControl.JankData.JANK_APP_DEADLINE_MISSED;
+import static android.view.SurfaceControl.JankData.JANK_APPLICATION;
+import static android.view.SurfaceControl.JankData.JANK_COMPOSER;
import static android.view.SurfaceControl.JankData.JANK_NONE;
-import static android.view.SurfaceControl.JankData.JANK_SURFACEFLINGER_DEADLINE_MISSED;
-import static android.view.SurfaceControl.JankData.JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED;
-import static android.view.SurfaceControl.JankData.PREDICTION_ERROR;
-import static android.view.SurfaceControl.JankData.SURFACE_FLINGER_SCHEDULING;
import static com.android.internal.jank.DisplayRefreshRate.UNKNOWN_REFRESH_RATE;
import static com.android.internal.jank.DisplayRefreshRate.VARIABLE_REFRESH_RATE;
@@ -181,23 +177,11 @@
case JANK_NONE:
str.append("JANK_NONE");
break;
- case JANK_APP_DEADLINE_MISSED:
- str.append("JANK_APP_DEADLINE_MISSED");
+ case JANK_APPLICATION:
+ str.append("JANK_APPLICATION");
break;
- case JANK_SURFACEFLINGER_DEADLINE_MISSED:
- str.append("JANK_SURFACEFLINGER_DEADLINE_MISSED");
- break;
- case JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED:
- str.append("JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED");
- break;
- case DISPLAY_HAL:
- str.append("DISPLAY_HAL");
- break;
- case PREDICTION_ERROR:
- str.append("PREDICTION_ERROR");
- break;
- case SURFACE_FLINGER_SCHEDULING:
- str.append("SURFACE_FLINGER_SCHEDULING");
+ case JANK_COMPOSER:
+ str.append("JANK_COMPOSER");
break;
default:
str.append("UNKNOWN: ").append(jankType);
@@ -628,16 +612,12 @@
if (info.surfaceControlCallbackFired) {
totalFramesCount++;
boolean missedFrame = false;
- if ((info.jankType & JANK_APP_DEADLINE_MISSED) != 0) {
+ if ((info.jankType & JANK_APPLICATION) != 0) {
Log.w(TAG, "Missed App frame:" + info + ", CUJ=" + name);
missedAppFramesCount++;
missedFrame = true;
}
- if ((info.jankType & DISPLAY_HAL) != 0
- || (info.jankType & JANK_SURFACEFLINGER_DEADLINE_MISSED) != 0
- || (info.jankType & JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED) != 0
- || (info.jankType & SURFACE_FLINGER_SCHEDULING) != 0
- || (info.jankType & PREDICTION_ERROR) != 0) {
+ if ((info.jankType & JANK_COMPOSER) != 0) {
Log.w(TAG, "Missed SF frame:" + info + ", CUJ=" + name);
missedSfFramesCount++;
missedFrame = true;
diff --git a/core/java/com/android/internal/os/flags.aconfig b/core/java/com/android/internal/os/flags.aconfig
index 07df248..25a9fbc 100644
--- a/core/java/com/android/internal/os/flags.aconfig
+++ b/core/java/com/android/internal/os/flags.aconfig
@@ -2,6 +2,48 @@
container: "system"
flag {
+ namespace: "ravenwood"
+ name: "ravenwood_flag_rw_1"
+ description: "Ravenwood test RW flag 1"
+ bug: "311370221"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ namespace: "ravenwood"
+ name: "ravenwood_flag_rw_2"
+ description: "Ravenwood test RW flag 2"
+ bug: "311370221"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ namespace: "ravenwood"
+ name: "ravenwood_flag_ro_1"
+ description: "Ravenwood test RO flag 1"
+ is_fixed_read_only: true
+ bug: "311370221"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ namespace: "ravenwood"
+ name: "ravenwood_flag_ro_2"
+ description: "Ravenwood test RO flag 2"
+ is_fixed_read_only: true
+ bug: "311370221"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "enable_apache_http_legacy_preload"
namespace: "system_performance"
description: "Enables zygote preload of non-BCP org.apache.http.legacy.jar library."
diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp
index 10e49ef..50252c1 100644
--- a/core/jni/android_hardware_Camera.cpp
+++ b/core/jni/android_hardware_Camera.cpp
@@ -27,6 +27,7 @@
#include <camera/StringUtils.h>
#include <com_android_internal_camera_flags.h>
#include <cutils/properties.h>
+#include <gui/Flags.h>
#include <gui/GLConsumer.h>
#include <gui/Surface.h>
#include <nativehelper/JNIHelp.h>
@@ -715,16 +716,20 @@
sp<Camera> camera = get_native_camera(env, thiz, NULL);
if (camera == 0) return;
- sp<IGraphicBufferProducer> gbp;
sp<Surface> surface;
if (jSurface) {
surface = android_view_Surface_getSurface(env, jSurface);
- if (surface != NULL) {
- gbp = surface->getIGraphicBufferProducer();
- }
}
+#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
+ if (camera->setPreviewTarget(surface) != NO_ERROR) {
+#else
+ sp<IGraphicBufferProducer> gbp;
+ if (surface != NULL) {
+ gbp = surface->getIGraphicBufferProducer();
+ }
if (camera->setPreviewTarget(gbp) != NO_ERROR) {
+#endif
jniThrowException(env, "java/io/IOException", "setPreviewTexture failed");
}
}
@@ -736,6 +741,9 @@
sp<Camera> camera = get_native_camera(env, thiz, NULL);
if (camera == 0) return;
+#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
+ sp<Surface> surface;
+#endif
sp<IGraphicBufferProducer> producer = NULL;
if (jSurfaceTexture != NULL) {
producer = SurfaceTexture_getProducer(env, jSurfaceTexture);
@@ -744,10 +752,16 @@
"SurfaceTexture already released in setPreviewTexture");
return;
}
-
+#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
+ surface = new Surface(producer);
+#endif
}
+#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
+ if (camera->setPreviewTarget(surface) != NO_ERROR) {
+#else
if (camera->setPreviewTarget(producer) != NO_ERROR) {
+#endif
jniThrowException(env, "java/io/IOException",
"setPreviewTexture failed");
}
@@ -761,18 +775,32 @@
sp<Camera> camera = get_native_camera(env, thiz, &context);
if (camera == 0) return;
+#if !WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
sp<IGraphicBufferProducer> gbp;
+#endif
sp<Surface> surface;
if (jSurface) {
surface = android_view_Surface_getSurface(env, jSurface);
+ if (surface == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ "android_view_Surface_getSurface failed");
+ return;
+ }
+
+#if !WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
if (surface != NULL) {
gbp = surface->getIGraphicBufferProducer();
}
+#endif
}
// Clear out normal preview callbacks
context->setCallbackMode(env, false, false);
// Then set up callback surface
+#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
+ if (camera->setPreviewCallbackTarget(surface) != NO_ERROR) {
+#else
if (camera->setPreviewCallbackTarget(gbp) != NO_ERROR) {
+#endif
jniThrowException(env, "java/io/IOException", "setPreviewCallbackTarget failed");
}
}
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 755704a..f162b74 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -35,6 +35,7 @@
#include <android_runtime/android_view_SurfaceSession.h>
#include <cutils/ashmem.h>
#include <gui/ISurfaceComposer.h>
+#include <gui/JankInfo.h>
#include <gui/Surface.h>
#include <gui/SurfaceComposerClient.h>
#include <jni.h>
@@ -2161,9 +2162,30 @@
jobjectArray jJankDataArray = env->NewObjectArray(jankData.size(),
gJankDataClassInfo.clazz, nullptr);
for (size_t i = 0; i < jankData.size(); i++) {
+ // The exposed constants in SurfaceControl are simplified, so we need to translate the
+ // jank type we get from SF to what is exposed in Java.
+ int sfJankType = jankData[i].jankType;
+ int javaJankType = 0x0; // SurfaceControl.JankData.JANK_NONE
+ if (sfJankType &
+ (JankType::DisplayHAL | JankType::SurfaceFlingerCpuDeadlineMissed |
+ JankType::SurfaceFlingerGpuDeadlineMissed | JankType::PredictionError |
+ JankType::SurfaceFlingerScheduling)) {
+ javaJankType |= 0x1; // SurfaceControl.JankData.JANK_COMPOSER
+ }
+ if (sfJankType & JankType::AppDeadlineMissed) {
+ javaJankType |= 0x2; // SurfaceControl.JankData.JANK_APPLICATION
+ }
+ if (sfJankType &
+ ~(JankType::DisplayHAL | JankType::SurfaceFlingerCpuDeadlineMissed |
+ JankType::SurfaceFlingerGpuDeadlineMissed | JankType::AppDeadlineMissed |
+ JankType::PredictionError | JankType::SurfaceFlingerScheduling |
+ JankType::BufferStuffing | JankType::SurfaceFlingerStuffing)) {
+ javaJankType |= 0x4; // SurfaceControl.JankData.JANK_OTHER
+ }
+
jobject jJankData =
env->NewObject(gJankDataClassInfo.clazz, gJankDataClassInfo.ctor,
- jankData[i].frameVsyncId, jankData[i].jankType,
+ jankData[i].frameVsyncId, javaJankType,
jankData[i].frameIntervalNs, jankData[i].scheduledAppFrameTimeNs,
jankData[i].actualAppFrameTimeNs);
env->SetObjectArrayElement(jJankDataArray, i, jJankData);
diff --git a/core/tests/coretests/src/android/content/pm/verify/VerificationSessionTest.java b/core/tests/coretests/src/android/content/pm/verify/VerificationSessionTest.java
index 90ae306..f1e1df5 100644
--- a/core/tests/coretests/src/android/content/pm/verify/VerificationSessionTest.java
+++ b/core/tests/coretests/src/android/content/pm/verify/VerificationSessionTest.java
@@ -142,10 +142,10 @@
new VerificationStatus.Builder().setVerified(true).build();
mTestSession.reportVerificationComplete(status);
verify(mTestSessionInterface, times(1)).reportVerificationComplete(
- eq(TEST_ID), eq(status));
+ eq(TEST_ID), eq(status), eq(null));
mTestSession.reportVerificationComplete(status, response);
verify(mTestSessionInterface, times(1))
- .reportVerificationCompleteWithExtensionResponse(
+ .reportVerificationComplete(
eq(TEST_ID), eq(status), eq(response));
final int reason = VerificationSession.VERIFICATION_INCOMPLETE_UNKNOWN;
diff --git a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
index 1cbc7d6..60b5a42 100644
--- a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
+++ b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
@@ -16,9 +16,9 @@
package com.android.internal.jank;
-import static android.view.SurfaceControl.JankData.JANK_APP_DEADLINE_MISSED;
+import static android.view.SurfaceControl.JankData.JANK_APPLICATION;
+import static android.view.SurfaceControl.JankData.JANK_COMPOSER;
import static android.view.SurfaceControl.JankData.JANK_NONE;
-import static android.view.SurfaceControl.JankData.JANK_SURFACEFLINGER_DEADLINE_MISSED;
import static com.android.internal.jank.FrameTracker.SurfaceControlWrapper;
import static com.android.internal.jank.FrameTracker.ViewRootWrapper;
@@ -164,7 +164,7 @@
verify(mRenderer, only()).addObserver(any());
// send first frame with a long duration - should not be taken into account
- sendFirstWindowFrame(tracker, 100, JANK_APP_DEADLINE_MISSED, 100L);
+ sendFirstWindowFrame(tracker, 100, JANK_APPLICATION, 100L);
// send another frame with a short duration - should not be considered janky
sendFrame(tracker, 5, JANK_NONE, 101L);
@@ -173,7 +173,7 @@
when(mChoreographer.getVsyncId()).thenReturn(102L);
tracker.end(FrameTracker.REASON_END_NORMAL);
sendFrame(tracker, 5, JANK_NONE, 102L);
- sendFrame(tracker, 500, JANK_APP_DEADLINE_MISSED, 103L);
+ sendFrame(tracker, 500, JANK_APPLICATION, 103L);
verify(tracker).removeObservers();
verify(mTrackerListener, never()).triggerPerfetto(any());
@@ -202,7 +202,7 @@
sendFrame(tracker, 4, JANK_NONE, 100L);
// send another frame - should be considered janky
- sendFrame(tracker, 40, JANK_SURFACEFLINGER_DEADLINE_MISSED, 101L);
+ sendFrame(tracker, 40, JANK_COMPOSER, 101L);
// end the trace session
when(mChoreographer.getVsyncId()).thenReturn(102L);
@@ -236,7 +236,7 @@
verify(mRenderer, only()).addObserver(any());
// send first frame - janky
- sendFrame(tracker, 40, JANK_APP_DEADLINE_MISSED, 100L);
+ sendFrame(tracker, 40, JANK_APPLICATION, 100L);
// send another frame - not jank
sendFrame(tracker, 4, JANK_NONE, 101L);
@@ -275,7 +275,7 @@
sendFrame(tracker, 4, JANK_NONE, 100L);
// send another frame - should be considered janky
- sendFrame(tracker, 40, JANK_APP_DEADLINE_MISSED, 101L);
+ sendFrame(tracker, 40, JANK_APPLICATION, 101L);
// end the trace session
when(mChoreographer.getVsyncId()).thenReturn(102L);
@@ -317,7 +317,7 @@
// end the trace session, simulate one more valid callback came after the end call.
when(mChoreographer.getVsyncId()).thenReturn(102L);
tracker.end(FrameTracker.REASON_END_NORMAL);
- sendFrame(tracker, 50, JANK_APP_DEADLINE_MISSED, 102L);
+ sendFrame(tracker, 50, JANK_APPLICATION, 102L);
// One more callback with VSYNC after the end() vsync id.
sendFrame(tracker, 4, JANK_NONE, 103L);
@@ -365,7 +365,7 @@
sendSfFrame(tracker, 4, 102L, JANK_NONE);
// Send janky but complete callbck fo 103L
- sendFrame(tracker, 50, JANK_APP_DEADLINE_MISSED, 103L);
+ sendFrame(tracker, 50, JANK_APPLICATION, 103L);
verify(tracker).removeObservers();
verify(mTrackerListener, never()).triggerPerfetto(any());
@@ -397,7 +397,7 @@
sendFrame(tracker, 4, JANK_NONE, 101L);
// a janky frame
- sendFrame(tracker, 50, JANK_APP_DEADLINE_MISSED, 102L);
+ sendFrame(tracker, 50, JANK_APPLICATION, 102L);
tracker.cancel(FrameTracker.REASON_CANCEL_NORMAL);
verify(tracker).removeObservers();
@@ -481,7 +481,7 @@
// normal frame - not janky
sendFrame(tracker, JANK_NONE, 101L);
// a janky frame
- sendFrame(tracker, JANK_APP_DEADLINE_MISSED, 102L);
+ sendFrame(tracker, JANK_APPLICATION, 102L);
when(mChoreographer.getVsyncId()).thenReturn(102L);
tracker.end(FrameTracker.REASON_CANCEL_NORMAL);
@@ -514,7 +514,7 @@
verify(mSurfaceControlWrapper).addJankStatsListener(any(), any());
// First frame - janky
- sendFrame(tracker, JANK_APP_DEADLINE_MISSED, 100L);
+ sendFrame(tracker, JANK_APPLICATION, 100L);
// normal frame - not janky
sendFrame(tracker, JANK_NONE, 101L);
// normal frame - not janky
@@ -561,7 +561,7 @@
tracker.end(FrameTracker.REASON_CANCEL_NORMAL);
// janky frame, should be ignored, trigger finish
- sendFrame(tracker, JANK_APP_DEADLINE_MISSED, 103L);
+ sendFrame(tracker, JANK_APPLICATION, 103L);
verify(mJankStatsRegistration).removeAfter(anyLong());
verify(mTrackerListener, never()).triggerPerfetto(any());
@@ -623,16 +623,16 @@
tracker.begin();
mRunnableArgumentCaptor.getValue().run();
verify(mSurfaceControlWrapper).addJankStatsListener(any(), any());
- sendFrame(tracker, JANK_SURFACEFLINGER_DEADLINE_MISSED, 100L);
- sendFrame(tracker, JANK_SURFACEFLINGER_DEADLINE_MISSED, 101L);
- sendFrame(tracker, JANK_APP_DEADLINE_MISSED, 102L);
+ sendFrame(tracker, JANK_COMPOSER, 100L);
+ sendFrame(tracker, JANK_COMPOSER, 101L);
+ sendFrame(tracker, JANK_APPLICATION, 102L);
sendFrame(tracker, JANK_NONE, 103L);
- sendFrame(tracker, JANK_APP_DEADLINE_MISSED, 104L);
- sendFrame(tracker, JANK_APP_DEADLINE_MISSED, 105L);
+ sendFrame(tracker, JANK_APPLICATION, 104L);
+ sendFrame(tracker, JANK_APPLICATION, 105L);
when(mChoreographer.getVsyncId()).thenReturn(106L);
tracker.end(FrameTracker.REASON_END_NORMAL);
- sendFrame(tracker, JANK_SURFACEFLINGER_DEADLINE_MISSED, 106L);
- sendFrame(tracker, JANK_SURFACEFLINGER_DEADLINE_MISSED, 107L);
+ sendFrame(tracker, JANK_COMPOSER, 106L);
+ sendFrame(tracker, JANK_COMPOSER, 107L);
verify(mJankStatsRegistration).removeAfter(anyLong());
verify(mTrackerListener).triggerPerfetto(any());
verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt
index 9fd255d..7adec39 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt
@@ -20,6 +20,7 @@
import android.graphics.Point
import android.graphics.Rect
import android.util.Log
+import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -102,14 +103,17 @@
hideEducation(animated = false)
log { "showStackEducation at: $position" }
+ val rootBounds = Rect()
+ // Get root bounds on screen as position is in screen coordinates
+ root.getBoundsOnScreen(rootBounds)
educationView =
createEducationView(R.layout.bubble_bar_stack_education, root).apply {
setArrowDirection(BubblePopupDrawable.ArrowDirection.DOWN)
- setArrowPosition(BubblePopupDrawable.ArrowPosition.End)
- updateEducationPosition(view = this, position, root)
+ updateEducationPosition(view = this, position, rootBounds)
val arrowToEdgeOffset = popupDrawable?.config?.cornerRadius ?: 0f
doOnLayout {
- it.pivotX = it.width - arrowToEdgeOffset
+ it.pivotX = if (position.x < rootBounds.centerX())
+ arrowToEdgeOffset else it.width - arrowToEdgeOffset
it.pivotY = it.height.toFloat()
}
setOnClickListener { educationClickHandler() }
@@ -218,12 +222,9 @@
*
* @param view the user education view to layout
* @param position the reference position in Screen coordinates
- * @param root the root view to use for the layout
+ * @param rootBounds bounds of the parent the education view is placed in
*/
- private fun updateEducationPosition(view: BubblePopupView, position: Point, root: ViewGroup) {
- val rootBounds = Rect()
- // Get root bounds on screen as position is in screen coordinates
- root.getBoundsOnScreen(rootBounds)
+ private fun updateEducationPosition(view: BubblePopupView, position: Point, rootBounds: Rect) {
// Get the offset to the arrow from the edge of the education view
val arrowToEdgeOffset =
view.popupDrawable?.config?.let { it.cornerRadius + it.arrowWidth / 2f }?.roundToInt()
@@ -231,7 +232,15 @@
// Calculate education view margins
val params = view.layoutParams as FrameLayout.LayoutParams
params.bottomMargin = rootBounds.bottom - position.y
- params.rightMargin = rootBounds.right - position.x - arrowToEdgeOffset
+ if (position.x < rootBounds.centerX()) {
+ params.leftMargin = position.x - arrowToEdgeOffset
+ params.gravity = Gravity.LEFT or Gravity.BOTTOM
+ view.setArrowPosition(BubblePopupDrawable.ArrowPosition.Start)
+ } else {
+ params.rightMargin = rootBounds.right - position.x - arrowToEdgeOffset
+ params.gravity = Gravity.RIGHT or Gravity.BOTTOM
+ view.setArrowPosition(BubblePopupDrawable.ArrowPosition.End)
+ }
view.layoutParams = params
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index a8af43f5..603a911 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -2479,10 +2479,10 @@
final long identity = Binder.clearCallingIdentity();
try {
int currentUser = ActivityManager.getCurrentUser();
- if (callingUser == currentUser) {
- // enforce the deny list only if the caller is not current user. Currently only auto
- // uses background visible user, and auto doesn't support profiles so profiles of
- // current users is not checked here.
+ if (callingUser == currentUser || callingUser == UserHandle.USER_SYSTEM) {
+ // enforce the deny list only if the caller is not current user or not a system
+ // user. Currently only auto uses background visible user, and auto doesn't
+ // support profiles so profiles of current users is not checked here.
return;
}
} finally {
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 5f90b39..9265ceb 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -106,6 +106,7 @@
srcs: [
"tests/src/**/systemui/ExpandHelperTest.java",
"tests/src/**/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java",
+ "tests/src/**/systemui/accessibility/AccessibilityGestureTargetsObserverTest.java",
"tests/src/**/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java",
"tests/src/**/systemui/accessibility/floatingmenu/AccessibilityTargetAdapterTest.java",
"tests/src/**/systemui/screenshot/appclips/AppClipsActivityTest.java",
@@ -269,6 +270,42 @@
"tests/src/**/systemui/volume/VolumeDialogImplTest.java",
"tests/src/**/systemui/wallet/controller/QuickAccessWalletControllerTest.java",
"tests/src/**/systemui/wallet/ui/WalletScreenControllerTest.java",
+ "tests/src/**/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplForDeviceTest.kt",
+ "tests/src/**/systemui/biometrics/UdfpsControllerOverlayTest.kt",
+ // TODO(b/322324387): Fails to start due to missing ScreenshotActivity
+ "tests/src/**/systemui/bouncer/ui/composable/BouncerContentTest.kt",
+ "tests/src/**/systemui/bouncer/ui/composable/PatternBouncerTest.kt",
+ "tests/src/**/systemui/clipboardoverlay/ClipboardListenerTest.java",
+ "tests/src/**/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt",
+ "tests/src/**/systemui/communal/data/db/CommunalWidgetDaoTest.kt",
+ "tests/src/**/systemui/display/data/repository/DisplayWindowPropertiesRepositoryImplTest.kt",
+ "tests/src/**/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt",
+ "tests/src/**/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt",
+ "tests/src/**/systemui/lifecycle/ActivatableTest.kt",
+ "tests/src/**/systemui/lifecycle/HydratorTest.kt",
+ "tests/src/**/systemui/media/dialog/MediaSwitchingControllerTest.java",
+ "tests/src/**/systemui/qs/QSImplTest.java",
+ "tests/src/**/systemui/qs/panels/ui/compose/DragAndDropTest.kt",
+ "tests/src/**/systemui/qs/panels/ui/compose/ResizingTest.kt",
+ "tests/src/**/systemui/screenshot/ActionExecutorTest.kt",
+ "tests/src/**/systemui/screenshot/ScreenshotDetectionControllerTest.kt",
+ "tests/src/**/systemui/screenshot/TakeScreenshotServiceTest.kt",
+ "tests/src/**/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java",
+ "tests/src/**/systemui/accessibility/floatingmenu/PositionTest.java",
+ "tests/src/**/systemui/animation/TransitionAnimatorTest.kt",
+ "tests/src/**/systemui/screenshot/scroll/ScrollCaptureControllerTest.java",
+ "tests/src/**/systemui/lifecycle/SysuiViewModelTest.kt",
+ "tests/src/**/systemui/flags/FakeFeatureFlags.kt",
+ "tests/src/**/systemui/animation/TransitionAnimatorTest.kt",
+ "tests/src/**/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java",
+ "tests/src/**/systemui/statusbar/connectivity/NetworkControllerSignalTest.java",
+ "tests/src/**/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt",
+ "tests/src/**/systemui/statusbar/policy/RotationLockControllerImplTest.java",
+ "tests/src/**/systemui/statusbar/phone/ScrimControllerTest.java",
+ "tests/src/**/systemui/stylus/StylusUsiPowerStartableTest.kt",
+ "tests/src/**/systemui/toast/ToastUITest.java",
+ "tests/src/**/systemui/statusbar/policy/FlashlightControllerImplTest.kt",
+ "tests/src/**/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt",
],
}
@@ -276,42 +313,23 @@
filegroup {
name: "SystemUI-tests-broken-robofiles-compile",
srcs: [
+ "tests/src/**/keyguard/KeyguardUpdateMonitorTest.java",
+ "tests/src/**/systemui/statusbar/notification/icon/IconManagerTest.kt",
+ "tests/src/**/systemui/statusbar/KeyguardIndicationControllerTest.java",
+ "tests/src/**/systemui/doze/DozeScreenStateTest.java",
+ "tests/src/**/keyguard/CarrierTextManagerTest.java",
+ "tests/src/**/systemui/notetask/NoteTaskInitializerTest.kt",
+ "tests/src/**/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt",
"tests/src/**/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt",
- "tests/src/**/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplForDeviceTest.kt",
- "tests/src/**/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt",
"tests/src/**/systemui/controls/management/ControlsFavoritingActivityTest.kt",
"tests/src/**/systemui/controls/management/ControlsProviderSelectorActivityTest.kt",
- "tests/src/**/systemui/controls/start/ControlsStartableTest.kt",
- "tests/src/**/systemui/haptics/slider/SliderStateTrackerTest.kt",
- "tests/src/**/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt",
- "tests/src/**/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt",
- "tests/src/**/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt",
- "tests/src/**/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt",
- "tests/src/**/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt",
- "tests/src/**/systemui/keyguard/ResourceTrimmerTest.kt",
- "tests/src/**/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt",
"tests/src/**/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt",
- "tests/src/**/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt",
- "tests/src/**/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt",
+ "tests/src/**/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt",
"tests/src/**/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt",
"tests/src/**/systemui/qs/tileimpl/QSTileViewImplTest.kt",
- "tests/src/**/systemui/qs/tiles/DeviceControlsTileTest.kt",
- "tests/src/**/systemui/screenshot/ActionExecutorTest.kt",
- "tests/src/**/systemui/screenshot/ActionIntentCreatorTest.kt",
- "tests/src/**/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt",
- "tests/src/**/systemui/screenshot/TakeScreenshotServiceTest.kt",
- "tests/src/**/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt",
- "tests/src/**/systemui/statusbar/notification/icon/IconManagerTest.kt",
- "tests/src/**/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt",
- "tests/src/**/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt",
- "tests/src/**/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerTest.kt",
- "tests/src/**/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt",
- "tests/src/**/systemui/statusbar/policy/FlashlightControllerImplTest.kt",
- "tests/src/**/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.kt",
- "tests/src/**/systemui/stylus/StylusUsiPowerStartableTest.kt",
+ "tests/src/**/systemui/statusbar/policy/BatteryStateNotifierTest.kt",
"tests/src/**/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt",
"tests/src/**/keyguard/ClockEventControllerTest.kt",
- "tests/src/**/systemui/animation/TransitionAnimatorTest.kt",
"tests/src/**/systemui/bluetooth/qsdialog/BluetoothAutoOnRepositoryTest.kt",
"tests/src/**/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt",
"tests/src/**/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt",
@@ -319,9 +337,6 @@
"tests/src/**/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt",
"tests/src/**/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt",
"tests/src/**/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt",
- // TODO(b/322324387): Fails to start due to missing ScreenshotActivity
- "tests/src/**/systemui/bouncer/ui/composable/BouncerContentTest.kt",
- "tests/src/**/systemui/bouncer/ui/composable/PatternBouncerTest.kt",
"tests/src/**/systemui/broadcast/UserBroadcastDispatcherTest.kt",
"tests/src/**/systemui/charging/WiredChargingRippleControllerTest.kt",
"tests/src/**/systemui/clipboardoverlay/ClipboardModelTest.kt",
@@ -348,8 +363,6 @@
"tests/src/**/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt",
"tests/src/**/systemui/navigationbar/gestural/BackPanelControllerTest.kt",
"tests/src/**/systemui/notetask/NoteTaskControllerTest.kt",
- "tests/src/**/systemui/notetask/NoteTaskInitializerTest.kt",
- "tests/src/**/systemui/power/domain/interactor/PowerInteractorTest.kt",
"tests/src/**/systemui/privacy/AppOpsPrivacyItemMonitorTest.kt",
"tests/src/**/systemui/privacy/PrivacyItemControllerTest.kt",
"tests/src/**/systemui/qs/external/CustomTileStatePersisterTest.kt",
@@ -376,7 +389,6 @@
"tests/src/**/systemui/statusbar/LightRevealScrimTest.kt",
"tests/src/**/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt",
"tests/src/**/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt",
- "tests/src/**/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt",
"tests/src/**/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt",
"tests/src/**/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt",
"tests/src/**/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt",
@@ -406,8 +418,6 @@
"tests/src/**/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt",
"tests/src/**/systemui/stylus/StylusUsiPowerUiTest.kt",
"tests/src/**/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt",
- "tests/src/**/keyguard/KeyguardUpdateMonitorTest.java",
- "tests/src/**/keyguard/CarrierTextManagerTest.java",
"tests/src/**/systemui/ScreenDecorationsTest.java",
"tests/src/**/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandlerTest.kt",
"tests/src/**/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt",
@@ -431,33 +441,17 @@
"tests/src/**/systemui/statusbar/policy/BatteryControllerStartableTest.java",
"tests/src/**/systemui/statusbar/policy/BatteryControllerTest.java",
"tests/src/**/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt",
- "tests/src/**/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt",
"tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt",
"tests/src/**/systemui/statusbar/KeyboardShortcutsReceiverTest.java",
"tests/src/**/systemui/wmshell/BubblesTest.java",
- "tests/src/**/systemui/biometrics/AuthRippleControllerTest.kt",
- "tests/src/**/keyguard/KeyguardAbsKeyInputViewControllerTest.java",
- "tests/src/**/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java",
- "tests/src/**/systemui/clipboardoverlay/ClipboardListenerTest.java",
- "tests/src/**/systemui/doze/DozeScreenStateTest.java",
- "tests/src/**/systemui/keyguard/WorkLockActivityControllerTest.java",
- "tests/src/**/systemui/media/dialog/MediaSwitchingControllerTest.java",
- "tests/src/**/systemui/navigationbar/views/NavigationBarTest.java",
- "tests/src/**/systemui/power/PowerNotificationWarningsTest.java",
- "tests/src/**/systemui/qs/QSFooterViewControllerTest.java",
- "tests/src/**/systemui/qs/QSImplTest.java",
- "tests/src/**/systemui/qs/tiles/QuickAccessWalletTileTest.java",
- "tests/src/**/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java",
- "tests/src/**/systemui/statusbar/connectivity/NetworkControllerBaseTest.java",
- "tests/src/**/systemui/statusbar/connectivity/NetworkControllerDataTest.java",
- "tests/src/**/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java",
- "tests/src/**/systemui/statusbar/connectivity/NetworkControllerSignalTest.java",
- "tests/src/**/systemui/statusbar/connectivity/NetworkControllerWifiTest.java",
- "tests/src/**/systemui/statusbar/KeyguardIndicationControllerTest.java",
- "tests/src/**/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java",
- "tests/src/**/systemui/statusbar/phone/ScrimControllerTest.java",
- "tests/src/**/systemui/statusbar/policy/RotationLockControllerImplTest.java",
- "tests/src/**/systemui/toast/ToastUITest.java",
+ "tests/src/**/systemui/power/PowerUITest.java",
+ "tests/src/**/systemui/qs/QSSecurityFooterTest.java",
+ "tests/src/**/systemui/qs/tileimpl/QSTileImplTest.java",
+ "tests/src/**/systemui/shared/plugins/PluginActionManagerTest.java",
+ "tests/src/**/systemui/statusbar/CommandQueueTest.java",
+ "tests/src/**/systemui/statusbar/connectivity/CallbackHandlerTest.java",
+ "tests/src/**/systemui/statusbar/policy/SecurityControllerTest.java",
+ "tests/src/**/systemui/shared/clocks/view/SimpleDigitalClockTextViewTest.kt",
],
visibility: ["//visibility:private"],
}
@@ -885,6 +879,7 @@
"android.test.base.impl",
"android.test.mock.impl",
"truth",
+ "androidx.test.ext.truth",
],
upstream: true,
@@ -922,6 +917,7 @@
"android.test.base.impl",
"android.test.mock.impl",
"truth",
+ "androidx.test.ext.truth",
],
upstream: true,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
index 5feb63d..1551ca9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
@@ -182,7 +182,8 @@
val itemBoundingBox = IntRect(item.offset, item.size)
draggingItemKey != item.key &&
contentListState.isItemEditable(item.index) &&
- draggingBoundingBox.contains(itemBoundingBox.center)
+ (draggingBoundingBox.contains(itemBoundingBox.center) ||
+ itemBoundingBox.contains(draggingBoundingBox.center))
}
} else {
state.layoutInfo.visibleItemsInfo
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
index 48f6cc4..14d34d7 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
@@ -33,6 +33,7 @@
import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_WAKE
import android.provider.Settings.Secure.ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS
import android.provider.Settings.Secure.ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
@@ -47,6 +48,7 @@
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
+import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
@@ -55,6 +57,7 @@
import org.mockito.MockitoAnnotations
@SmallTest
+@RunWith(AndroidJUnit4::class)
class ActiveUnlockConfigTest : SysuiTestCase() {
private lateinit var secureSettings: FakeSettings
@Mock private lateinit var contentResolver: ContentResolver
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockAccessibilityDelegateTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockAccessibilityDelegateTest.java
similarity index 96%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockAccessibilityDelegateTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockAccessibilityDelegateTest.java
index edf29c5..b937db6 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockAccessibilityDelegateTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockAccessibilityDelegateTest.java
@@ -25,6 +25,7 @@
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.TextView;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
@@ -32,10 +33,12 @@
import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
import java.util.List;
@SmallTest
+@RunWith(AndroidJUnit4.class)
public class KeyguardClockAccessibilityDelegateTest extends SysuiTestCase {
private TextView mView;
@@ -111,4 +114,4 @@
private boolean isEmpty(List<CharSequence> texts) {
return texts.stream().allMatch(TextUtils::isEmpty);
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/DependencyTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/DependencyTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ExpandHelperTest.java
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/ExpandHelperTest.java
index ccdcee5..1b07241 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ExpandHelperTest.java
@@ -21,12 +21,12 @@
import static org.mockito.Mockito.when;
import android.content.Context;
-import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import androidx.core.animation.ObjectAnimator;
import androidx.test.annotation.UiThreadTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.keyguard.KeyguardUpdateMonitor;
@@ -42,7 +42,7 @@
import org.junit.runner.RunWith;
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
@RunWithLooper
public class ExpandHelperTest extends SysuiTestCase {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/InitControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/InitControllerTest.java
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/InitControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/InitControllerTest.java
index e0ca87c..e0c7c7a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/InitControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/InitControllerTest.java
@@ -19,16 +19,16 @@
import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertTrue;
-import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import org.junit.Test;
import org.junit.runner.RunWith;
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
@TestableLooper.RunWithLooper
public class InitControllerTest extends SysuiTestCase {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapterTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapterTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapterTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogDelegateTest.kt
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogDelegateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogDelegateTest.kt
index 25b85b5..9f0c7e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogDelegateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogDelegateTest.kt
@@ -16,7 +16,7 @@
package com.android.systemui.bluetooth.qsdialog
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import android.testing.TestableLooper
import android.widget.Button
import android.widget.TextView
@@ -43,7 +43,7 @@
import org.mockito.kotlin.whenever
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@OptIn(ExperimentalCoroutinesApi::class)
class AudioSharingDialogDelegateTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt
index 9c427c6..44f9720 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt
@@ -15,7 +15,7 @@
*/
package com.android.systemui.bluetooth.qsdialog
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -37,7 +37,7 @@
import org.mockito.kotlin.whenever
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@OptIn(ExperimentalCoroutinesApi::class)
class DeviceItemActionInteractorTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/ActionReceiverTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/broadcast/ActionReceiverTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/broadcast/ActionReceiverTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/broadcast/ActionReceiverTest.kt
index b7ed27f..94d0dfe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/ActionReceiverTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/broadcast/ActionReceiverTest.kt
@@ -21,8 +21,8 @@
import android.content.Intent
import android.content.IntentFilter
import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger
@@ -51,7 +51,7 @@
import java.lang.IllegalStateException
import java.util.concurrent.Executor
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
@SmallTest
class ActionReceiverTest : SysuiTestCase() {
@@ -273,4 +273,4 @@
fun testBroadcastWithWrongAction_throwsException() {
actionReceiver.onReceive(mContext, Intent(ACTION2))
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/compose/ComposeInitializerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/compose/ComposeInitializerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/compose/ComposeInitializerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/compose/ComposeInitializerTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
index f5d2d42..8062358 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -96,7 +96,7 @@
import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
import org.mockito.kotlin.times
-import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
@OptIn(ExperimentalCoroutinesApi::class)
@@ -453,7 +453,7 @@
mMainExecutor.runAllReady()
- verifyZeroInteractions(mTouchMonitor)
+ verifyNoMoreInteractions(mTouchMonitor)
val captor = ArgumentCaptor.forClass(DreamOverlayStateController.Callback::class.java)
verify(mStateController).addCallback(captor.capture())
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModelTest.kt
new file mode 100644
index 0000000..5efb617
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModelTest.kt
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.haptics.msdl.qs
+
+import android.service.quicksettings.Tile
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.haptics.msdl.fakeMSDLPlayer
+import com.android.systemui.haptics.msdl.tileHapticsViewModelFactory
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.panels.ui.viewmodel.fakeQsTile
+import com.android.systemui.qs.panels.ui.viewmodel.tileViewModel
+import com.android.systemui.testKosmos
+import com.google.android.msdl.data.model.MSDLToken
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+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
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+class TileHapticsViewModelTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val qsTile = kosmos.fakeQsTile
+ private val msdlPlayer = kosmos.fakeMSDLPlayer
+ private val tileViewModel = kosmos.tileViewModel
+
+ private val underTest = kosmos.tileHapticsViewModelFactory.create(tileViewModel)
+
+ @Before
+ fun setUp() {
+ underTest.activateIn(testScope)
+ }
+
+ @Test
+ fun whenTileTogglesOnFromClick_playsSwitchOnHaptics() =
+ testScope.runTest {
+ // WHEN the tile toggles on after being clicked
+ underTest.setTileInteractionState(TileHapticsViewModel.TileInteractionState.CLICKED)
+ toggleOn()
+
+ // THEN the switch on token plays
+ assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.SWITCH_ON)
+ assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+ }
+
+ @Test
+ fun whenTileTogglesOffFromClick_playsSwitchOffHaptics() =
+ testScope.runTest {
+ // WHEN the tile toggles off after being clicked
+ underTest.setTileInteractionState(TileHapticsViewModel.TileInteractionState.CLICKED)
+ toggleOff()
+
+ // THEN the switch off token plays
+ assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.SWITCH_OFF)
+ assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+ }
+
+ @Test
+ fun whenTileTogglesOnWhileIdle_doesNotPlaySwitchOnHaptics() =
+ testScope.runTest {
+ // WHEN the tile toggles on without being clicked
+ toggleOn()
+
+ // THEN no token plays
+ assertThat(msdlPlayer.latestTokenPlayed).isNull()
+ assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+ }
+
+ @Test
+ fun whenTileTogglesOffWhileIdle_doesNotPlaySwitchOffHaptics() =
+ testScope.runTest {
+ // WHEN the tile toggles off without being clicked
+ toggleOff()
+
+ // THEN no token plays
+ assertThat(msdlPlayer.latestTokenPlayed).isNull()
+ assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+ }
+
+ @Test
+ fun whenLaunchingFromLongClick_playsLongPressHaptics() =
+ testScope.runTest {
+ // WHEN the tile is long-clicked and its action state changes accordingly
+ underTest.setTileInteractionState(
+ TileHapticsViewModel.TileInteractionState.LONG_CLICKED
+ )
+ // WHEN the activity transition (from the long-click) starts
+ underTest.onActivityLaunchTransitionStart()
+ runCurrent()
+
+ // THEN the long-press token plays
+ assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.LONG_PRESS)
+ assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+ }
+
+ @Test
+ fun onLongClick_whenTileDoesNotHandleLongClick_playsFailureHaptics() =
+ testScope.runTest {
+ // WHEN the tile is long-clicked but the tile does not handle a long-click
+ val state = QSTile.State().apply { handlesLongClick = false }
+ qsTile.changeState(state)
+ underTest.setTileInteractionState(
+ TileHapticsViewModel.TileInteractionState.LONG_CLICKED
+ )
+ runCurrent()
+
+ // THEN the failure token plays
+ assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.FAILURE)
+ assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+ }
+
+ @Test
+ fun whenLaunchingFromClick_doesNotPlayHaptics() =
+ testScope.runTest {
+ // WHEN the tile is clicked and its action state changes accordingly
+ underTest.setTileInteractionState(TileHapticsViewModel.TileInteractionState.CLICKED)
+ // WHEN an activity transition starts (from clicking)
+ underTest.onActivityLaunchTransitionStart()
+ runCurrent()
+
+ // THEN no haptics play
+ assertThat(msdlPlayer.latestTokenPlayed).isNull()
+ assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+ }
+
+ private fun TestScope.toggleOn() {
+ qsTile.changeState(QSTile.State().apply { state = Tile.STATE_INACTIVE })
+ runCurrent()
+
+ qsTile.changeState(QSTile.State().apply { state = Tile.STATE_ACTIVE })
+ runCurrent()
+ }
+
+ private fun TestScope.toggleOff() {
+ qsTile.changeState(QSTile.State().apply { state = Tile.STATE_ACTIVE })
+ runCurrent()
+
+ qsTile.changeState(QSTile.State().apply { state = Tile.STATE_INACTIVE })
+ runCurrent()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyConfigFlagsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/privacy/PrivacyConfigFlagsTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyConfigFlagsTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/privacy/PrivacyConfigFlagsTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogV2Test.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/privacy/PrivacyDialogV2Test.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogV2Test.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/privacy/PrivacyDialogV2Test.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/FgsManagerControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/FgsManagerControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogEventLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/TileRequestDialogEventLoggerTest.kt
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogEventLoggerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/TileRequestDialogEventLoggerTest.kt
index 64796f1..12bd5af 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogEventLoggerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/TileRequestDialogEventLoggerTest.kt
@@ -17,8 +17,8 @@
package com.android.systemui.qs.external
import android.app.StatusBarManager
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
import com.android.internal.logging.UiEventLogger
@@ -31,7 +31,7 @@
import org.junit.runner.RunWith
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
class TileRequestDialogEventLoggerTest : SysuiTestCase() {
@@ -138,4 +138,4 @@
assertThat(packageName).isEqualTo(PACKAGE_NAME)
assertThat(this.instanceId).isEqualTo(instanceId)
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index adc336d..47eebf6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -65,6 +65,7 @@
import com.android.systemui.statusbar.notification.stack.AmbientState
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.statusbar.phone.ConfigurationForwarder
import com.android.systemui.statusbar.phone.DozeScrimController
import com.android.systemui.statusbar.phone.DozeServiceHost
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
@@ -219,6 +220,7 @@
primaryBouncerInteractor,
alternateBouncerInteractor,
mock(BouncerViewBinder::class.java),
+ mock(ConfigurationForwarder::class.java),
)
underTest.setupExpandedStatusBar()
underTest.setDragDownHelper(dragDownHelper)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index d61320e..1c196c0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -15,8 +15,10 @@
*/
package com.android.systemui.shade
+import android.content.res.Configuration
import android.os.SystemClock
import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.testing.TestableLooper.RunWithLooper
import android.view.MotionEvent
import android.widget.FrameLayout
@@ -54,6 +56,7 @@
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.statusbar.phone.ConfigurationForwarder
import com.android.systemui.statusbar.phone.DozeScrimController
import com.android.systemui.statusbar.phone.DozeServiceHost
import com.android.systemui.statusbar.window.StatusBarWindowStateController
@@ -76,9 +79,11 @@
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito
+import org.mockito.Mockito.never
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.eq
@ExperimentalCoroutinesApi
@RunWith(AndroidJUnit4::class)
@@ -101,7 +106,7 @@
@Mock private lateinit var quickSettingsController: QuickSettingsController
@Mock
private lateinit var notificationStackScrollLayoutController:
- NotificationStackScrollLayoutController
+ NotificationStackScrollLayoutController
@Mock private lateinit var statusBarWindowStateController: StatusBarWindowStateController
@Mock
private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController
@@ -117,12 +122,13 @@
private lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController
@Mock
private lateinit var unfoldTransitionProgressProvider:
- Optional<UnfoldTransitionProgressProvider>
+ Optional<UnfoldTransitionProgressProvider>
@Mock private lateinit var notificationInsetsController: NotificationInsetsController
@Mock private lateinit var mGlanceableHubContainerController: GlanceableHubContainerController
@Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
@Mock lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
@Mock lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
+ @Mock lateinit var configurationForwarder: ConfigurationForwarder
@Captor
private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler>
@@ -136,10 +142,10 @@
MockitoAnnotations.initMocks(this)
underTest = spy(NotificationShadeWindowView(context, null))
whenever(
- underTest.findViewById<NotificationStackScrollLayout>(
- R.id.notification_stack_scroller
- )
+ underTest.findViewById<NotificationStackScrollLayout>(
+ R.id.notification_stack_scroller
)
+ )
.thenReturn(notificationStackScrollLayout)
whenever(underTest.findViewById<FrameLayout>(R.id.keyguard_bouncer_container))
.thenReturn(mock())
@@ -191,6 +197,7 @@
primaryBouncerInteractor,
alternateBouncerInteractor,
mock(),
+ configurationForwarder,
)
controller.setupExpandedStatusBar()
@@ -222,6 +229,23 @@
assertThat(interactionEventHandler.shouldInterceptTouchEvent(mock())).isFalse()
}
+ @Test
+ @DisableFlags(AConfigFlags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ fun onConfigurationChanged_configForwarderNotSet() {
+ underTest.onConfigurationChanged(Configuration())
+
+ verify(configurationForwarder, never()).onConfigurationChanged(any())
+ }
+
+ @Test
+ @EnableFlags(AConfigFlags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ fun onConfigurationChanged_configForwarderSet_propagatesConfig() {
+ val config = Configuration()
+ underTest.onConfigurationChanged(config)
+
+ verify(configurationForwarder).onConfigurationChanged(eq(config))
+ }
+
private fun captureInteractionEventHandler() {
verify(underTest).setInteractionEventHandler(interactionEventHandlerCaptor.capture())
interactionEventHandler = interactionEventHandlerCaptor.value
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/KeyguardIndicationControllerWithCoroutinesTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerWithCoroutinesTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/KeyguardIndicationControllerWithCoroutinesTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
index b00f9e9..f502cab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
@@ -61,6 +61,7 @@
import android.util.Log;
import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
import com.android.settingslib.R;
import com.android.settingslib.graph.SignalDrawable;
@@ -98,6 +99,7 @@
import java.util.Collections;
import java.util.List;
+@SmallTest
public class NetworkControllerBaseTest extends SysuiTestCase {
private static final String TAG = "NetworkControllerBaseTest";
protected static final int DEFAULT_LEVEL = 2;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelTest.kt
new file mode 100644
index 0000000..faf01ed
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelTest.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.ringer.ui.viewmodel
+
+import android.media.AudioManager.RINGER_MODE_NORMAL
+import android.media.AudioManager.RINGER_MODE_SILENT
+import android.media.AudioManager.RINGER_MODE_VIBRATE
+import android.media.AudioManager.STREAM_RING
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.volume.shared.model.RingerMode
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.haptics.fakeVibratorHelper
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.fakeVolumeDialogController
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+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
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+class VolumeDialogRingerDrawerViewModelTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val controller = kosmos.fakeVolumeDialogController
+ private val vibratorHelper = kosmos.fakeVibratorHelper
+
+ private lateinit var underTest: VolumeDialogRingerDrawerViewModel
+
+ @Before
+ fun setUp() {
+ underTest = kosmos.volumeDialogRingerDrawerViewModel
+ }
+
+ @Test
+ fun onSelectedRingerNormalModeButtonClicked_openDrawer() =
+ testScope.runTest {
+ val ringerViewModel by collectLastValue(underTest.ringerViewModel)
+ val normalRingerMode = RingerMode(RINGER_MODE_NORMAL)
+
+ setUpRingerModeAndOpenDrawer(normalRingerMode)
+
+ assertThat(ringerViewModel).isNotNull()
+ assertThat(ringerViewModel?.drawerState)
+ .isEqualTo(RingerDrawerState.Open(normalRingerMode))
+ }
+
+ @Test
+ fun onSelectedRingerButtonClicked_drawerOpened_closeDrawer() =
+ testScope.runTest {
+ val ringerViewModel by collectLastValue(underTest.ringerViewModel)
+ val normalRingerMode = RingerMode(RINGER_MODE_NORMAL)
+
+ setUpRingerModeAndOpenDrawer(normalRingerMode)
+ underTest.onRingerButtonClicked(normalRingerMode)
+ controller.getState()
+
+ assertThat(ringerViewModel).isNotNull()
+ assertThat(ringerViewModel?.drawerState)
+ .isEqualTo(RingerDrawerState.Closed(normalRingerMode))
+ }
+
+ @Test
+ fun onNewRingerButtonClicked_drawerOpened_updateRingerMode_closeDrawer() =
+ testScope.runTest {
+ val ringerViewModel by collectLastValue(underTest.ringerViewModel)
+ val vibrateRingerMode = RingerMode(RINGER_MODE_VIBRATE)
+
+ setUpRingerModeAndOpenDrawer(RingerMode(RINGER_MODE_NORMAL))
+ // Select vibrate ringer mode.
+ underTest.onRingerButtonClicked(vibrateRingerMode)
+ controller.getState()
+ runCurrent()
+
+ assertThat(ringerViewModel).isNotNull()
+ assertThat(
+ ringerViewModel
+ ?.availableButtons
+ ?.get(ringerViewModel!!.currentButtonIndex)
+ ?.ringerMode
+ )
+ .isEqualTo(vibrateRingerMode)
+ assertThat(ringerViewModel?.drawerState)
+ .isEqualTo(RingerDrawerState.Closed(vibrateRingerMode))
+
+ val silentRingerMode = RingerMode(RINGER_MODE_SILENT)
+ // Open drawer
+ underTest.onRingerButtonClicked(vibrateRingerMode)
+ controller.getState()
+
+ // Select silent ringer mode.
+ underTest.onRingerButtonClicked(silentRingerMode)
+ controller.getState()
+ runCurrent()
+
+ assertThat(ringerViewModel).isNotNull()
+ assertThat(
+ ringerViewModel
+ ?.availableButtons
+ ?.get(ringerViewModel!!.currentButtonIndex)
+ ?.ringerMode
+ )
+ .isEqualTo(silentRingerMode)
+ assertThat(ringerViewModel?.drawerState)
+ .isEqualTo(RingerDrawerState.Closed(silentRingerMode))
+ assertThat(controller.hasScheduledTouchFeedback).isFalse()
+ assertThat(vibratorHelper.totalVibrations).isEqualTo(2)
+ }
+
+ private fun TestScope.setUpRingerModeAndOpenDrawer(selectedRingerMode: RingerMode) {
+ controller.setStreamVolume(STREAM_RING, 50)
+ controller.setRingerMode(selectedRingerMode.value, false)
+ runCurrent()
+
+ underTest.onRingerButtonClicked(RingerMode(selectedRingerMode.value))
+ controller.getState()
+ runCurrent()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractorTest.kt
new file mode 100644
index 0000000..bfafdab
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractorTest.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.sliders.domain.interactor
+
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.VolumeDialogController
+import com.android.systemui.plugins.fakeVolumeDialogController
+import com.android.systemui.testKosmos
+import com.android.systemui.volume.dialog.sliders.domain.model.volumeDialogSliderType
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+class VolumeDialogSliderInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ private lateinit var underTest: VolumeDialogSliderInteractor
+
+ @Before
+ fun setUp() {
+ underTest = kosmos.volumeDialogSliderInteractor
+ }
+
+ @Test
+ fun settingStreamVolume_setsActiveStream() =
+ with(kosmos) {
+ testScope.runTest {
+ runCurrent()
+ // initialize the stream model
+ fakeVolumeDialogController.setStreamVolume(volumeDialogSliderType.audioStream, 0)
+
+ val sliderModel by collectLastValue(underTest.slider)
+ underTest.setStreamVolume(1)
+ runCurrent()
+
+ assertThat(sliderModel!!.isActive).isTrue()
+ }
+ }
+
+ @Test
+ fun streamVolumeIs_minMaxAreEnforced() =
+ with(kosmos) {
+ testScope.runTest {
+ runCurrent()
+ fakeVolumeDialogController.updateState {
+ states.put(
+ volumeDialogSliderType.audioStream,
+ VolumeDialogController.StreamState().apply {
+ levelMin = 0
+ level = 2
+ levelMax = 1
+ },
+ )
+ }
+
+ val sliderModel by collectLastValue(underTest.slider)
+ runCurrent()
+
+ assertThat(sliderModel!!.level).isEqualTo(1)
+ }
+ }
+}
diff --git a/packages/SystemUI/res/layout-land-television/volume_dialog.xml b/packages/SystemUI/res/layout-land-television/volume_dialog.xml
index f77db95..4321fb0 100644
--- a/packages/SystemUI/res/layout-land-television/volume_dialog.xml
+++ b/packages/SystemUI/res/layout-land-television/volume_dialog.xml
@@ -36,6 +36,7 @@
android:showDividers="middle" />
<LinearLayout
+ android:id="@+id/volume_dialog"
android:layout_width="@dimen/volume_dialog_width"
android:layout_height="wrap_content"
android:background="@drawable/volume_dialog_background"
@@ -50,9 +51,7 @@
android:layout_width="@dimen/volume_dialog_button_size"
android:layout_height="@dimen/volume_dialog_button_size" />
- <include
- android:id="@+id/volume_dialog_slider"
- layout="@layout/volume_dialog_slider" />
+ <include layout="@layout/volume_dialog_slider" />
<Button
android:id="@+id/volume_dialog_settings"
diff --git a/packages/SystemUI/res/layout-land/volume_dialog.xml b/packages/SystemUI/res/layout-land/volume_dialog.xml
index f77db95..4321fb0 100644
--- a/packages/SystemUI/res/layout-land/volume_dialog.xml
+++ b/packages/SystemUI/res/layout-land/volume_dialog.xml
@@ -36,6 +36,7 @@
android:showDividers="middle" />
<LinearLayout
+ android:id="@+id/volume_dialog"
android:layout_width="@dimen/volume_dialog_width"
android:layout_height="wrap_content"
android:background="@drawable/volume_dialog_background"
@@ -50,9 +51,7 @@
android:layout_width="@dimen/volume_dialog_button_size"
android:layout_height="@dimen/volume_dialog_button_size" />
- <include
- android:id="@+id/volume_dialog_slider"
- layout="@layout/volume_dialog_slider" />
+ <include layout="@layout/volume_dialog_slider" />
<Button
android:id="@+id/volume_dialog_settings"
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index f77db95..4321fb0 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -36,6 +36,7 @@
android:showDividers="middle" />
<LinearLayout
+ android:id="@+id/volume_dialog"
android:layout_width="@dimen/volume_dialog_width"
android:layout_height="wrap_content"
android:background="@drawable/volume_dialog_background"
@@ -50,9 +51,7 @@
android:layout_width="@dimen/volume_dialog_button_size"
android:layout_height="@dimen/volume_dialog_button_size" />
- <include
- android:id="@+id/volume_dialog_slider"
- layout="@layout/volume_dialog_slider" />
+ <include layout="@layout/volume_dialog_slider" />
<Button
android:id="@+id/volume_dialog_settings"
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 450863f..59c8f06 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -107,6 +107,7 @@
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeDisplayAwareModule;
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.shade.transition.LargeScreenShadeInterpolatorImpl;
import com.android.systemui.shared.condition.Monitor;
@@ -265,6 +266,7 @@
CommonSystemUIUnfoldModule.class,
TelephonyRepositoryModule.class,
TemporaryDisplayModule.class,
+ ShadeDisplayAwareModule.class,
TouchpadModule.class,
TunerModule.class,
UserDomainLayerModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/StateAwareExpandable.kt b/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/StateAwareExpandable.kt
new file mode 100644
index 0000000..215ceac
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/StateAwareExpandable.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.haptics.msdl.qs
+
+import android.content.ComponentName
+import android.view.ViewGroup
+import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.animation.Expandable
+
+private fun ActivityTransitionAnimator.Controller.withStateAwareness(
+ onActivityLaunchTransitionStart: () -> Unit,
+ onActivityLaunchTransitionEnd: () -> Unit,
+): ActivityTransitionAnimator.Controller {
+ val delegate = this
+ return object : ActivityTransitionAnimator.Controller by delegate {
+ override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
+ onActivityLaunchTransitionStart()
+ delegate.onTransitionAnimationStart(isExpandingFullyAbove)
+ }
+
+ override fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean?) {
+ onActivityLaunchTransitionEnd()
+ delegate.onTransitionAnimationCancelled(newKeyguardOccludedState)
+ }
+
+ override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
+ onActivityLaunchTransitionEnd()
+ delegate.onTransitionAnimationEnd(isExpandingFullyAbove)
+ }
+ }
+}
+
+private fun DialogTransitionAnimator.Controller.withStateAwareness(
+ onDialogDrawingStart: () -> Unit,
+ onDialogDrawingEnd: () -> Unit,
+): DialogTransitionAnimator.Controller {
+ val delegate = this
+ return object : DialogTransitionAnimator.Controller by delegate {
+
+ override fun startDrawingInOverlayOf(viewGroup: ViewGroup) {
+ onDialogDrawingStart()
+ delegate.startDrawingInOverlayOf(viewGroup)
+ }
+
+ override fun stopDrawingInOverlay() {
+ onDialogDrawingEnd()
+ delegate.stopDrawingInOverlay()
+ }
+ }
+}
+
+fun Expandable.withStateAwareness(
+ onDialogDrawingStart: () -> Unit,
+ onDialogDrawingEnd: () -> Unit,
+ onActivityLaunchTransitionStart: () -> Unit,
+ onActivityLaunchTransitionEnd: () -> Unit,
+): Expandable {
+ val delegate = this
+ return object : Expandable {
+ override fun activityTransitionController(
+ launchCujType: Int?,
+ cookie: ActivityTransitionAnimator.TransitionCookie?,
+ component: ComponentName?,
+ returnCujType: Int?,
+ ): ActivityTransitionAnimator.Controller? =
+ delegate
+ .activityTransitionController(launchCujType, cookie, component, returnCujType)
+ ?.withStateAwareness(onActivityLaunchTransitionStart, onActivityLaunchTransitionEnd)
+
+ override fun dialogTransitionController(
+ cuj: DialogCuj?
+ ): DialogTransitionAnimator.Controller? =
+ delegate
+ .dialogTransitionController(cuj)
+ ?.withStateAwareness(onDialogDrawingStart, onDialogDrawingEnd)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModel.kt b/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModel.kt
new file mode 100644
index 0000000..7905950
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModel.kt
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.haptics.msdl.qs
+
+import android.service.quicksettings.Tile
+import com.android.systemui.Flags
+import com.android.systemui.animation.Expandable
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
+import com.android.systemui.util.kotlin.pairwise
+import com.google.android.msdl.data.model.MSDLToken
+import com.google.android.msdl.domain.MSDLPlayer
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.transform
+
+/** A view-model to trigger haptics feedback on Quick Settings tiles */
+@OptIn(ExperimentalCoroutinesApi::class)
+class TileHapticsViewModel
+@AssistedInject
+constructor(
+ private val msdlPlayer: MSDLPlayer,
+ @Assisted private val tileViewModel: TileViewModel,
+) : ExclusiveActivatable() {
+
+ private val tileInteractionState = MutableStateFlow(TileInteractionState.IDLE)
+ private val tileAnimationState = MutableStateFlow(TileAnimationState.IDLE)
+ private val canPlayToggleHaptics: Boolean
+ get() =
+ tileAnimationState.value == TileAnimationState.IDLE &&
+ tileInteractionState.value == TileInteractionState.CLICKED
+
+ val isIdle: Boolean
+ get() =
+ tileAnimationState.value == TileAnimationState.IDLE &&
+ tileInteractionState.value == TileInteractionState.IDLE
+
+ private val toggleHapticsState: Flow<TileHapticsState> =
+ tileViewModel.state
+ .mapLatest { it.state }
+ .pairwise()
+ .transform { (previous, current) ->
+ val toggleState =
+ when {
+ !canPlayToggleHaptics -> TileHapticsState.NO_HAPTICS
+ previous == Tile.STATE_INACTIVE && current == Tile.STATE_ACTIVE ->
+ TileHapticsState.TOGGLE_ON
+ previous == Tile.STATE_ACTIVE && current == Tile.STATE_INACTIVE ->
+ TileHapticsState.TOGGLE_OFF
+ else -> TileHapticsState.NO_HAPTICS
+ }
+ emit(toggleState)
+ }
+ .distinctUntilChanged()
+
+ private val interactionHapticsState: Flow<TileHapticsState> =
+ combine(tileInteractionState, tileAnimationState) { interactionState, animationState ->
+ when {
+ interactionState == TileInteractionState.LONG_CLICKED &&
+ animationState == TileAnimationState.ACTIVITY_LAUNCH ->
+ TileHapticsState.LONG_PRESS
+ interactionState == TileInteractionState.LONG_CLICKED &&
+ !tileViewModel.currentState.handlesLongClick ->
+ TileHapticsState.FAILED_LONGPRESS
+ else -> TileHapticsState.NO_HAPTICS
+ }
+ }
+ .distinctUntilChanged()
+
+ private val hapticsState: Flow<TileHapticsState> =
+ merge(toggleHapticsState, interactionHapticsState)
+
+ override suspend fun onActivated(): Nothing {
+ try {
+ hapticsState.collect { hapticsState ->
+ val tokenToPlay: MSDLToken? =
+ when (hapticsState) {
+ TileHapticsState.TOGGLE_ON -> MSDLToken.SWITCH_ON
+ TileHapticsState.TOGGLE_OFF -> MSDLToken.SWITCH_OFF
+ TileHapticsState.LONG_PRESS -> MSDLToken.LONG_PRESS
+ TileHapticsState.FAILED_LONGPRESS -> MSDLToken.FAILURE
+ TileHapticsState.NO_HAPTICS -> null
+ }
+ tokenToPlay?.let {
+ msdlPlayer.playToken(it)
+ resetStates()
+ }
+ }
+ awaitCancellation()
+ } finally {
+ resetStates()
+ }
+ }
+
+ private fun resetStates() {
+ tileInteractionState.value = TileInteractionState.IDLE
+ tileAnimationState.value = TileAnimationState.IDLE
+ }
+
+ fun onDialogDrawingStart() {
+ tileAnimationState.value = TileAnimationState.DIALOG_LAUNCH
+ }
+
+ fun onDialogDrawingEnd() {
+ tileAnimationState.value = TileAnimationState.IDLE
+ }
+
+ fun onActivityLaunchTransitionStart() {
+ tileAnimationState.value = TileAnimationState.ACTIVITY_LAUNCH
+ }
+
+ fun onActivityLaunchTransitionEnd() {
+ tileAnimationState.value = TileAnimationState.IDLE
+ }
+
+ fun setTileInteractionState(actionState: TileInteractionState) {
+ tileInteractionState.value = actionState
+ }
+
+ fun createStateAwareExpandable(baseExpandable: Expandable): Expandable =
+ baseExpandable.withStateAwareness(
+ onDialogDrawingStart = ::onDialogDrawingStart,
+ onDialogDrawingEnd = ::onDialogDrawingEnd,
+ onActivityLaunchTransitionStart = ::onActivityLaunchTransitionStart,
+ onActivityLaunchTransitionEnd = ::onActivityLaunchTransitionEnd,
+ )
+
+ /** Models the state of toggle haptics to play */
+ enum class TileHapticsState {
+ TOGGLE_ON,
+ TOGGLE_OFF,
+ LONG_PRESS,
+ FAILED_LONGPRESS,
+ NO_HAPTICS,
+ }
+
+ /** Models the interaction that took place on the tile */
+ enum class TileInteractionState {
+ IDLE,
+ CLICKED,
+ LONG_CLICKED,
+ }
+
+ /** Models the animation state of dialogs and activity launches from a tile */
+ enum class TileAnimationState {
+ IDLE,
+ DIALOG_LAUNCH,
+ ACTIVITY_LAUNCH,
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(tileViewModel: TileViewModel): TileHapticsViewModel
+ }
+}
+
+class TileHapticsViewModelFactoryProvider
+@Inject
+constructor(private val tileHapticsViewModelFactory: TileHapticsViewModel.Factory) {
+ fun getHapticsViewModelFactory(): TileHapticsViewModel.Factory? =
+ if (Flags.msdlFeedback()) {
+ tileHapticsViewModelFactory
+ } else {
+ null
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
index 1c7a334..99a6cda 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
@@ -73,6 +73,7 @@
squishiness = { squishiness },
coroutineScope = scope,
bounceableInfo = bounceables.bounceableInfo(it, spanIndex, column, columns),
+ tileHapticsViewModelFactoryProvider = viewModel.tileHapticsViewModelFactoryProvider,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
index 28f5463..978a353 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
@@ -59,6 +59,7 @@
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.android.compose.modifiers.thenIf
+import com.android.systemui.Flags
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.common.ui.compose.load
@@ -97,6 +98,7 @@
onClick = toggleClick!!,
onLongClick = onLongClick,
onLongClickLabel = longPressLabel,
+ hapticFeedbackEnabled = !Flags.msdlFeedback(),
)
.thenIf(accessibilityUiState != null) {
Modifier.semantics {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
index 366bc9a..91f2da2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
@@ -28,6 +28,7 @@
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.grid.ui.compose.VerticalSpannedGrid
+import com.android.systemui.haptics.msdl.qs.TileHapticsViewModelFactoryProvider
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.qs.panels.shared.model.SizedTileImpl
import com.android.systemui.qs.panels.ui.compose.PaginatableGridLayout
@@ -49,6 +50,7 @@
constructor(
private val iconTilesViewModel: IconTilesViewModel,
private val viewModelFactory: InfiniteGridViewModel.Factory,
+ private val tileHapticsViewModelFactoryProvider: TileHapticsViewModelFactoryProvider,
) : PaginatableGridLayout {
@Composable
@@ -92,6 +94,7 @@
iconOnly = iconTilesViewModel.isIconTile(it.tile.spec),
modifier = Modifier.element(it.tile.spec.toElementKey(spanIndex)),
squishiness = { squishiness },
+ tileHapticsViewModelFactoryProvider = tileHapticsViewModelFactoryProvider,
coroutineScope = scope,
bounceableInfo = bounceables.bounceableInfo(it, spanIndex, column, columns),
)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
index 4104e53..e1583e3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
@@ -64,9 +64,13 @@
import com.android.compose.animation.Expandable
import com.android.compose.animation.bounceable
import com.android.compose.modifiers.thenIf
+import com.android.systemui.Flags
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.compose.modifiers.sysuiResTag
+import com.android.systemui.haptics.msdl.qs.TileHapticsViewModel
+import com.android.systemui.haptics.msdl.qs.TileHapticsViewModelFactoryProvider
+import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.qs.panels.ui.compose.BounceableInfo
import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.InactiveCornerRadius
@@ -109,6 +113,7 @@
squishiness: () -> Float,
coroutineScope: CoroutineScope,
bounceableInfo: BounceableInfo,
+ tileHapticsViewModelFactoryProvider: TileHapticsViewModelFactoryProvider,
modifier: Modifier = Modifier,
) {
val state by tile.state.collectAsStateWithLifecycle(tile.currentState)
@@ -116,6 +121,10 @@
val resources = resources()
val uiState = remember(state, resources) { state.toUiState(resources) }
val colors = TileDefaults.getColorForState(uiState)
+ val hapticsViewModel: TileHapticsViewModel? =
+ rememberViewModel(traceName = "TileHapticsViewModel") {
+ tileHapticsViewModelFactoryProvider.getHapticsViewModelFactory()?.create(tile)
+ }
// TODO(b/361789146): Draw the shapes instead of clipping
val tileShape = TileDefaults.animateTileShape(uiState.state)
@@ -129,6 +138,7 @@
},
shape = tileShape,
squishiness = squishiness,
+ hapticsViewModel = hapticsViewModel,
modifier =
modifier
.fillMaxWidth()
@@ -143,11 +153,19 @@
TileContainer(
onClick = {
tile.onClick(expandable)
+ hapticsViewModel?.setTileInteractionState(
+ TileHapticsViewModel.TileInteractionState.CLICKED
+ )
if (uiState.accessibilityUiState.toggleableState != null) {
coroutineScope.launch { currentBounceableInfo.bounceable.animateBounce() }
}
},
- onLongClick = { tile.onLongClick(expandable) },
+ onLongClick = {
+ hapticsViewModel?.setTileInteractionState(
+ TileHapticsViewModel.TileInteractionState.LONG_CLICKED
+ )
+ tile.onLongClick(expandable)
+ },
uiState = uiState,
iconOnly = iconOnly,
) {
@@ -161,9 +179,21 @@
} else {
val iconShape = TileDefaults.animateIconShape(uiState.state)
val secondaryClick: (() -> Unit)? =
- { tile.onSecondaryClick() }.takeIf { uiState.handlesSecondaryClick }
+ {
+ hapticsViewModel?.setTileInteractionState(
+ TileHapticsViewModel.TileInteractionState.CLICKED
+ )
+ tile.onSecondaryClick()
+ }
+ .takeIf { uiState.handlesSecondaryClick }
val longClick: (() -> Unit)? =
- { tile.onLongClick(expandable) }.takeIf { uiState.handlesLongClick }
+ {
+ hapticsViewModel?.setTileInteractionState(
+ TileHapticsViewModel.TileInteractionState.LONG_CLICKED
+ )
+ tile.onLongClick(expandable)
+ }
+ .takeIf { uiState.handlesLongClick }
LargeTileContent(
label = uiState.label,
secondaryLabel = uiState.secondaryLabel,
@@ -185,6 +215,7 @@
color: Color,
shape: Shape,
squishiness: () -> Float,
+ hapticsViewModel: TileHapticsViewModel?,
modifier: Modifier = Modifier,
content: @Composable (Expandable) -> Unit,
) {
@@ -193,7 +224,7 @@
shape = shape,
modifier = modifier.clip(shape).verticalSquish(squishiness),
) {
- content(it)
+ content(hapticsViewModel?.createStateAwareExpandable(it) ?: it)
}
}
@@ -254,6 +285,7 @@
onLongClick = onLongClick,
onClickLabel = uiState.accessibilityUiState.clickLabel,
onLongClickLabel = longPressLabel,
+ hapticFeedbackEnabled = !Flags.msdlFeedback(),
)
.semantics {
role = uiState.accessibilityUiState.accessibilityRole
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
index 72b586a..887a70f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
@@ -18,6 +18,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.haptics.msdl.qs.TileHapticsViewModelFactoryProvider
import com.android.systemui.qs.panels.domain.interactor.QuickQuickSettingsRowInteractor
import com.android.systemui.qs.panels.shared.model.SizedTile
import com.android.systemui.qs.panels.shared.model.SizedTileImpl
@@ -45,6 +46,7 @@
val squishinessViewModel: TileSquishinessViewModel,
private val iconTilesViewModel: IconTilesViewModel,
@Application private val applicationScope: CoroutineScope,
+ val tileHapticsViewModelFactoryProvider: TileHapticsViewModelFactoryProvider,
) {
val columns = qsColumnsViewModel.columns
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 750f5ad..67d162b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -689,7 +689,7 @@
KeyguardUpdateMonitor keyguardUpdateMonitor,
MetricsLogger metricsLogger,
ShadeLogger shadeLogger,
- ConfigurationController configurationController,
+ @ShadeDisplayAware ConfigurationController configurationController,
Provider<FlingAnimationUtils.Builder> flingAnimationUtilsBuilder,
StatusBarTouchableRegionManager statusBarTouchableRegionManager,
ConversationNotificationManager conversationNotificationManager,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index f83548d..5b6696b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -154,7 +154,7 @@
IActivityManager activityManager,
DozeParameters dozeParameters,
StatusBarStateController statusBarStateController,
- ConfigurationController configurationController,
+ @ShadeDisplayAware ConfigurationController configurationController,
KeyguardViewMediator keyguardViewMediator,
KeyguardBypassController keyguardBypassController,
@Main Executor mainExecutor,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
index 64085fd..f5fc1f4 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
@@ -24,6 +24,7 @@
import android.annotation.ColorInt;
import android.annotation.DrawableRes;
import android.annotation.LayoutRes;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Canvas;
@@ -53,6 +54,8 @@
import com.android.internal.view.FloatingActionMode;
import com.android.internal.widget.floatingtoolbar.FloatingToolbar;
import com.android.systemui.scene.ui.view.WindowRootView;
+import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround;
+import com.android.systemui.statusbar.phone.ConfigurationForwarder;
/**
* Combined keyguard and notification panel view. Also holding backdrop and scrims. This view can
@@ -68,6 +71,7 @@
private ActionMode mFloatingActionMode;
private FloatingToolbar mFloatingToolbar;
private ViewTreeObserver.OnPreDrawListener mFloatingToolbarPreDrawListener;
+ @Nullable private ConfigurationForwarder mConfigurationForwarder;
private InteractionEventHandler mInteractionEventHandler;
@@ -162,6 +166,20 @@
}
@Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ if (mConfigurationForwarder != null) {
+ ShadeWindowGoesAround.isUnexpectedlyInLegacyMode();
+ mConfigurationForwarder.onConfigurationChanged(newConfig);
+ }
+ }
+
+ public void setConfigurationForwarder(ConfigurationForwarder configurationForwarder) {
+ ShadeWindowGoesAround.isUnexpectedlyInLegacyMode();
+ mConfigurationForwarder = configurationForwarder;
+ }
+
+ @Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (DEBUG) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 36449be..365666d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -57,6 +57,7 @@
import com.android.systemui.res.R;
import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
+import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround;
import com.android.systemui.shared.animation.DisableSubpixelTextTransitionListener;
import com.android.systemui.statusbar.DragDownHelper;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
@@ -69,6 +70,7 @@
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.phone.ConfigurationForwarder;
import com.android.systemui.statusbar.phone.DozeScrimController;
import com.android.systemui.statusbar.phone.DozeServiceHost;
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController;
@@ -188,7 +190,8 @@
QuickSettingsController quickSettingsController,
PrimaryBouncerInteractor primaryBouncerInteractor,
AlternateBouncerInteractor alternateBouncerInteractor,
- BouncerViewBinder bouncerViewBinder) {
+ BouncerViewBinder bouncerViewBinder,
+ @ShadeDisplayAware ConfigurationForwarder configurationForwarder) {
mLockscreenShadeTransitionController = transitionController;
mFalsingCollector = falsingCollector;
mStatusBarStateController = statusBarStateController;
@@ -245,6 +248,9 @@
mDisableSubpixelTextTransitionListener));
}
+ if (ShadeWindowGoesAround.isEnabled()) {
+ mView.setConfigurationForwarder(configurationForwarder);
+ }
dumpManager.registerDumpable(this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
index c72db56..51f1f81 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
@@ -20,9 +20,13 @@
import android.content.res.Resources
import android.view.LayoutInflater
import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
-import com.android.systemui.Flags
+import com.android.systemui.common.ui.GlobalConfig
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.res.R
+import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
+import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
+import com.android.systemui.statusbar.phone.ConfigurationForwarder
+import com.android.systemui.statusbar.policy.ConfigurationController
import dagger.Module
import dagger.Provides
@@ -46,7 +50,7 @@
@ShadeDisplayAware
@SysUISingleton
fun provideShadeDisplayAwareContext(context: Context): Context {
- return if (Flags.shadeWindowGoesAround()) {
+ return if (ShadeWindowGoesAround.isEnabled) {
context
.createWindowContext(context.display, TYPE_APPLICATION_OVERLAY, /* options= */ null)
.apply { setTheme(R.style.Theme_SystemUI) }
@@ -68,4 +72,33 @@
fun providesDisplayAwareLayoutInflater(@ShadeDisplayAware context: Context): LayoutInflater {
return LayoutInflater.from(context)
}
+
+ @Provides
+ @ShadeDisplayAware
+ @SysUISingleton
+ fun provideShadeWindowConfigurationController(
+ @ShadeDisplayAware shadeContext: Context,
+ factory: ConfigurationControllerImpl.Factory,
+ @GlobalConfig globalConfigConfigController: ConfigurationController,
+ ): ConfigurationController {
+ return if (ShadeWindowGoesAround.isEnabled) {
+ factory.create(shadeContext)
+ } else {
+ globalConfigConfigController
+ }
+ }
+
+ @Provides
+ @ShadeDisplayAware
+ @SysUISingleton
+ fun provideShadeWindowConfigurationForwarder(
+ @ShadeDisplayAware shadeConfigurationController: ConfigurationController,
+ @GlobalConfig globalConfigController: ConfigurationController,
+ ): ConfigurationForwarder {
+ return if (ShadeWindowGoesAround.isEnabled) {
+ shadeConfigurationController
+ } else {
+ globalConfigController
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index d0f0386..e8a792c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -91,7 +91,7 @@
private val tintedIconManagerFactory: TintedIconManager.Factory,
private val privacyIconsController: HeaderPrivacyIconsController,
private val insetsProviderStore: StatusBarContentInsetsProviderStore,
- private val configurationController: ConfigurationController,
+ @ShadeDisplayAware private val configurationController: ConfigurationController,
private val variableDateViewControllerFactory: VariableDateViewController.Factory,
@Named(SHADE_HEADER) private val batteryMeterViewController: BatteryMeterViewController,
private val dumpManager: DumpManager,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index 6f5547a..72a4650 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -51,7 +51,7 @@
/** Module for classes related to the notification shade. */
@Module(
includes =
- [StartShadeModule::class, ShadeViewProviderModule::class, ShadeDisplayAwareModule::class]
+ [StartShadeModule::class, ShadeViewProviderModule::class]
)
abstract class ShadeModule {
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
index 5afc539..1918094 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
@@ -250,7 +250,7 @@
fun providesBatteryMeterViewController(
@Named(SHADE_HEADER) batteryMeterView: BatteryMeterView,
userTracker: UserTracker,
- configurationController: ConfigurationController,
+ @ShadeDisplayAware configurationController: ConfigurationController,
tunerService: TunerService,
@Main mainHandler: Handler,
contentResolver: ContentResolver,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt b/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt
new file mode 100644
index 0000000..6f492cf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.shared.flag
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the shade window goes around flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object ShadeWindowGoesAround {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_SHADE_WINDOW_GOES_AROUND
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.shadeWindowGoesAround()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This will throw an exception if
+ * the flag is not enabled to ensure that the refactor author catches issues in testing.
+ * Caution!! Using this check incorrectly will cause crashes in nextfood builds!
+ */
+ @JvmStatic
+ inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImpl.kt
index 4ba5674..823742a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImpl.kt
@@ -19,6 +19,7 @@
import android.content.Context
import android.content.res.Configuration
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.SplitShadeStateController
import javax.inject.Inject
@@ -28,11 +29,11 @@
internal class LargeScreenShadeInterpolatorImpl
@Inject
internal constructor(
- configurationController: ConfigurationController,
+ @ShadeDisplayAware configurationController: ConfigurationController,
private val context: Context,
private val splitShadeInterpolator: SplitShadeInterpolator,
private val portraitShadeInterpolator: LargeScreenPortraitShadeInterpolator,
- private val splitShadeStateController: SplitShadeStateController
+ private val splitShadeStateController: SplitShadeStateController,
) : LargeScreenShadeInterpolator {
private var inSplitShade = false
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt
index 3d125b8..fa108842 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt
@@ -105,8 +105,8 @@
scope.trySend(VolumeDialogEventModel.ShowSafetyWarning(flags))
}
- override fun onAccessibilityModeChanged(showA11yStream: Boolean) {
- scope.trySend(VolumeDialogEventModel.AccessibilityModeChanged(showA11yStream))
+ override fun onAccessibilityModeChanged(showA11yStream: Boolean?) {
+ scope.trySend(VolumeDialogEventModel.AccessibilityModeChanged(showA11yStream == true))
}
// Captions button is remove from the Volume Dialog
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt
index 2668589b..fb108c5 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt
@@ -60,7 +60,7 @@
) {
@SuppressLint("SharedFlowCreation")
- private val mutableDismissDialogEvents = MutableSharedFlow<Unit>()
+ private val mutableDismissDialogEvents = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
val dialogVisibility: Flow<VolumeDialogVisibilityModel> = repository.dialogVisibility
init {
@@ -74,7 +74,7 @@
.mapNotNull { it.toVisibilityModel() }
.onEach { model ->
updateVisibility { model }
- if (model is VolumeDialogVisibilityModel.Visible) {
+ if (model is Visible) {
resetDismissTimeout()
}
}
@@ -87,17 +87,17 @@
*/
fun dismissDialog(reason: Int) {
updateVisibility { visibilityModel ->
- if (visibilityModel is VolumeDialogVisibilityModel.Dismissed) {
+ if (visibilityModel is Dismissed) {
visibilityModel
} else {
- VolumeDialogVisibilityModel.Dismissed(reason)
+ Dismissed(reason)
}
}
}
/** Resets current dialog timeout. */
- suspend fun resetDismissTimeout() {
- mutableDismissDialogEvents.emit(Unit)
+ fun resetDismissTimeout() {
+ mutableDismissDialogEvents.tryEmit(Unit)
}
private fun updateVisibility(
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerButtonViewModel.kt
new file mode 100644
index 0000000..78d2d16
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerButtonViewModel.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.ringer.ui.viewmodel
+
+import android.annotation.DrawableRes
+import android.annotation.StringRes
+import com.android.settingslib.volume.shared.model.RingerMode
+
+/** Models ringer button that corresponds to each ringer mode. */
+data class RingerButtonViewModel(
+ /** Image resource id for the image button. */
+ @DrawableRes val imageResId: Int,
+ /** Content description for a11y. */
+ @StringRes val contentDescriptionResId: Int,
+ /** Hint label for accessibility use. */
+ @StringRes val hintLabelResId: Int,
+ /** Used to notify view model when button is clicked. */
+ val ringerMode: RingerMode,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerDrawerState.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerDrawerState.kt
new file mode 100644
index 0000000..f321837
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerDrawerState.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.ringer.ui.viewmodel
+
+import com.android.settingslib.volume.shared.model.RingerMode
+
+/** Models volume dialog ringer drawer state */
+sealed interface RingerDrawerState {
+
+ /** When clicked to open drawer */
+ data class Open(val mode: RingerMode) : RingerDrawerState
+
+ /** When clicked to close drawer */
+ data class Closed(val mode: RingerMode) : RingerDrawerState
+
+ /** Initial state when volume dialog is shown with a closed drawer. */
+ interface Initial : RingerDrawerState {
+ companion object : Initial
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerViewModel.kt
new file mode 100644
index 0000000..a09bfeb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerViewModel.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.ringer.ui.viewmodel
+
+/** Models volume dialog ringer */
+data class RingerViewModel(
+ /** List of the available buttons according to the available modes */
+ val availableButtons: List<RingerButtonViewModel?>,
+ /** The index of the currently selected button */
+ val currentButtonIndex: Int,
+ /** For open and close animations */
+ val drawerState: RingerDrawerState,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt
new file mode 100644
index 0000000..ac82ae3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.ringer.ui.viewmodel
+
+import android.media.AudioAttributes
+import android.media.AudioManager.RINGER_MODE_NORMAL
+import android.media.AudioManager.RINGER_MODE_SILENT
+import android.media.AudioManager.RINGER_MODE_VIBRATE
+import android.os.VibrationEffect
+import com.android.settingslib.volume.shared.model.RingerMode
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.volume.Events
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
+import com.android.systemui.volume.dialog.ringer.domain.VolumeDialogRingerInteractor
+import com.android.systemui.volume.dialog.ringer.shared.model.VolumeDialogRingerModel
+import com.android.systemui.volume.dialog.shared.VolumeDialogLogger
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.stateIn
+
+private const val TAG = "VolumeDialogRingerDrawerViewModel"
+
+class VolumeDialogRingerDrawerViewModel
+@AssistedInject
+constructor(
+ @VolumeDialog private val coroutineScope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val interactor: VolumeDialogRingerInteractor,
+ private val vibrator: VibratorHelper,
+ private val volumeDialogLogger: VolumeDialogLogger,
+) {
+
+ private val drawerState = MutableStateFlow<RingerDrawerState>(RingerDrawerState.Initial)
+
+ val ringerViewModel: Flow<RingerViewModel> =
+ combine(interactor.ringerModel, drawerState) { ringerModel, state ->
+ ringerModel.toViewModel(state)
+ }
+ .flowOn(backgroundDispatcher)
+ .stateIn(coroutineScope, SharingStarted.Eagerly, null)
+ .filterNotNull()
+
+ // Vibration attributes.
+ private val sonificiationVibrationAttributes =
+ AudioAttributes.Builder()
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
+ .build()
+
+ fun onRingerButtonClicked(ringerMode: RingerMode) {
+ if (drawerState.value is RingerDrawerState.Open) {
+ Events.writeEvent(Events.EVENT_RINGER_TOGGLE, ringerMode.value)
+ provideTouchFeedback(ringerMode)
+ interactor.setRingerMode(ringerMode)
+ }
+ drawerState.value =
+ when (drawerState.value) {
+ is RingerDrawerState.Initial -> {
+ RingerDrawerState.Open(ringerMode)
+ }
+ is RingerDrawerState.Open -> {
+ RingerDrawerState.Closed(ringerMode)
+ }
+ is RingerDrawerState.Closed -> {
+ RingerDrawerState.Open(ringerMode)
+ }
+ }
+ }
+
+ private fun provideTouchFeedback(ringerMode: RingerMode) {
+ when (ringerMode.value) {
+ RINGER_MODE_NORMAL -> {
+ interactor.scheduleTouchFeedback()
+ null
+ }
+ RINGER_MODE_SILENT -> VibrationEffect.get(VibrationEffect.EFFECT_CLICK)
+ RINGER_MODE_VIBRATE -> VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK)
+ else -> VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK)
+ }?.let { vibrator.vibrate(it, sonificiationVibrationAttributes) }
+ }
+
+ private fun VolumeDialogRingerModel.toViewModel(
+ drawerState: RingerDrawerState
+ ): RingerViewModel {
+ val currentIndex = availableModes.indexOf(currentRingerMode)
+ if (currentIndex == -1) {
+ volumeDialogLogger.onCurrentRingerModeIsUnsupported(currentRingerMode)
+ }
+ return RingerViewModel(
+ availableButtons = availableModes.map { mode -> toButtonViewModel(mode) },
+ currentButtonIndex = currentIndex,
+ drawerState = drawerState,
+ )
+ }
+
+ private fun VolumeDialogRingerModel.toButtonViewModel(
+ ringerMode: RingerMode
+ ): RingerButtonViewModel? {
+ return when (ringerMode.value) {
+ RINGER_MODE_SILENT ->
+ RingerButtonViewModel(
+ imageResId = R.drawable.ic_speaker_mute,
+ contentDescriptionResId = R.string.volume_ringer_status_silent,
+ hintLabelResId = R.string.volume_ringer_hint_unmute,
+ ringerMode = ringerMode,
+ )
+ RINGER_MODE_VIBRATE ->
+ RingerButtonViewModel(
+ imageResId = R.drawable.ic_volume_ringer_vibrate,
+ contentDescriptionResId = R.string.volume_ringer_status_vibrate,
+ hintLabelResId = R.string.volume_ringer_hint_vibrate,
+ ringerMode = ringerMode,
+ )
+ RINGER_MODE_NORMAL ->
+ when {
+ isMuted && isEnabled ->
+ RingerButtonViewModel(
+ imageResId = R.drawable.ic_speaker_mute,
+ contentDescriptionResId = R.string.volume_ringer_status_normal,
+ hintLabelResId = R.string.volume_ringer_hint_unmute,
+ ringerMode = ringerMode,
+ )
+
+ availableModes.contains(RingerMode(RINGER_MODE_VIBRATE)) ->
+ RingerButtonViewModel(
+ imageResId = R.drawable.ic_speaker_on,
+ contentDescriptionResId = R.string.volume_ringer_status_normal,
+ hintLabelResId = R.string.volume_ringer_hint_vibrate,
+ ringerMode = ringerMode,
+ )
+
+ else ->
+ RingerButtonViewModel(
+ imageResId = R.drawable.ic_speaker_on,
+ contentDescriptionResId = R.string.volume_ringer_status_normal,
+ hintLabelResId = R.string.volume_ringer_hint_mute,
+ ringerMode = ringerMode,
+ )
+ }
+ else -> null
+ }
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(): VolumeDialogRingerDrawerViewModel
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/VolumeDialogLogger.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/VolumeDialogLogger.kt
index 59c38c0..9a3aa7e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/VolumeDialogLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/VolumeDialogLogger.kt
@@ -15,6 +15,7 @@
*/
package com.android.systemui.volume.dialog.shared
+import com.android.settingslib.volume.shared.model.RingerMode
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
import com.android.systemui.log.dagger.VolumeLog
@@ -43,4 +44,13 @@
{ "Dismiss: ${Events.DISMISS_REASONS[int1]}" },
)
}
+
+ fun onCurrentRingerModeIsUnsupported(ringerMode: RingerMode) {
+ logBuffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ { int1 = ringerMode.value },
+ { "Current ringer mode: $int1, ringer mode is unsupported in ringer drawer options" },
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt
index f78a8dc..876bf2c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt
@@ -25,6 +25,7 @@
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.mapNotNull
/** Operates a state of particular slider of the Volume Dialog. */
@@ -37,12 +38,23 @@
) {
val slider: Flow<VolumeDialogStreamModel> =
- volumeDialogStateInteractor.volumeDialogState.mapNotNull {
- it.streamModels[sliderType.audioStream]
- }
+ volumeDialogStateInteractor.volumeDialogState
+ .mapNotNull {
+ it.streamModels[sliderType.audioStream]?.run {
+ if (level < levelMin || level > levelMax) {
+ copy(level = level.coerceIn(levelMin, levelMax))
+ } else {
+ this
+ }
+ }
+ }
+ .distinctUntilChanged()
fun setStreamVolume(userLevel: Int) {
- volumeDialogController.setStreamVolume(sliderType.audioStream, userLevel)
+ with(volumeDialogController) {
+ setStreamVolume(sliderType.audioStream, userLevel)
+ setActiveStream(sliderType.audioStream)
+ }
}
@VolumeDialogScope
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
index 25a5f28..5c4d53a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
@@ -16,32 +16,55 @@
package com.android.systemui.volume.dialog.sliders.ui
+import android.animation.Animator
+import android.animation.ObjectAnimator
import android.view.View
-import androidx.lifecycle.viewmodel.compose.viewModel
+import android.view.animation.DecelerateInterpolator
import com.android.systemui.lifecycle.WindowLifecycleState
import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.lifecycle.setSnapshotBinding
import com.android.systemui.lifecycle.viewModel
+import com.android.systemui.res.R
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
+import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel
import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderViewModel
+import com.android.systemui.volume.dialog.ui.utils.JankListenerFactory
+import com.android.systemui.volume.dialog.ui.utils.awaitAnimation
+import com.google.android.material.slider.LabelFormatter
+import com.google.android.material.slider.Slider
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
+import kotlin.math.roundToInt
import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+private const val PROGRESS_CHANGE_ANIMATION_DURATION_MS = 80L
class VolumeDialogSliderViewBinder
@AssistedInject
-constructor(@Assisted private val viewModelProvider: () -> VolumeDialogSliderViewModel) {
+constructor(
+ @Assisted private val viewModelProvider: () -> VolumeDialogSliderViewModel,
+ private val jankListenerFactory: JankListenerFactory,
+) {
fun bind(view: View) {
with(view) {
+ val sliderView: Slider =
+ requireViewById<Slider>(R.id.volume_dialog_slider).apply {
+ labelBehavior = LabelFormatter.LABEL_GONE
+ }
repeatWhenAttached {
viewModel(
traceName = "VolumeDialogSliderViewBinder",
minWindowLifecycleState = WindowLifecycleState.ATTACHED,
factory = { viewModelProvider() },
) { viewModel ->
- setSnapshotBinding {}
+ sliderView.addOnChangeListener { _, value, fromUser ->
+ viewModel.setStreamVolume(value.roundToInt(), fromUser)
+ }
+
+ viewModel.model.onEach { it.bindToSlider(sliderView) }.launchIn(this)
awaitCancellation()
}
@@ -49,6 +72,19 @@
}
}
+ private suspend fun VolumeDialogStreamModel.bindToSlider(slider: Slider) {
+ with(slider) {
+ valueFrom = levelMin.toFloat()
+ valueTo = levelMax.toFloat()
+ // coerce the current value to the new value range before animating it
+ value = value.coerceIn(valueFrom, valueTo)
+ setValueAnimated(
+ level.toFloat(),
+ jankListenerFactory.update(this, PROGRESS_CHANGE_ANIMATION_DURATION_MS),
+ )
+ }
+ }
+
@AssistedFactory
@VolumeDialogScope
interface Factory {
@@ -58,3 +94,16 @@
): VolumeDialogSliderViewBinder
}
}
+
+private suspend fun Slider.setValueAnimated(
+ newValue: Float,
+ jankListener: Animator.AnimatorListener,
+) {
+ ObjectAnimator.ofFloat(value, newValue)
+ .apply {
+ duration = PROGRESS_CHANGE_ANIMATION_DURATION_MS
+ interpolator = DecelerateInterpolator()
+ addListener(jankListener)
+ }
+ .awaitAnimation<Float> { value = it }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
index 0a00f70..f486fe1 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
@@ -16,14 +16,20 @@
package com.android.systemui.volume.dialog.sliders.ui
+import android.view.LayoutInflater
import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.LayoutRes
+import androidx.compose.ui.util.fastForEachIndexed
import com.android.systemui.lifecycle.WindowLifecycleState
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.lifecycle.setSnapshotBinding
import com.android.systemui.lifecycle.viewModel
+import com.android.systemui.res.R
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSlidersViewModel
import javax.inject.Inject
+import kotlin.math.abs
import kotlinx.coroutines.awaitCancellation
@VolumeDialogScope
@@ -33,17 +39,44 @@
fun bind(view: View) {
with(view) {
+ val volumeDialog: View = requireViewById(R.id.volume_dialog)
+ val floatingSlidersContainer: ViewGroup =
+ requireViewById(R.id.volume_dialog_floating_sliders_container)
repeatWhenAttached {
viewModel(
traceName = "VolumeDialogSlidersViewBinder",
minWindowLifecycleState = WindowLifecycleState.ATTACHED,
factory = { viewModelFactory.create() },
) { viewModel ->
- setSnapshotBinding {}
+ setSnapshotBinding {
+ viewModel.uiModel?.sliderViewBinder?.bind(volumeDialog)
+ val floatingSliderViewBinders =
+ viewModel.uiModel?.floatingSliderViewBinders ?: emptyList()
+ floatingSlidersContainer.ensureChildCount(
+ viewLayoutId = R.layout.volume_dialog_slider_floating,
+ count = floatingSliderViewBinders.size,
+ )
+ floatingSliderViewBinders.fastForEachIndexed { index, viewBinder ->
+ viewBinder.bind(floatingSlidersContainer.getChildAt(index))
+ }
+ }
awaitCancellation()
}
}
}
}
}
+
+private fun ViewGroup.ensureChildCount(@LayoutRes viewLayoutId: Int, count: Int) {
+ val childCountDelta = childCount - count
+ when {
+ childCountDelta > 0 -> {
+ removeViews(0, childCountDelta)
+ }
+ childCountDelta < 0 -> {
+ val inflater = LayoutInflater.from(context)
+ repeat(abs(childCountDelta)) { inflater.inflate(viewLayoutId, this, true) }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
index 7ee722d..ea0b49d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
@@ -16,26 +16,85 @@
package com.android.systemui.volume.dialog.sliders.ui.viewmodel
+import com.android.systemui.util.time.SystemClock
+import com.android.systemui.volume.Events
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
+import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel
import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInteractor
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
+/*
+ This prevents volume slider updates while user interacts with it. This is needed due to the
+ flawed VolumeDialogControllerImpl. It has a single threaded message queue that handles all state
+ updates and doesn't skip sequential updates of the same stream. This leads to a bottleneck when
+ user rigorously adjusts the slider.
+
+ Remove this when getting rid of the VolumeDialogControllerImpl as this doesn't happen in the
+ Volume Panel that uses the new coroutine-backed AudioRepository.
+*/
+// TODO(b/375355785) remove this
+private const val VOLUME_UPDATE_GRACE_PERIOD = 1000
+
+@OptIn(ExperimentalCoroutinesApi::class)
class VolumeDialogSliderViewModel
@AssistedInject
-constructor(@Assisted private val interactor: VolumeDialogSliderInteractor) {
+constructor(
+ @Assisted private val interactor: VolumeDialogSliderInteractor,
+ private val visibilityInteractor: VolumeDialogVisibilityInteractor,
+ @VolumeDialog private val coroutineScope: CoroutineScope,
+ private val systemClock: SystemClock,
+) {
- val model: Flow<VolumeDialogStreamModel> = interactor.slider
+ private val userVolumeUpdates = MutableStateFlow<VolumeUpdate?>(null)
- fun setStreamVolume(volume: Int) {
- interactor.setStreamVolume(volume)
+ val model: Flow<VolumeDialogStreamModel> =
+ interactor.slider
+ .filter {
+ val lastVolumeUpdateTime = userVolumeUpdates.value?.timestampMillis ?: 0
+ getTimestampMillis() - lastVolumeUpdateTime > VOLUME_UPDATE_GRACE_PERIOD
+ }
+ .stateIn(coroutineScope, SharingStarted.Eagerly, null)
+ .filterNotNull()
+
+ init {
+ userVolumeUpdates
+ .filterNotNull()
+ .mapLatest { volume ->
+ interactor.setStreamVolume(volume.newVolumeLevel)
+ Events.writeEvent(Events.EVENT_TOUCH_LEVEL_CHANGED, model.first().stream, volume)
+ }
+ .launchIn(coroutineScope)
}
+ fun setStreamVolume(volume: Int, fromUser: Boolean) {
+ if (fromUser) {
+ visibilityInteractor.resetDismissTimeout()
+ userVolumeUpdates.value =
+ VolumeUpdate(newVolumeLevel = volume, timestampMillis = getTimestampMillis())
+ }
+ }
+
+ private fun getTimestampMillis(): Long = systemClock.uptimeMillis()
+
@AssistedFactory
interface Factory {
fun create(interactor: VolumeDialogSliderInteractor): VolumeDialogSliderViewModel
}
+
+ private data class VolumeUpdate(val newVolumeLevel: Int, val timestampMillis: Long)
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt
index b5b292f..22cf89f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt
@@ -16,6 +16,9 @@
package com.android.systemui.volume.dialog.sliders.ui.viewmodel
+import androidx.compose.runtime.getValue
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInteractor
import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSlidersInteractor
@@ -24,10 +27,9 @@
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@@ -39,9 +41,10 @@
private val sliderInteractorFactory: VolumeDialogSliderInteractor.Factory,
private val sliderViewModelFactory: VolumeDialogSliderViewModel.Factory,
private val sliderViewBinderFactory: VolumeDialogSliderViewBinder.Factory,
-) {
+) : ExclusiveActivatable() {
- val sliders: Flow<VolumeDialogSliderUiModel> =
+ private val hydrator = Hydrator("VolumeDialogSlidersViewModel")
+ private val slidersStateFlow: StateFlow<VolumeDialogSliderUiModel?> =
slidersInteractor.sliders
.distinctUntilChanged()
.map { slidersModel ->
@@ -52,7 +55,13 @@
)
}
.stateIn(coroutineScope, SharingStarted.Eagerly, null)
- .filterNotNull()
+
+ val uiModel: VolumeDialogSliderUiModel? by
+ hydrator.hydratedStateOf("VolumeDialogSlidersViewModel#uiModel", slidersStateFlow)
+
+ override suspend fun onActivated(): Nothing {
+ hydrator.activate()
+ }
private fun createSliderViewBinder(type: VolumeDialogSliderType): VolumeDialogSliderViewBinder =
sliderViewBinderFactory.create {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt
index 77733fe..eb9483f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt
@@ -20,6 +20,7 @@
import android.graphics.Color
import android.graphics.PixelFormat
import android.graphics.drawable.ColorDrawable
+import android.view.View
import android.view.ViewGroup
import android.view.Window
import android.view.WindowManager
@@ -27,6 +28,7 @@
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
import com.android.systemui.volume.dialog.settings.ui.binder.VolumeDialogSettingsButtonViewBinder
+import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSlidersViewBinder
import com.android.systemui.volume.dialog.ui.viewmodel.VolumeDialogGravityViewModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -40,6 +42,7 @@
constructor(
@VolumeDialog private val coroutineScope: CoroutineScope,
private val volumeDialogViewBinder: VolumeDialogViewBinder,
+ private val slidersViewBinder: VolumeDialogSlidersViewBinder,
private val settingsButtonViewBinder: VolumeDialogSettingsButtonViewBinder,
private val gravityViewModel: VolumeDialogGravityViewModel,
) {
@@ -50,11 +53,11 @@
dialog.setContentView(R.layout.volume_dialog)
dialog.setCanceledOnTouchOutside(true)
- settingsButtonViewBinder.bind(dialog.requireViewById(R.id.volume_dialog_settings))
- volumeDialogViewBinder.bind(
- dialog,
- dialog.requireViewById(R.id.volume_dialog_container),
- )
+ with(dialog.requireViewById<View>(R.id.volume_dialog_container)) {
+ slidersViewBinder.bind(this)
+ settingsButtonViewBinder.bind(this)
+ volumeDialogViewBinder.bind(dialog, this)
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 34ebba8..b941fde 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -159,6 +159,7 @@
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.utils.FieldSetter;
import org.junit.After;
import org.junit.Assert;
@@ -172,7 +173,6 @@
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession;
-import org.mockito.internal.util.reflection.FieldSetter;
import org.mockito.quality.Strictness;
import java.util.ArrayList;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplForDeviceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplForDeviceTest.kt
index 1d1329ac..4e0ebae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplForDeviceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplForDeviceTest.kt
@@ -34,6 +34,7 @@
import com.android.systemui.qs.tiles.ReduceBrightColorsTile
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.utils.FieldSetter
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -46,7 +47,6 @@
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito
-import org.mockito.internal.util.reflection.FieldSetter
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/TileHapticsViewModelFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/TileHapticsViewModelFactoryKosmos.kt
new file mode 100644
index 0000000..a798eb7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/TileHapticsViewModelFactoryKosmos.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.haptics.msdl
+
+import com.android.systemui.haptics.msdl.qs.TileHapticsViewModel
+import com.android.systemui.haptics.msdl.qs.TileHapticsViewModelFactoryProvider
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
+
+val Kosmos.tileHapticsViewModelFactory by
+ Kosmos.Fixture {
+ object : TileHapticsViewModel.Factory {
+ override fun create(tileViewModel: TileViewModel): TileHapticsViewModel =
+ TileHapticsViewModel(fakeMSDLPlayer, tileViewModel)
+ }
+ }
+
+val Kosmos.tileHapticsViewModelFactoryProvider by
+ Kosmos.Fixture { TileHapticsViewModelFactoryProvider(tileHapticsViewModelFactory) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt
index b45120e..43eb93e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt
@@ -43,30 +43,36 @@
private var state = VolumeDialogController.State()
override fun setActiveStream(stream: Int) {
- // ensure streamState existence for the active stream
- state.states.getOrElse(stream) {
- VolumeDialogController.StreamState().also { streamState ->
- state.states.put(stream, streamState)
- }
- }
- state.activeStream = stream
- }
-
- override fun setStreamVolume(stream: Int, userLevel: Int) {
- val streamState =
- state.states.getOrElse(stream) {
+ updateState {
+ // ensure streamState existence for the active stream`
+ states.getOrElse(stream) {
VolumeDialogController.StreamState().also { streamState ->
state.states.put(stream, streamState)
}
}
- streamState.level = userLevel.coerceIn(streamState.levelMin, streamState.levelMax)
+ activeStream = stream
+ }
+ }
+
+ override fun setStreamVolume(stream: Int, userLevel: Int) {
+ updateState {
+ val streamState =
+ states.getOrElse(stream) {
+ VolumeDialogController.StreamState().also { streamState ->
+ states.put(stream, streamState)
+ }
+ }
+ streamState.level = userLevel.coerceIn(streamState.levelMin, streamState.levelMax)
+ }
}
override fun setRingerMode(ringerModeNormal: Int, external: Boolean) {
- if (external) {
- state.ringerModeExternal = ringerModeNormal
- } else {
- state.ringerModeInternal = ringerModeNormal
+ updateState {
+ if (external) {
+ ringerModeExternal = ringerModeNormal
+ } else {
+ ringerModeInternal = ringerModeNormal
+ }
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSTile.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSTile.kt
index 093ebd6..562980d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSTile.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSTile.kt
@@ -20,13 +20,10 @@
import com.android.systemui.animation.Expandable
import com.android.systemui.plugins.qs.QSTile
-class FakeQSTile(
- var user: Int,
- var available: Boolean = true,
-) : QSTile {
+class FakeQSTile(var user: Int, var available: Boolean = true) : QSTile {
private var tileSpec: String? = null
var destroyed = false
- private val state = QSTile.State()
+ private var state = QSTile.State()
val callbacks = mutableListOf<QSTile.Callback>()
override fun getTileSpec(): String? {
@@ -93,4 +90,9 @@
override fun isListening(): Boolean {
return false
}
+
+ fun changeState(newState: QSTile.State) {
+ state = newState
+ callbacks.forEach { it.onStateChanged(state) }
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
index b6b0a41..b5a6bf1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
@@ -16,10 +16,17 @@
package com.android.systemui.qs.panels.domain.interactor
+import com.android.systemui.haptics.msdl.tileHapticsViewModelFactoryProvider
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.qs.panels.ui.compose.infinitegrid.InfiniteGridLayout
import com.android.systemui.qs.panels.ui.viewmodel.iconTilesViewModel
import com.android.systemui.qs.panels.ui.viewmodel.infiniteGridViewModelFactory
val Kosmos.infiniteGridLayout by
- Kosmos.Fixture { InfiniteGridLayout(iconTilesViewModel, infiniteGridViewModelFactory) }
+ Kosmos.Fixture {
+ InfiniteGridLayout(
+ iconTilesViewModel,
+ infiniteGridViewModelFactory,
+ tileHapticsViewModelFactoryProvider,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt
index 67d9e0e..41ee260 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.panels.ui.viewmodel
+import com.android.systemui.haptics.msdl.tileHapticsViewModelFactoryProvider
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.qs.panels.domain.interactor.quickQuickSettingsRowInteractor
@@ -30,5 +31,6 @@
tileSquishinessViewModel,
iconTilesViewModel,
applicationCoroutineScope,
+ tileHapticsViewModelFactoryProvider,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModelKosmos.kt
new file mode 100644
index 0000000..223755d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModelKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.FakeQSTile
+import com.android.systemui.qs.pipeline.shared.TileSpec
+
+val Kosmos.fakeQsTile by Kosmos.Fixture { FakeQSTile(user = 0, available = true) }
+val Kosmos.tileViewModel by
+ Kosmos.Fixture { TileViewModel(fakeQsTile, TileSpec.Companion.create("test")) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/FieldSetter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/FieldSetter.kt
new file mode 100644
index 0000000..6e2d722
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/FieldSetter.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.utils
+
+import java.lang.reflect.Field
+
+object FieldSetter {
+ @JvmStatic
+ fun setField(obj: Any, fieldName: String, value: Any?) {
+ try {
+ val field = obj.javaClass.getDeclaredField(fieldName)
+ field.isAccessible = true
+ field[obj] = value
+ } catch (e: NoSuchFieldException) {
+ throw RuntimeException("Failed to set $fieldName of obj", e)
+ } catch (e: IllegalAccessException) {
+ throw RuntimeException("Failed to set $fieldName of obj", e)
+ }
+ }
+
+ @JvmStatic
+ fun setField(obj: Any?, fld: Field, value: Any?) {
+ try {
+ fld.setAccessible(true)
+ fld.set(obj, value)
+ } catch (e: IllegalAccessException) {
+ throw RuntimeException("Failed to set ${fld.getName()} of obj", e)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt
new file mode 100644
index 0000000..db1c01a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.ringer.ui.viewmodel
+
+import com.android.systemui.haptics.vibratorHelper
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.volume.dialog.ringer.domain.volumeDialogRingerInteractor
+import com.android.systemui.volume.dialog.shared.volumeDialogLogger
+
+val Kosmos.volumeDialogRingerDrawerViewModel by
+ Kosmos.Fixture {
+ VolumeDialogRingerDrawerViewModel(
+ backgroundDispatcher = testDispatcher,
+ coroutineScope = applicationCoroutineScope,
+ interactor = volumeDialogRingerInteractor,
+ vibrator = vibratorHelper,
+ volumeDialogLogger = volumeDialogLogger,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/shared/VolumeDialogLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/shared/VolumeDialogLoggerKosmos.kt
new file mode 100644
index 0000000..f9d4a99
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/shared/VolumeDialogLoggerKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.shared
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.log.logcatLogBuffer
+
+val Kosmos.volumeDialogLogger by Kosmos.Fixture { VolumeDialogLogger(logcatLogBuffer()) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractorKosmos.kt
new file mode 100644
index 0000000..423100a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractorKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.sliders.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.plugins.volumeDialogController
+import com.android.systemui.volume.dialog.domain.interactor.volumeDialogStateInteractor
+import com.android.systemui.volume.dialog.sliders.domain.model.volumeDialogSliderType
+
+val Kosmos.volumeDialogSliderInteractor: VolumeDialogSliderInteractor by
+ Kosmos.Fixture {
+ VolumeDialogSliderInteractor(
+ volumeDialogSliderType,
+ volumeDialogStateInteractor,
+ volumeDialogController,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/domain/model/VolumeDialogSliderTypeKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/domain/model/VolumeDialogSliderTypeKosmos.kt
new file mode 100644
index 0000000..cc8c1ea
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/domain/model/VolumeDialogSliderTypeKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.sliders.domain.model
+
+import android.media.AudioManager
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.volumeDialogSliderType: VolumeDialogSliderType by
+ Kosmos.Fixture { VolumeDialogSliderType.Stream(AudioManager.STREAM_SYSTEM) }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index f6a808b..a59f4bd 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -39,6 +39,7 @@
import static org.xmlpull.v1.XmlPullParser.START_TAG;
import android.Manifest;
+import android.annotation.EnforcePermission;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -87,6 +88,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.ParcelableException;
+import android.os.PermissionEnforcer;
import android.os.Process;
import android.os.RemoteCallback;
import android.os.RemoteCallbackList;
@@ -314,6 +316,8 @@
public PackageInstallerService(Context context, PackageManagerService pm,
Supplier<PackageParser2> apexParserSupplier) {
+ super(PermissionEnforcer.fromContext(context));
+
mContext = context;
mPm = pm;
@@ -1877,23 +1881,20 @@
}
@Override
+ @EnforcePermission(android.Manifest.permission.VERIFICATION_AGENT)
public @PackageInstaller.VerificationPolicy int getVerificationPolicy() {
- if (mContext.checkCallingOrSelfPermission(Manifest.permission.VERIFICATION_AGENT)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("You need the "
- + "com.android.permission.VERIFICATION_AGENT permission "
- + "to get the verification policy");
- }
+ getVerificationPolicy_enforcePermission();
return mVerificationPolicy.get();
}
@Override
+ @EnforcePermission(android.Manifest.permission.VERIFICATION_AGENT)
public boolean setVerificationPolicy(@PackageInstaller.VerificationPolicy int policy) {
- if (mContext.checkCallingOrSelfPermission(Manifest.permission.VERIFICATION_AGENT)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("You need the "
- + "com.android.permission.VERIFICATION_AGENT permission "
- + "to set the verification policy");
+ setVerificationPolicy_enforcePermission();
+ final int callingUid = getCallingUid();
+ // Only the verifier currently bound by the system can change the policy, except for Shell
+ if (!PackageManagerServiceUtils.isRootOrShell(callingUid)) {
+ mVerifierController.assertCallerIsCurrentVerifier(callingUid);
}
if (!isValidVerificationPolicy(policy)) {
return false;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 9a9e434..512b195 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -2884,14 +2884,13 @@
}
// Send the request to the verifier and wait for its response before the rest of
// the installation can proceed.
+ final VerifierCallback verifierCallback = new VerifierCallback();
if (!mVerifierController.startVerificationSession(mPm::snapshotComputer, userId,
sessionId, getPackageName(), Uri.fromFile(stageDir), signingInfo,
declaredLibraries, mVerificationPolicy.get(), /* extensionParams= */ null,
- new VerifierCallback(), /* retry= */ false)) {
- // A verifier is installed but cannot be connected. Installation disallowed.
- onSessionVerificationFailure(INSTALL_FAILED_INTERNAL_ERROR,
- "A verifier agent is available on device but cannot be connected.",
- /* extras= */ null);
+ verifierCallback, /* retry= */ false)) {
+ // A verifier is installed but cannot be connected.
+ verifierCallback.onConnectionFailed();
}
} else {
// No need to check with verifier. Proceed with the rest of the verification.
@@ -2995,7 +2994,6 @@
onSessionVerificationFailure(INSTALL_FAILED_VERIFICATION_FAILURE,
"A verifier agent is available on device but cannot be connected.",
bundle);
-
});
}
/**
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 4652c3a..f8e56e1 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -399,6 +399,10 @@
return runUnarchive();
case "get-domain-verification-agent":
return runGetDomainVerificationAgent();
+ case "get-verification-policy":
+ return runGetVerificationPolicy();
+ case "set-verification-policy":
+ return runSetVerificationPolicy();
default: {
if (ART_SERVICE_COMMANDS.contains(cmd)) {
return runArtServiceCommand();
@@ -4645,6 +4649,86 @@
return 0;
}
+ private int runGetVerificationPolicy() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ int userId = UserHandle.USER_ALL;
+
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ if (opt.equals("--user")) {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ if (userId != UserHandle.USER_ALL && userId != UserHandle.USER_CURRENT) {
+ UserManagerInternal umi =
+ LocalServices.getService(UserManagerInternal.class);
+ UserInfo userInfo = umi.getUserInfo(userId);
+ if (userInfo == null) {
+ pw.println("Failure [user " + userId + " doesn't exist]");
+ return 1;
+ }
+ }
+ } else {
+ pw.println("Error: Unknown option: " + opt);
+ return 1;
+ }
+ }
+ final int translatedUserId =
+ translateUserId(userId, UserHandle.USER_SYSTEM, "runGetVerificationPolicy");
+ try {
+ final IPackageInstaller installer = mInterface.getPackageInstaller();
+ // TODO(b/360129657): global verification policy should be per user
+ final int policy = installer.getVerificationPolicy();
+ pw.println(policy);
+ } catch (Exception e) {
+ pw.println("Failure [" + e.getMessage() + "]");
+ return 1;
+ }
+ return 0;
+ }
+
+ private int runSetVerificationPolicy() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ int userId = UserHandle.USER_ALL;
+
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ if (opt.equals("--user")) {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ if (userId != UserHandle.USER_ALL && userId != UserHandle.USER_CURRENT) {
+ UserManagerInternal umi =
+ LocalServices.getService(UserManagerInternal.class);
+ UserInfo userInfo = umi.getUserInfo(userId);
+ if (userInfo == null) {
+ pw.println("Failure [user " + userId + " doesn't exist]");
+ return 1;
+ }
+ }
+ } else {
+ pw.println("Error: Unknown option: " + opt);
+ return 1;
+ }
+ }
+ final String policyStr = getNextArg();
+ if (policyStr == null) {
+ pw.println("Error: policy not specified");
+ return 1;
+ }
+ final int translatedUserId =
+ translateUserId(userId, UserHandle.USER_SYSTEM, "runSetVerificationPolicy");
+ try {
+ final IPackageInstaller installer = mInterface.getPackageInstaller();
+ // TODO(b/360129657): global verification policy should be per user
+ final boolean success = installer.setVerificationPolicy(Integer.parseInt(policyStr));
+ if (!success) {
+ pw.println("Failure setting verification policy.");
+ return 1;
+ }
+ } catch (Exception e) {
+ pw.println("Failure [" + e.getMessage() + "]");
+ return 1;
+ }
+ return 0;
+ }
+
@Override
public void onHelp() {
final PrintWriter pw = getOutPrintWriter();
@@ -5073,6 +5157,14 @@
pw.println(" --user: return the agent of the given user (SYSTEM_USER if unspecified)");
pw.println(" get-package-storage-stats [--user <USER_ID>] <PACKAGE>");
pw.println(" Return the storage stats for the given app, if present");
+ pw.println(" get-verification-policy [--user USER_ID]");
+ pw.println(" Display current verification enforcement policy which will be applied to");
+ pw.println(" all the future installation sessions");
+ pw.println(" --user: show the policy of the given user (SYSTEM_USER if unspecified)");
+ pw.println(" set-verification-policy POLICY [--user USER_ID]");
+ pw.println(" Sets the verification policy of all the future installation sessions.");
+ pw.println(" --user: set the policy of the given user (SYSTEM_USER if unspecified)");
+ pw.println("");
pw.println("");
printArtServiceHelp();
pw.println("");
diff --git a/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java b/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java
index a35618b..78849d2 100644
--- a/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java
+++ b/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java
@@ -18,13 +18,12 @@
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+import static android.os.Process.INVALID_UID;
import static android.os.Process.SYSTEM_UID;
import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE;
-import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.content.ComponentName;
import android.content.Context;
@@ -114,10 +113,17 @@
private final Context mContext;
private final Handler mHandler;
+ // Guards the remote service object, as well as the verifier name and UID, which should all be
+ // changed at the same time.
+ private final Object mLock = new Object();
@Nullable
+ @GuardedBy("mLock")
private ServiceConnector<IVerifierService> mRemoteService;
@Nullable
+ @GuardedBy("mLock")
private ComponentName mRemoteServiceComponentName;
+ @GuardedBy("mLock")
+ private int mRemoteServiceUid = INVALID_UID;
@NonNull
private Injector mInjector;
@@ -143,9 +149,11 @@
*/
@Nullable
public String getVerifierPackageName(Supplier<Computer> snapshotSupplier, int userId) {
- if (isVerifierConnected()) {
- // Verifier is connected or is being connected, so it must be installed.
- return mRemoteServiceComponentName.getPackageName();
+ synchronized (mLock) {
+ if (isVerifierConnectedLocked()) {
+ // Verifier is connected or is being connected, so it must be installed.
+ return mRemoteServiceComponentName.getPackageName();
+ }
}
// Verifier has been disconnected, or it hasn't been connected. Check if it's installed.
return mInjector.getVerifierPackageName(snapshotSupplier.get(), userId);
@@ -178,16 +186,29 @@
}
return true;
}
+ Computer snapshot = snapshotSupplier.get();
Pair<ServiceConnector<IVerifierService>, ComponentName> result =
- mInjector.getRemoteService(snapshotSupplier.get(), mContext, userId, mHandler);
+ mInjector.getRemoteService(snapshot, mContext, userId, mHandler);
if (result == null || result.first == null) {
if (DEBUG) {
Slog.i(TAG, "Unable to find a qualified verifier.");
}
return false;
}
- mRemoteService = result.first;
- mRemoteServiceComponentName = result.second;
+ final int verifierUid = snapshot.getPackageUidInternal(
+ result.second.getPackageName(), 0, userId, /* callingUid= */ SYSTEM_UID);
+ if (verifierUid == INVALID_UID) {
+ if (DEBUG) {
+ Slog.i(TAG, "Unable to find the UID of the qualified verifier.");
+ }
+ return false;
+ }
+ synchronized (mLock) {
+ mRemoteService = result.first;
+ mRemoteServiceComponentName = result.second;
+ mRemoteServiceUid = verifierUid;
+ }
+
if (DEBUG) {
Slog.i(TAG, "Connecting to a qualified verifier: " + mRemoteServiceComponentName);
}
@@ -212,10 +233,13 @@
}
private void destroy() {
- if (isVerifierConnected()) {
- mRemoteService.unbind();
- mRemoteService = null;
- mRemoteServiceComponentName = null;
+ synchronized (mLock) {
+ if (isVerifierConnectedLocked()) {
+ mRemoteService.unbind();
+ mRemoteService = null;
+ mRemoteServiceComponentName = null;
+ mRemoteServiceUid = INVALID_UID;
+ }
}
}
});
@@ -223,7 +247,8 @@
return true;
}
- private boolean isVerifierConnected() {
+ @GuardedBy("mLock")
+ private boolean isVerifierConnectedLocked() {
return mRemoteService != null && mRemoteServiceComponentName != null;
}
@@ -232,19 +257,21 @@
* requested for verification.
*/
public void notifyPackageNameAvailable(@NonNull String packageName) {
- if (!isVerifierConnected()) {
- if (DEBUG) {
- Slog.i(TAG, "Verifier is not connected. Not notifying package name available");
+ synchronized (mLock) {
+ if (!isVerifierConnectedLocked()) {
+ if (DEBUG) {
+ Slog.i(TAG, "Verifier is not connected. Not notifying package name available");
+ }
+ return;
}
- return;
+ // Best effort. We don't check for the result.
+ mRemoteService.run(service -> {
+ if (DEBUG) {
+ Slog.i(TAG, "Notifying package name available for " + packageName);
+ }
+ service.onPackageNameAvailable(packageName);
+ });
}
- // Best effort. We don't check for the result.
- mRemoteService.run(service -> {
- if (DEBUG) {
- Slog.i(TAG, "Notifying package name available for " + packageName);
- }
- service.onPackageNameAvailable(packageName);
- });
}
/**
@@ -253,27 +280,29 @@
* will no longer be requested for verification, possibly because the installation is canceled.
*/
public void notifyVerificationCancelled(@NonNull String packageName) {
- if (!isVerifierConnected()) {
- if (DEBUG) {
- Slog.i(TAG, "Verifier is not connected. Not notifying verification cancelled");
+ synchronized (mLock) {
+ if (!isVerifierConnectedLocked()) {
+ if (DEBUG) {
+ Slog.i(TAG, "Verifier is not connected. Not notifying verification cancelled");
+ }
+ return;
}
- return;
+ // Best effort. We don't check for the result.
+ mRemoteService.run(service -> {
+ if (DEBUG) {
+ Slog.i(TAG, "Notifying verification cancelled for " + packageName);
+ }
+ service.onVerificationCancelled(packageName);
+ });
}
- // Best effort. We don't check for the result.
- mRemoteService.run(service -> {
- if (DEBUG) {
- Slog.i(TAG, "Notifying verification cancelled for " + packageName);
- }
- service.onVerificationCancelled(packageName);
- });
}
/**
* Called to notify the bound verifier agent that a package that's pending installation needs
* to be verified right now.
* <p>The verification request must be sent to the verifier as soon as the verifier is
- * connected. If the connection cannot be made within {@link #CONNECTION_TIMEOUT_SECONDS}</p>
- * of when the request is sent out, we consider the verification to be failed and notify the
+ * connected. If the connection cannot be made within the specified time limit from
+ * when the request is sent out, we consider the verification to be failed and notify the
* installation session.</p>
* <p>If a response is not returned from the verifier agent within a timeout duration from the
* time the request is sent to the verifier, the verification will be considered a failure.</p>
@@ -291,43 +320,48 @@
if (!bindToVerifierServiceIfNeeded(snapshotSupplier, userId)) {
return false;
}
- if (!isVerifierConnected()) {
- if (DEBUG) {
- Slog.i(TAG, "Verifier is not connected. Not notifying verification required");
- }
- // Normally this should not happen because we just tried to bind. But if the verifier
- // just crashed or just became unavailable, we should notify the installation session so
- // it can finish with a verification failure.
- return false;
- }
// For now, the verification id is the same as the installation session id.
final int verificationId = installationSessionId;
- final VerificationSession session = new VerificationSession(
- /* id= */ verificationId,
- /* installSessionId= */ installationSessionId,
- packageName, stagedPackageUri, signingInfo, declaredLibraries, extensionParams,
- verificationPolicy, new VerificationSessionInterface(callback));
- AndroidFuture<Void> unusedFuture = mRemoteService.post(service -> {
- if (!retry) {
+ synchronized (mLock) {
+ if (!isVerifierConnectedLocked()) {
if (DEBUG) {
- Slog.i(TAG, "Notifying verification required for session " + verificationId);
+ Slog.i(TAG, "Verifier is not connected. Not notifying verification required");
}
- service.onVerificationRequired(session);
- } else {
- if (DEBUG) {
- Slog.i(TAG, "Notifying verification retry for session " + verificationId);
+ // Normally this should not happen because we just tried to bind. But if the
+ // verifier just crashed or just became unavailable, we should notify the
+ // installation session so it can finish with a verification failure.
+ return false;
+ }
+ final VerificationSession session = new VerificationSession(
+ /* id= */ verificationId,
+ /* installSessionId= */ installationSessionId,
+ packageName, stagedPackageUri, signingInfo, declaredLibraries, extensionParams,
+ verificationPolicy, new VerificationSessionInterface(callback));
+ AndroidFuture<Void> unusedFuture = mRemoteService.post(service -> {
+ if (!retry) {
+ if (DEBUG) {
+ Slog.i(TAG, "Notifying verification required for session "
+ + verificationId);
+ }
+ service.onVerificationRequired(session);
+ } else {
+ if (DEBUG) {
+ Slog.i(TAG, "Notifying verification retry for session "
+ + verificationId);
+ }
+ service.onVerificationRetry(session);
}
- service.onVerificationRetry(session);
- }
- }).orTimeout(mInjector.getVerifierConnectionTimeoutMillis(), TimeUnit.MILLISECONDS)
- .whenComplete((res, err) -> {
- if (err != null) {
- Slog.e(TAG, "Error notifying verification request for session " + verificationId,
- err);
- // Notify the installation session so it can finish with verification failure.
- callback.onConnectionFailed();
- }
- });
+ }).orTimeout(mInjector.getVerifierConnectionTimeoutMillis(), TimeUnit.MILLISECONDS)
+ .whenComplete((res, err) -> {
+ if (err != null) {
+ Slog.e(TAG, "Error notifying verification request for session "
+ + verificationId, err);
+ // Notify the installation session so it can finish with verification
+ // failure.
+ callback.onConnectionFailed();
+ }
+ });
+ }
// Keep track of the session status with the ID. Start counting down the session timeout.
final long defaultTimeoutMillis = mInjector.getVerificationRequestTimeoutMillis();
final long maxExtendedTimeoutMillis = mInjector.getMaxVerificationExtendedTimeoutMillis();
@@ -369,24 +403,27 @@
* Called to notify the bound verifier agent that a verification request has timed out.
*/
public void notifyVerificationTimeout(int verificationId) {
- if (!isVerifierConnected()) {
- if (DEBUG) {
- Slog.i(TAG,
- "Verifier is not connected. Not notifying timeout for " + verificationId);
+ synchronized (mLock) {
+ if (!isVerifierConnectedLocked()) {
+ if (DEBUG) {
+ Slog.i(TAG,
+ "Verifier is not connected. Not notifying timeout for "
+ + verificationId);
+ }
+ return;
}
- return;
+ AndroidFuture<Void> unusedFuture = mRemoteService.post(service -> {
+ if (DEBUG) {
+ Slog.i(TAG, "Notifying timeout for " + verificationId);
+ }
+ service.onVerificationTimeout(verificationId);
+ }).whenComplete((res, err) -> {
+ if (err != null) {
+ Slog.e(TAG, "Error notifying VerificationTimeout for session "
+ + verificationId, err);
+ }
+ });
}
- AndroidFuture<Void> unusedFuture = mRemoteService.post(service -> {
- if (DEBUG) {
- Slog.i(TAG, "Notifying timeout for " + verificationId);
- }
- service.onVerificationTimeout(verificationId);
- }).whenComplete((res, err) -> {
- if (err != null) {
- Slog.e(TAG, "Error notifying VerificationTimeout for session "
- + verificationId, (Throwable) err);
- }
- });
}
/**
@@ -405,17 +442,19 @@
}
}
- @RequiresPermission(Manifest.permission.VERIFICATION_AGENT)
- private void checkCallerPermission() {
- // TODO: think of a better way to test it on non-eng builds
- if (Build.IS_ENG) {
- return;
- }
- if (mContext.checkCallingOrSelfPermission(Manifest.permission.VERIFICATION_AGENT)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("You need the"
- + " com.android.permission.VERIFICATION_AGENT permission"
- + " to use VerificationSession APIs.");
+ /**
+ * Assert that the calling UID is the same as the UID of the currently connected verifier.
+ */
+ public void assertCallerIsCurrentVerifier(int callingUid) {
+ synchronized (mLock) {
+ if (!isVerifierConnectedLocked()) {
+ throw new IllegalStateException(
+ "Unable to proceed because the verifier has been disconnected.");
+ }
+ if (callingUid != mRemoteServiceUid) {
+ throw new IllegalStateException(
+ "Calling uid " + callingUid + " is not the current verifier.");
+ }
}
}
@@ -429,7 +468,7 @@
@Override
public long getTimeoutTime(int verificationId) {
- checkCallerPermission();
+ assertCallerIsCurrentVerifier(getCallingUid());
synchronized (mVerificationStatus) {
final VerificationStatusTracker tracker = mVerificationStatus.get(verificationId);
if (tracker == null) {
@@ -442,7 +481,7 @@
@Override
public long extendTimeRemaining(int verificationId, long additionalMs) {
- checkCallerPermission();
+ assertCallerIsCurrentVerifier(getCallingUid());
synchronized (mVerificationStatus) {
final VerificationStatusTracker tracker = mVerificationStatus.get(verificationId);
if (tracker == null) {
@@ -456,7 +495,7 @@
@Override
public boolean setVerificationPolicy(int verificationId,
@PackageInstaller.VerificationPolicy int policy) {
- checkCallerPermission();
+ assertCallerIsCurrentVerifier(getCallingUid());
synchronized (mVerificationStatus) {
final VerificationStatusTracker tracker = mVerificationStatus.get(verificationId);
if (tracker == null) {
@@ -469,7 +508,7 @@
@Override
public void reportVerificationIncomplete(int id, int reason) {
- checkCallerPermission();
+ assertCallerIsCurrentVerifier(getCallingUid());
final VerificationStatusTracker tracker;
synchronized (mVerificationStatus) {
tracker = mVerificationStatus.get(id);
@@ -484,15 +523,9 @@
}
@Override
- public void reportVerificationComplete(int id, VerificationStatus verificationStatus) {
- reportVerificationCompleteWithExtensionResponse(id, verificationStatus,
- /* extensionResponse= */ null);
- }
-
- @Override
- public void reportVerificationCompleteWithExtensionResponse(int id,
- VerificationStatus verificationStatus, PersistableBundle extensionResponse) {
- checkCallerPermission();
+ public void reportVerificationComplete(int id, VerificationStatus verificationStatus,
+ @Nullable PersistableBundle extensionResponse) {
+ assertCallerIsCurrentVerifier(getCallingUid());
final VerificationStatusTracker tracker;
synchronized (mVerificationStatus) {
tracker = mVerificationStatus.get(id);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 0b5b0d2..6a7f22e 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3621,7 +3621,9 @@
if (mEnableBugReportKeyboardShortcut && firstDown
&& event.isMetaPressed() && event.isCtrlPressed()) {
try {
- mActivityManagerService.requestInteractiveBugReport();
+ if (!mActivityManagerService.launchBugReportHandlerApp()) {
+ mActivityManagerService.requestInteractiveBugReport();
+ }
} catch (RemoteException e) {
Slog.d(TAG, "Error taking bugreport", e);
}
@@ -4117,7 +4119,9 @@
case KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT:
if (complete && mEnableBugReportKeyboardShortcut) {
try {
- mActivityManagerService.requestInteractiveBugReport();
+ if (!mActivityManagerService.launchBugReportHandlerApp()) {
+ mActivityManagerService.requestInteractiveBugReport();
+ }
} catch (RemoteException e) {
Slog.d(TAG, "Error taking bugreport", e);
}
diff --git a/services/core/java/com/android/server/security/forensic/DataAggregator.java b/services/core/java/com/android/server/security/forensic/DataAggregator.java
new file mode 100644
index 0000000..0079818
--- /dev/null
+++ b/services/core/java/com/android/server/security/forensic/DataAggregator.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.security.forensic;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.security.forensic.ForensicEvent;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.ServiceThread;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DataAggregator {
+ private static final String TAG = "Forensic DataAggregator";
+ private static final int MSG_SINGLE_DATA = 0;
+ private static final int MSG_BATCH_DATA = 1;
+ private static final int MSG_DISABLE = 2;
+
+ private static final int STORED_EVENTS_SIZE_LIMIT = 1024;
+ private final ForensicService mForensicService;
+ private final ArrayList<DataSource> mDataSources;
+
+ private List<ForensicEvent> mStoredEvents = new ArrayList<>();
+ private ServiceThread mHandlerThread;
+ private Handler mHandler;
+ public DataAggregator(ForensicService forensicService) {
+ mForensicService = forensicService;
+ mDataSources = new ArrayList<DataSource>();
+ }
+
+ @VisibleForTesting
+ void setHandler(Looper looper, ServiceThread serviceThread) {
+ mHandlerThread = serviceThread;
+ mHandler = new EventHandler(looper, this);
+ }
+
+ /**
+ * Initialize DataSources
+ * @return Whether the initialization succeeds.
+ */
+ // TODO: Add the corresponding data sources
+ public boolean initialize() {
+ return true;
+ }
+
+ /**
+ * Enable the data collection of all DataSources.
+ */
+ public void enable() {
+ mHandlerThread = new ServiceThread(TAG, android.os.Process.THREAD_PRIORITY_BACKGROUND,
+ /* allowIo */ false);
+ mHandlerThread.start();
+ mHandler = new EventHandler(mHandlerThread.getLooper(), this);
+ for (DataSource ds : mDataSources) {
+ ds.enable();
+ }
+ }
+
+ /**
+ * DataSource calls it to transmit a single event.
+ */
+ public void addSingleData(ForensicEvent event) {
+ mHandler.obtainMessage(MSG_SINGLE_DATA, event).sendToTarget();
+ }
+
+ /**
+ * DataSource calls it to transmit list of events.
+ */
+ public void addBatchData(List<ForensicEvent> events) {
+ mHandler.obtainMessage(MSG_BATCH_DATA, events).sendToTarget();
+ }
+
+ /**
+ * Disable the data collection of all DataSources.
+ */
+ public void disable() {
+ mHandler.obtainMessage(MSG_DISABLE).sendToTarget();
+ }
+
+ private void onNewSingleData(ForensicEvent event) {
+ if (mStoredEvents.size() < STORED_EVENTS_SIZE_LIMIT) {
+ mStoredEvents.add(event);
+ } else {
+ mForensicService.addNewData(mStoredEvents);
+ mStoredEvents = new ArrayList<>();
+ }
+ }
+
+ private void onNewBatchData(List<ForensicEvent> events) {
+ mForensicService.addNewData(events);
+ }
+
+ private void onDisable() {
+ for (DataSource ds : mDataSources) {
+ ds.disable();
+ }
+ mHandlerThread.quitSafely();
+ mHandlerThread = null;
+ }
+
+ private static class EventHandler extends Handler {
+ private final DataAggregator mDataAggregator;
+ EventHandler(Looper looper, DataAggregator dataAggregator) {
+ super(looper);
+ mDataAggregator = dataAggregator;
+ }
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_SINGLE_DATA:
+ mDataAggregator.onNewSingleData((ForensicEvent) msg.obj);
+ break;
+ case MSG_BATCH_DATA:
+ mDataAggregator.onNewBatchData((List<ForensicEvent>) msg.obj);
+ break;
+ case MSG_DISABLE:
+ mDataAggregator.onDisable();
+ break;
+ default:
+ Slog.w(TAG, "Unknown message: " + msg.what);
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/security/forensic/DataSource.java b/services/core/java/com/android/server/security/forensic/DataSource.java
new file mode 100644
index 0000000..da7ee21
--- /dev/null
+++ b/services/core/java/com/android/server/security/forensic/DataSource.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.security.forensic;
+
+public interface DataSource {
+ /**
+ * Enable the data collection.
+ */
+ void enable();
+
+ /**
+ * Disable the data collection.
+ */
+ void disable();
+}
diff --git a/services/core/java/com/android/server/security/forensic/ForensicService.java b/services/core/java/com/android/server/security/forensic/ForensicService.java
index 20c648e..53b07c0 100644
--- a/services/core/java/com/android/server/security/forensic/ForensicService.java
+++ b/services/core/java/com/android/server/security/forensic/ForensicService.java
@@ -22,6 +22,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
+import android.security.forensic.ForensicEvent;
import android.security.forensic.IForensicService;
import android.security.forensic.IForensicServiceCommandCallback;
import android.security.forensic.IForensicServiceStateCallback;
@@ -32,6 +33,7 @@
import com.android.server.SystemService;
import java.util.ArrayList;
+import java.util.List;
/**
* @hide
@@ -64,6 +66,7 @@
private final Context mContext;
private final Handler mHandler;
private final BackupTransportConnection mBackupTransportConnection;
+ private final DataAggregator mDataAggregator;
private final BinderService mBinderService;
private final ArrayList<IForensicServiceStateCallback> mStateMonitors = new ArrayList<>();
@@ -79,6 +82,7 @@
mContext = injector.getContext();
mHandler = new EventHandler(injector.getLooper(), this);
mBackupTransportConnection = injector.getBackupTransportConnection();
+ mDataAggregator = injector.getDataAggregator(this);
mBinderService = new BinderService(this);
}
@@ -167,6 +171,9 @@
Slog.e(TAG, "RemoteException", e);
}
break;
+ case MSG_BACKUP:
+ mService.backup((List<ForensicEvent>) msg.obj);
+ break;
default:
Slog.w(TAG, "Unknown message: " + msg.what);
}
@@ -192,6 +199,10 @@
private void makeVisible(IForensicServiceCommandCallback callback) throws RemoteException {
switch (mState) {
case STATE_INVISIBLE:
+ if (!mDataAggregator.initialize()) {
+ callback.onFailure(ERROR_DATA_SOURCE_UNAVAILABLE);
+ break;
+ }
mState = STATE_VISIBLE;
notifyStateMonitors();
callback.onSuccess();
@@ -227,6 +238,7 @@
callback.onFailure(ERROR_BACKUP_TRANSPORT_UNAVAILABLE);
break;
}
+ mDataAggregator.enable();
mState = STATE_ENABLED;
notifyStateMonitors();
callback.onSuccess();
@@ -243,6 +255,7 @@
switch (mState) {
case STATE_ENABLED:
mBackupTransportConnection.release();
+ mDataAggregator.disable();
mState = STATE_VISIBLE;
notifyStateMonitors();
callback.onSuccess();
@@ -255,6 +268,17 @@
}
}
+ /**
+ * Add a list of ForensicEvent.
+ */
+ public void addNewData(List<ForensicEvent> events) {
+ mHandler.obtainMessage(MSG_BACKUP, events).sendToTarget();
+ }
+
+ private void backup(List<ForensicEvent> events) {
+ mBackupTransportConnection.addData(events);
+ }
+
@Override
public void onStart() {
try {
@@ -275,6 +299,8 @@
Looper getLooper();
BackupTransportConnection getBackupTransportConnection();
+
+ DataAggregator getDataAggregator(ForensicService forensicService);
}
private static final class InjectorImpl implements Injector {
@@ -303,6 +329,11 @@
public BackupTransportConnection getBackupTransportConnection() {
return new BackupTransportConnection(mContext);
}
+
+ @Override
+ public DataAggregator getDataAggregator(ForensicService forensicService) {
+ return new DataAggregator(forensicService);
+ }
}
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index b87867a..6baab25 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -3037,8 +3037,13 @@
|| context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_WIFI_RTT)) {
t.traceBegin("RangingService");
- mSystemServiceManager.startServiceFromJar(RANGING_SERVICE_CLASS,
- RANGING_APEX_SERVICE_JAR_PATH);
+ // TODO: b/375264320 - Remove after RELEASE_RANGING_STACK is ramped to next.
+ try {
+ mSystemServiceManager.startServiceFromJar(RANGING_SERVICE_CLASS,
+ RANGING_APEX_SERVICE_JAR_PATH);
+ } catch (Throwable e) {
+ Slog.d(TAG, "service-ranging.jar not found, not starting RangingService");
+ }
t.traceEnd();
}
}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/verify/pkg/VerifierControllerTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/verify/pkg/VerifierControllerTest.java
index 2461798..3046d4b 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/verify/pkg/VerifierControllerTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/verify/pkg/VerifierControllerTest.java
@@ -24,6 +24,7 @@
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.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -49,6 +50,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
import com.android.internal.infra.AndroidFuture;
import com.android.internal.infra.ServiceConnector;
@@ -122,6 +124,10 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ // Mock that the UID of this test becomes the UID of the verifier
+ when(mSnapshot.getPackageUidInternal(anyString(), anyLong(), anyInt(), anyInt()))
+ .thenReturn(InstrumentationRegistry.getInstrumentation().getContext()
+ .getApplicationInfo().uid);
when(mInjector.getVerifierPackageName(any(Computer.class), anyInt())).thenReturn(
TEST_VERIFIER_COMPONENT_NAME.getPackageName());
when(mInjector.getRemoteService(
diff --git a/services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java b/services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java
index 2b55303..feb00e7 100644
--- a/services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java
+++ b/services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java
@@ -19,23 +19,35 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
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.annotation.SuppressLint;
import android.content.Context;
import android.os.Looper;
import android.os.RemoteException;
import android.os.test.TestLooper;
+import android.security.forensic.ForensicEvent;
import android.security.forensic.IForensicServiceCommandCallback;
import android.security.forensic.IForensicServiceStateCallback;
+import android.util.ArrayMap;
+
+import com.android.server.ServiceThread;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
public class ForensicServiceTest {
private static final int STATE_UNKNOWN = IForensicServiceStateCallback.State.UNKNOWN;
private static final int STATE_INVISIBLE = IForensicServiceStateCallback.State.INVISIBLE;
@@ -55,10 +67,12 @@
@Mock
private Context mContext;
private BackupTransportConnection mBackupTransportConnection;
-
+ private DataAggregator mDataAggregator;
private ForensicService mForensicService;
private TestLooper mTestLooper;
private Looper mLooper;
+ private TestLooper mTestLooperOfDataAggregator;
+ private Looper mLooperOfDataAggregator;
@SuppressLint("VisibleForTests")
@Before
@@ -67,6 +81,8 @@
mTestLooper = new TestLooper();
mLooper = mTestLooper.getLooper();
+ mTestLooperOfDataAggregator = new TestLooper();
+ mLooperOfDataAggregator = mTestLooperOfDataAggregator.getLooper();
mForensicService = new ForensicService(new MockInjector(mContext));
mForensicService.onStart();
}
@@ -121,6 +137,8 @@
assertEquals(STATE_INVISIBLE, scb1.mState);
assertEquals(STATE_INVISIBLE, scb2.mState);
+ doReturn(true).when(mDataAggregator).initialize();
+
CommandCallback ccb = new CommandCallback();
mForensicService.getBinderService().makeVisible(ccb);
mTestLooper.dispatchAll();
@@ -130,6 +148,29 @@
}
@Test
+ public void testMakeVisible_FromInvisible_TwoMonitors_DataSourceUnavailable()
+ throws RemoteException {
+ mForensicService.setState(STATE_INVISIBLE);
+ StateCallback scb1 = new StateCallback();
+ StateCallback scb2 = new StateCallback();
+ mForensicService.getBinderService().monitorState(scb1);
+ mForensicService.getBinderService().monitorState(scb2);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_INVISIBLE, scb1.mState);
+ assertEquals(STATE_INVISIBLE, scb2.mState);
+
+ doReturn(false).when(mDataAggregator).initialize();
+
+ CommandCallback ccb = new CommandCallback();
+ mForensicService.getBinderService().makeVisible(ccb);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_INVISIBLE, scb1.mState);
+ assertEquals(STATE_INVISIBLE, scb2.mState);
+ assertNotNull(ccb.mErrorCode);
+ assertEquals(ERROR_DATA_SOURCE_UNAVAILABLE, ccb.mErrorCode.intValue());
+ }
+
+ @Test
public void testMakeVisible_FromVisible_TwoMonitors() throws RemoteException {
mForensicService.setState(STATE_VISIBLE);
StateCallback scb1 = new StateCallback();
@@ -262,6 +303,8 @@
CommandCallback ccb = new CommandCallback();
mForensicService.getBinderService().enable(ccb);
mTestLooper.dispatchAll();
+
+ verify(mDataAggregator, times(1)).enable();
assertEquals(STATE_ENABLED, scb1.mState);
assertEquals(STATE_ENABLED, scb2.mState);
assertNull(ccb.mErrorCode);
@@ -361,14 +404,67 @@
doNothing().when(mBackupTransportConnection).release();
+ ServiceThread mockThread = spy(ServiceThread.class);
+ mDataAggregator.setHandler(mLooperOfDataAggregator, mockThread);
+
CommandCallback ccb = new CommandCallback();
mForensicService.getBinderService().disable(ccb);
mTestLooper.dispatchAll();
+ mTestLooperOfDataAggregator.dispatchAll();
+ // TODO: We can verify the data sources once we implement them.
+ verify(mockThread, times(1)).quitSafely();
assertEquals(STATE_VISIBLE, scb1.mState);
assertEquals(STATE_VISIBLE, scb2.mState);
assertNull(ccb.mErrorCode);
}
+ @Test
+ public void testDataAggregator_AddBatchData() {
+ mForensicService.setState(STATE_ENABLED);
+ ServiceThread mockThread = spy(ServiceThread.class);
+ mDataAggregator.setHandler(mLooperOfDataAggregator, mockThread);
+
+ String eventOneType = "event_one_type";
+ String eventOneMapKey = "event_one_map_key";
+ String eventOneMapVal = "event_one_map_val";
+ Map<String, String> eventOneMap = new ArrayMap<String, String>();
+ eventOneMap.put(eventOneMapKey, eventOneMapVal);
+ ForensicEvent eventOne = new ForensicEvent(eventOneType, eventOneMap);
+
+ String eventTwoType = "event_two_type";
+ String eventTwoMapKey = "event_two_map_key";
+ String eventTwoMapVal = "event_two_map_val";
+ Map<String, String> eventTwoMap = new ArrayMap<String, String>();
+ eventTwoMap.put(eventTwoMapKey, eventTwoMapVal);
+ ForensicEvent eventTwo = new ForensicEvent(eventTwoType, eventTwoMap);
+
+ List<ForensicEvent> events = new ArrayList<>();
+ events.add(eventOne);
+ events.add(eventTwo);
+
+ doReturn(true).when(mBackupTransportConnection).addData(any());
+
+ mDataAggregator.addBatchData(events);
+ mTestLooperOfDataAggregator.dispatchAll();
+ mTestLooper.dispatchAll();
+
+ ArgumentCaptor<List<ForensicEvent>> captor = ArgumentCaptor.forClass(List.class);
+ verify(mBackupTransportConnection).addData(captor.capture());
+ List<ForensicEvent> receivedEvents = captor.getValue();
+ assertEquals(receivedEvents.size(), 2);
+
+ assertEquals(receivedEvents.getFirst().getType(), eventOneType);
+ assertEquals(receivedEvents.getFirst().getKeyValuePairs().size(), 1);
+ assertEquals(receivedEvents.getFirst().getKeyValuePairs().get(eventOneMapKey),
+ eventOneMapVal);
+
+ assertEquals(receivedEvents.getLast().getType(), eventTwoType);
+ assertEquals(receivedEvents.getLast().getKeyValuePairs().size(), 1);
+ assertEquals(receivedEvents.getLast().getKeyValuePairs().get(eventTwoMapKey),
+ eventTwoMapVal);
+
+ }
+
private class MockInjector implements ForensicService.Injector {
private final Context mContext;
@@ -393,6 +489,11 @@
return mBackupTransportConnection;
}
+ @Override
+ public DataAggregator getDataAggregator(ForensicService forensicService) {
+ mDataAggregator = spy(new DataAggregator(forensicService));
+ return mDataAggregator;
+ }
}
private static class StateCallback extends IForensicServiceStateCallback.Stub {
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 62670b4..6596ee9 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -822,9 +822,9 @@
void assertTakeBugreport(boolean wasCalled) throws RemoteException {
mTestLooper.dispatchAll();
if (wasCalled) {
- verify(mActivityManagerService).requestInteractiveBugReport();
+ verify(mActivityManagerService).launchBugReportHandlerApp();
} else {
- verify(mActivityManagerService, never()).requestInteractiveBugReport();
+ verify(mActivityManagerService, never()).launchBugReportHandlerApp();
}
}
diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java
index f803717..7adcd46 100644
--- a/telecomm/java/android/telecom/Conference.java
+++ b/telecomm/java/android/telecom/Conference.java
@@ -101,7 +101,7 @@
private Set<String> mPreviousExtraKeys;
private final Object mExtrasLock = new Object();
private Uri mAddress;
- private int mAddressPresentation;
+ private int mAddressPresentation = TelecomManager.PRESENTATION_UNKNOWN;
private String mCallerDisplayName;
private int mCallerDisplayNamePresentation;
private int mCallDirection;
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index ad7d987..29d3942 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -2164,7 +2164,7 @@
private CallAudioState mCallAudioState;
private CallEndpoint mCallEndpoint;
private Uri mAddress;
- private int mAddressPresentation;
+ private int mAddressPresentation = TelecomManager.PRESENTATION_UNKNOWN;
private String mCallerDisplayName;
private int mCallerDisplayNamePresentation;
private boolean mRingbackRequested = false;