Merge "Remove period from split unsupported toast." into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index ab0d5a3..3a772e1 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -61,6 +61,7 @@
":service-jobscheduler-deviceidle.flags-aconfig-java{.generated_srcjars}",
":surfaceflinger_flags_java_lib{.generated_srcjars}",
":android.view.contentcapture.flags-aconfig-java{.generated_srcjars}",
+ ":android.hardware.usb.flags-aconfig-java{.generated_srcjars}",
]
filegroup {
@@ -681,3 +682,16 @@
aconfig_declarations: "android.view.contentcapture.flags-aconfig",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+
+// USB
+aconfig_declarations {
+ name: "android.hardware.usb.flags-aconfig",
+ package: "android.hardware.usb.flags",
+ srcs: ["core/java/android/hardware/usb/flags/*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.hardware.usb.flags-aconfig-java",
+ aconfig_declarations: "android.hardware.usb.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 7d38377..12f455a 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -2539,6 +2539,38 @@
pw.println("]");
pw.println();
+ pw.println("mActiveAdminApps=[");
+ synchronized (mActiveAdminApps) {
+ final int size = mActiveAdminApps.size();
+ for (int i = 0; i < size; ++i) {
+ final int userId = mActiveAdminApps.keyAt(i);
+ pw.print(" ");
+ pw.print(userId);
+ pw.print(": ");
+ pw.print(mActiveAdminApps.valueAt(i));
+ if (i != size - 1) pw.print(",");
+ pw.println();
+ }
+ }
+ pw.println("]");
+ pw.println();
+
+ pw.println("mAdminProtectedPackages=[");
+ synchronized (mAdminProtectedPackages) {
+ final int size = mAdminProtectedPackages.size();
+ for (int i = 0; i < size; ++i) {
+ final int userId = mAdminProtectedPackages.keyAt(i);
+ pw.print(" ");
+ pw.print(userId);
+ pw.print(": ");
+ pw.print(mAdminProtectedPackages.valueAt(i));
+ if (i != size - 1) pw.print(",");
+ pw.println();
+ }
+ }
+ pw.println("]");
+ pw.println();
+
mInjector.dump(pw);
}
diff --git a/cmds/gpu_counter_producer/main.cpp b/cmds/gpu_counter_producer/main.cpp
index 1054cba..4616638 100644
--- a/cmds/gpu_counter_producer/main.cpp
+++ b/cmds/gpu_counter_producer/main.cpp
@@ -133,6 +133,12 @@
daemon(0, 0);
}
+ if (getenv("LD_LIBRARY_PATH") == nullptr) {
+ setenv("LD_LIBRARY_PATH", "/vendor/lib64:/vendor/lib", 0 /*override*/);
+ LOG_INFO("execv with: LD_LIBRARY_PATH=%s", getenv("LD_LIBRARY_PATH"));
+ execvpe(pname, argv, environ);
+ }
+
if (!writeToPidFile()) {
LOG_ERR("Could not open %s", kPidFileName);
return 1;
diff --git a/core/api/current.txt b/core/api/current.txt
index e8a6ac9..87f5b3c 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -18634,7 +18634,7 @@
method @Deprecated @Nullable public android.security.identity.IdentityCredential getIdentityCredential();
method @FlaggedApi("android.hardware.biometrics.add_key_agreement_crypto_object") @Nullable public javax.crypto.KeyAgreement getKeyAgreement();
method @Nullable public javax.crypto.Mac getMac();
- method @FlaggedApi("android.hardware.biometrics.get_op_id_crypto_object") public long getOpId();
+ method @FlaggedApi("android.hardware.biometrics.get_op_id_crypto_object") public long getOperationHandle();
method @Nullable public android.security.identity.PresentationSession getPresentationSession();
method @Nullable public java.security.Signature getSignature();
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 2a3593b..739fdc5 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -368,6 +368,7 @@
field public static final String SYSTEM_APPLICATION_OVERLAY = "android.permission.SYSTEM_APPLICATION_OVERLAY";
field public static final String SYSTEM_CAMERA = "android.permission.SYSTEM_CAMERA";
field public static final String TETHER_PRIVILEGED = "android.permission.TETHER_PRIVILEGED";
+ field @FlaggedApi("com.android.net.thread.flags.thread_enabled") public static final String THREAD_NETWORK_PRIVILEGED = "android.permission.THREAD_NETWORK_PRIVILEGED";
field public static final String TIS_EXTENSION_INTERFACE = "android.permission.TIS_EXTENSION_INTERFACE";
field public static final String TOGGLE_AUTOMOTIVE_PROJECTION = "android.permission.TOGGLE_AUTOMOTIVE_PROJECTION";
field public static final String TRIGGER_LOST_MODE = "android.permission.TRIGGER_LOST_MODE";
@@ -6188,8 +6189,13 @@
method public void writeToParcel(android.os.Parcel, int);
field public static final int COMPLIANCE_WARNING_BC_1_2 = 3; // 0x3
field public static final int COMPLIANCE_WARNING_DEBUG_ACCESSORY = 2; // 0x2
+ field @FlaggedApi("android.hardware.usb.flags.enable_usb_data_compliance_warning") public static final int COMPLIANCE_WARNING_ENUMERATION_FAIL = 7; // 0x7
+ field @FlaggedApi("android.hardware.usb.flags.enable_usb_data_compliance_warning") public static final int COMPLIANCE_WARNING_FLAKY_CONNECTION = 8; // 0x8
+ field @FlaggedApi("android.hardware.usb.flags.enable_usb_data_compliance_warning") public static final int COMPLIANCE_WARNING_INPUT_POWER_LIMITED = 5; // 0x5
+ field @FlaggedApi("android.hardware.usb.flags.enable_usb_data_compliance_warning") public static final int COMPLIANCE_WARNING_MISSING_DATA_LINES = 6; // 0x6
field public static final int COMPLIANCE_WARNING_MISSING_RP = 4; // 0x4
field public static final int COMPLIANCE_WARNING_OTHER = 1; // 0x1
+ field @FlaggedApi("android.hardware.usb.flags.enable_usb_data_compliance_warning") public static final int COMPLIANCE_WARNING_UNRELIABLE_IO = 9; // 0x9
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.usb.UsbPortStatus> CREATOR;
field public static final int DATA_ROLE_DEVICE = 2; // 0x2
field public static final int DATA_ROLE_HOST = 1; // 0x1
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index b2bfda1..8b20720 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3592,8 +3592,8 @@
field public static final int ACCESSIBILITY_TITLE_CHANGED = 33554432; // 0x2000000
field public static final int FLAG_SLIPPERY = 536870912; // 0x20000000
field public CharSequence accessibilityTitle;
- field @FlaggedApi("android.view.flags.wm_display_refresh_rate_test") public float preferredMaxDisplayRefreshRate;
- field @FlaggedApi("android.view.flags.wm_display_refresh_rate_test") public float preferredMinDisplayRefreshRate;
+ field public float preferredMaxDisplayRefreshRate;
+ field public float preferredMinDisplayRefreshRate;
field public int privateFlags;
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 3b6ea14..c136db6 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -378,6 +378,15 @@
/** Maps from activity token to the pending override configuration. */
@GuardedBy("mPendingOverrideConfigs")
private final ArrayMap<IBinder, Configuration> mPendingOverrideConfigs = new ArrayMap<>();
+
+ /**
+ * A queue of pending ApplicationInfo updates. In case when we get a concurrent update
+ * this queue allows us to only apply the latest object, and it can be applied on demand
+ * instead of waiting for the handler thread to reach the scheduled callback.
+ */
+ @GuardedBy("mResourcesManager")
+ private final ArrayMap<String, ApplicationInfo> mPendingAppInfoUpdates = new ArrayMap<>();
+
/** The activities to be truly destroyed (not include relaunch). */
final Map<IBinder, DestroyActivityItem> mActivitiesToBeDestroyed =
Collections.synchronizedMap(new ArrayMap<>());
@@ -1326,9 +1335,19 @@
}
public void scheduleApplicationInfoChanged(ApplicationInfo ai) {
+ synchronized (mResourcesManager) {
+ var oldAi = mPendingAppInfoUpdates.put(ai.packageName, ai);
+ if (oldAi != null && oldAi.createTimestamp > ai.createTimestamp) {
+ Slog.w(TAG, "Skipping application info changed for obsolete AI with TS "
+ + ai.createTimestamp + " < already pending TS "
+ + oldAi.createTimestamp);
+ mPendingAppInfoUpdates.put(ai.packageName, oldAi);
+ return;
+ }
+ }
mResourcesManager.appendPendingAppInfoUpdate(new String[]{ai.sourceDir}, ai);
- mH.removeMessages(H.APPLICATION_INFO_CHANGED, ai);
- sendMessage(H.APPLICATION_INFO_CHANGED, ai);
+ mH.removeMessages(H.APPLICATION_INFO_CHANGED, ai.packageName);
+ sendMessage(H.APPLICATION_INFO_CHANGED, ai.packageName);
}
public void updateTimeZone() {
@@ -2507,7 +2526,7 @@
break;
}
case APPLICATION_INFO_CHANGED:
- handleApplicationInfoChanged((ApplicationInfo) msg.obj);
+ applyPendingApplicationInfoChanges((String) msg.obj);
break;
case RUN_ISOLATED_ENTRY_POINT:
handleRunIsolatedEntryPoint((String) ((SomeArgs) msg.obj).arg1,
@@ -4070,7 +4089,8 @@
mProfiler.startProfiling();
}
- // Make sure we are running with the most recent config.
+ // Make sure we are running with the most recent config and resource paths.
+ applyPendingApplicationInfoChanges(r.activityInfo.packageName);
mConfigurationController.handleConfigurationChanged(null, null);
updateDeviceIdForNonUIContexts(deviceId);
@@ -6438,6 +6458,17 @@
r.mLastReportedWindowingMode = newWindowingMode;
}
+ private void applyPendingApplicationInfoChanges(String packageName) {
+ final ApplicationInfo ai;
+ synchronized (mResourcesManager) {
+ ai = mPendingAppInfoUpdates.remove(packageName);
+ }
+ if (ai == null) {
+ return;
+ }
+ handleApplicationInfoChanged(ai);
+ }
+
/**
* Updates the application info.
*
@@ -6463,6 +6494,16 @@
apk = ref != null ? ref.get() : null;
ref = mResourcePackages.get(ai.packageName);
resApk = ref != null ? ref.get() : null;
+ for (ActivityClientRecord ar : mActivities.values()) {
+ if (ar.activityInfo.applicationInfo.packageName.equals(ai.packageName)) {
+ ar.activityInfo.applicationInfo = ai;
+ if (apk != null || resApk != null) {
+ ar.packageInfo = apk != null ? apk : resApk;
+ } else {
+ apk = ar.packageInfo;
+ }
+ }
+ }
}
if (apk != null) {
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index ebf183f..f1e44cc 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -342,7 +342,9 @@
*/
public void updateApplicationInfo(@NonNull ApplicationInfo aInfo,
@Nullable List<String> oldPaths) {
- setApplicationInfo(aInfo);
+ if (!setApplicationInfo(aInfo)) {
+ return;
+ }
final List<String> newPaths = new ArrayList<>();
makePaths(mActivityThread, aInfo, newPaths);
@@ -387,7 +389,13 @@
mAppComponentFactory = createAppFactory(aInfo, mDefaultClassLoader);
}
- private void setApplicationInfo(ApplicationInfo aInfo) {
+ private boolean setApplicationInfo(ApplicationInfo aInfo) {
+ if (mApplicationInfo != null && mApplicationInfo.createTimestamp > aInfo.createTimestamp) {
+ Slog.w(TAG, "New application info for package " + aInfo.packageName
+ + " is out of date with TS " + aInfo.createTimestamp + " < the current TS "
+ + mApplicationInfo.createTimestamp);
+ return false;
+ }
final int myUid = Process.myUid();
aInfo = adjustNativeLibraryPaths(aInfo);
mApplicationInfo = aInfo;
@@ -410,6 +418,7 @@
if (aInfo.requestsIsolatedSplitLoading() && !ArrayUtils.isEmpty(mSplitNames)) {
mSplitLoader = new SplitDependencyLoaderImpl(aInfo.splitDependencies);
}
+ return true;
}
void setSdkSandboxStorage(@Nullable String sdkSandboxClientAppVolumeUuid,
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 43cf97f..9ec082a 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -35,3 +35,10 @@
description: "Further framework support for communal profile, beyond the basics, for later releases."
bug: "285426179"
}
+
+flag {
+ name: "use_all_cpus_during_user_switch"
+ namespace: "multiuser"
+ description: "Allow using all cpu cores during a user switch."
+ bug: "308105403"
+}
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 97bbfbb..8c1ea5f 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -857,7 +857,7 @@
* Get the operation handle associated with this object or 0 if none.
*/
@FlaggedApi(FLAG_GET_OP_ID_CRYPTO_OBJECT)
- public long getOpId() {
+ public long getOperationHandle() {
return super.getOpId();
}
}
diff --git a/core/java/android/hardware/usb/UsbPort.java b/core/java/android/hardware/usb/UsbPort.java
index 490b128..8f0149b 100644
--- a/core/java/android/hardware/usb/UsbPort.java
+++ b/core/java/android/hardware/usb/UsbPort.java
@@ -52,6 +52,11 @@
import static android.hardware.usb.UsbPortStatus.COMPLIANCE_WARNING_BC_1_2;
import static android.hardware.usb.UsbPortStatus.COMPLIANCE_WARNING_MISSING_RP;
import static android.hardware.usb.UsbPortStatus.COMPLIANCE_WARNING_OTHER;
+import static android.hardware.usb.UsbPortStatus.COMPLIANCE_WARNING_INPUT_POWER_LIMITED;
+import static android.hardware.usb.UsbPortStatus.COMPLIANCE_WARNING_MISSING_DATA_LINES;
+import static android.hardware.usb.UsbPortStatus.COMPLIANCE_WARNING_ENUMERATION_FAIL;
+import static android.hardware.usb.UsbPortStatus.COMPLIANCE_WARNING_FLAKY_CONNECTION;
+import static android.hardware.usb.UsbPortStatus.COMPLIANCE_WARNING_UNRELIABLE_IO;
import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_UNKNOWN;
import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_NOT_CAPABLE;
import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED;
@@ -789,6 +794,21 @@
case UsbPortStatus.COMPLIANCE_WARNING_MISSING_RP:
complianceWarningString.append("missing rp, ");
break;
+ case UsbPortStatus.COMPLIANCE_WARNING_INPUT_POWER_LIMITED:
+ complianceWarningString.append("input power limited, ");
+ break;
+ case UsbPortStatus.COMPLIANCE_WARNING_MISSING_DATA_LINES:
+ complianceWarningString.append("missing data lines, ");
+ break;
+ case UsbPortStatus.COMPLIANCE_WARNING_ENUMERATION_FAIL:
+ complianceWarningString.append("enumeration fail, ");
+ break;
+ case UsbPortStatus.COMPLIANCE_WARNING_FLAKY_CONNECTION:
+ complianceWarningString.append("flaky connection, ");
+ break;
+ case UsbPortStatus.COMPLIANCE_WARNING_UNRELIABLE_IO:
+ complianceWarningString.append("unreliable io, ");
+ break;
default:
complianceWarningString.append(String.format("Unknown(%d), ", warning));
break;
diff --git a/core/java/android/hardware/usb/UsbPortStatus.java b/core/java/android/hardware/usb/UsbPortStatus.java
index b4fe3a2..d959240 100644
--- a/core/java/android/hardware/usb/UsbPortStatus.java
+++ b/core/java/android/hardware/usb/UsbPortStatus.java
@@ -18,11 +18,13 @@
import android.Manifest;
import android.annotation.CheckResult;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
+import android.hardware.usb.flags.Flags;
import android.os.Parcel;
import android.os.Parcelable;
@@ -310,6 +312,54 @@
public static final int COMPLIANCE_WARNING_MISSING_RP = 4;
/**
+ * Used to indicate the charging setups on the USB ports are unable to
+ * deliver negotiated power. Introduced in Android V (API level 35)
+ * and client applicantions that target API levels lower than 35 will
+ * receive {@link #COMPLIANCE_WARNING_OTHER} instead.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_USB_DATA_COMPLIANCE_WARNING)
+ public static final int COMPLIANCE_WARNING_INPUT_POWER_LIMITED = 5;
+
+ /**
+ * Used to indicate the cable/connector on the USB ports are missing
+ * the required wires on the data pins to make data transfer.
+ * Introduced in Android V (API level 35) and client applicantions that
+ * target API levels lower than 35 will receive
+ * {@link #COMPLIANCE_WARNING_OTHER} instead.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_USB_DATA_COMPLIANCE_WARNING)
+ public static final int COMPLIANCE_WARNING_MISSING_DATA_LINES = 6;
+
+ /**
+ * Used to indicate enumeration failures on the USB ports, potentially due to
+ * signal integrity issues or other causes. Introduced in Android V
+ * (API level 35) and client applicantions that target API levels lower
+ * than 35 will receive {@link #COMPLIANCE_WARNING_OTHER} instead.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_USB_DATA_COMPLIANCE_WARNING)
+ public static final int COMPLIANCE_WARNING_ENUMERATION_FAIL = 7;
+
+ /**
+ * Used to indicate unexpected data disconnection on the USB ports,
+ * potentially due to signal integrity issues or other causes.
+ * Introduced in Android V (API level 35) and client applicantions that
+ * target API levels lower than 35 will receive
+ * {@link #COMPLIANCE_WARNING_OTHER} instead.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_USB_DATA_COMPLIANCE_WARNING)
+ public static final int COMPLIANCE_WARNING_FLAKY_CONNECTION = 8;
+
+ /**
+ * Used to indicate unreliable or slow data transfer on the USB ports,
+ * potentially due to signal integrity issues or other causes.
+ * Introduced in Android V (API level 35) and client applicantions that
+ * target API levels lower than 35 will receive
+ * {@link #COMPLIANCE_WARNING_OTHER} instead.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_USB_DATA_COMPLIANCE_WARNING)
+ public static final int COMPLIANCE_WARNING_UNRELIABLE_IO = 9;
+
+ /**
* Indicates that the Type-C plug orientation cannot be
* determined because the connected state of the device is unknown.
*/
@@ -372,6 +422,11 @@
COMPLIANCE_WARNING_DEBUG_ACCESSORY,
COMPLIANCE_WARNING_BC_1_2,
COMPLIANCE_WARNING_MISSING_RP,
+ COMPLIANCE_WARNING_INPUT_POWER_LIMITED,
+ COMPLIANCE_WARNING_MISSING_DATA_LINES,
+ COMPLIANCE_WARNING_ENUMERATION_FAIL,
+ COMPLIANCE_WARNING_FLAKY_CONNECTION,
+ COMPLIANCE_WARNING_UNRELIABLE_IO,
})
@Retention(RetentionPolicy.SOURCE)
@interface ComplianceWarning{}
@@ -591,7 +646,12 @@
* @return array including {@link #COMPLIANCE_WARNING_OTHER},
* {@link #COMPLIANCE_WARNING_DEBUG_ACCESSORY},
* {@link #COMPLIANCE_WARNING_BC_1_2},
- * or {@link #COMPLIANCE_WARNING_MISSING_RP}
+ * {@link #COMPLIANCE_WARNING_MISSING_RP},
+ * {@link #COMPLIANCE_WARNING_INPUT_POWER_LIMITED},
+ * {@link #COMPLIANCE_WARNING_MISSING_DATA_LINES},
+ * {@link #COMPLIANCE_WARNING_ENUMERATION_FAIL},
+ * {@link #COMPLIANCE_WARNING_FLAKY_CONNECTION},
+ * {@link #COMPLIANCE_WARNING_UNRELIABLE_IO}.
*/
@CheckResult
@NonNull
diff --git a/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig b/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
new file mode 100644
index 0000000..6b78d05
--- /dev/null
+++ b/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.hardware.usb.flags"
+
+flag {
+ name: "enable_usb_data_compliance_warning"
+ namespace: "system_sw_usb"
+ description: "Enable USB data compliance warnings when set"
+ bug: "296119135"
+}
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 47b6d8d..94f90cc 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -2018,9 +2018,13 @@
return;
}
+ // Temporarily disable checks so that explicit GC is allowed.
+ final int oldMask = getThreadPolicyMask();
+ setThreadPolicyMask(0);
System.gc();
System.runFinalization();
System.gc();
+ setThreadPolicyMask(oldMask);
// Note: classInstanceLimit is immutable, so this is lock-free
// Create the classes array.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index c9c1f20..d97dfb0 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -123,6 +123,7 @@
import android.graphics.BLASTBufferQueue;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.ForceDarkType;
import android.graphics.FrameInfo;
import android.graphics.HardwareRenderer;
import android.graphics.HardwareRenderer.FrameDrawingCallback;
@@ -1796,7 +1797,7 @@
/** Returns true if force dark should be enabled according to various settings */
@VisibleForTesting
- public boolean isForceDarkEnabled() {
+ public @ForceDarkType.ForceDarkTypeDef int determineForceDarkType() {
if (forceInvertColor()) {
boolean isForceInvertEnabled = Settings.Secure.getIntForUser(
mContext.getContentResolver(),
@@ -1808,7 +1809,7 @@
// for dark mode in configuration.uiMode. Instead, we assume that the force invert
// setting will be enabled at the same time dark theme is in the Settings app.
if (isForceInvertEnabled) {
- return true;
+ return ForceDarkType.FORCE_INVERT_COLOR_DARK;
}
}
@@ -1822,12 +1823,12 @@
&& a.getBoolean(R.styleable.Theme_forceDarkAllowed, forceDarkAllowedDefault);
a.recycle();
}
- return useAutoDark;
+ return useAutoDark ? ForceDarkType.FORCE_DARK : ForceDarkType.NONE;
}
private void updateForceDarkMode() {
if (mAttachInfo.mThreadedRenderer == null) return;
- if (mAttachInfo.mThreadedRenderer.setForceDark(isForceDarkEnabled())) {
+ if (mAttachInfo.mThreadedRenderer.setForceDark(determineForceDarkType())) {
// TODO: Don't require regenerating all display lists to apply this setting
invalidateWorld(mView);
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index cfec081..c735142 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -17,7 +17,6 @@
package android.view;
import static android.content.pm.ActivityInfo.COLOR_MODE_DEFAULT;
-import static android.view.flags.Flags.FLAG_WM_DISPLAY_REFRESH_RATE_TEST;
import static android.view.View.STATUS_BAR_DISABLE_BACK;
import static android.view.View.STATUS_BAR_DISABLE_CLOCK;
import static android.view.View.STATUS_BAR_DISABLE_EXPAND;
@@ -3907,7 +3906,7 @@
* This value is ignored if {@link #preferredDisplayModeId} is set.
* @hide
*/
- @FlaggedApi(FLAG_WM_DISPLAY_REFRESH_RATE_TEST)
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
@TestApi
public float preferredMinDisplayRefreshRate;
@@ -3917,7 +3916,7 @@
* This value is ignored if {@link #preferredDisplayModeId} is set.
* @hide
*/
- @FlaggedApi(FLAG_WM_DISPLAY_REFRESH_RATE_TEST)
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
@TestApi
public float preferredMaxDisplayRefreshRate;
diff --git a/core/java/android/view/flags/refresh_rate_flags.aconfig b/core/java/android/view/flags/refresh_rate_flags.aconfig
index cc951cf..a467afe 100644
--- a/core/java/android/view/flags/refresh_rate_flags.aconfig
+++ b/core/java/android/view/flags/refresh_rate_flags.aconfig
@@ -42,11 +42,4 @@
namespace: "core_graphics"
description: "Enable the `setFrameRate` callback"
bug: "299946220"
-}
-
-flag {
- name: "wm_display_refresh_rate_test"
- namespace: "core_graphics"
- description: "Adds WindowManager display refresh rate fields to test API"
- bug: "304475199"
}
\ No newline at end of file
diff --git a/core/java/android/window/flags/responsible_apis.aconfig b/core/java/android/window/flags/responsible_apis.aconfig
index 4bfb177..94e6009 100644
--- a/core/java/android/window/flags/responsible_apis.aconfig
+++ b/core/java/android/window/flags/responsible_apis.aconfig
@@ -19,4 +19,11 @@
namespace: "responsible_apis"
description: "Enable toasts to indicate (potential) BAL blocking."
bug: "308059069"
+}
+
+flag {
+ name: "bal_show_toasts_blocked"
+ namespace: "responsible_apis"
+ description: "Enable toasts to indicate actual BAL blocking."
+ bug: "308059069"
}
\ No newline at end of file
diff --git a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
index 08de4dfb..be3f10a 100644
--- a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
+++ b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
@@ -287,7 +287,6 @@
updatedView = mInflater.inflate(
R.layout.app_language_picker_current_locale_item,
parent, false);
- addStateDescriptionIntoCurrentLocaleItem(updatedView);
}
} else {
shouldReuseView = convertView instanceof TextView
@@ -304,7 +303,6 @@
if (!shouldReuseView) {
updatedView = mInflater.inflate(
R.layout.app_language_picker_current_locale_item, parent, false);
- addStateDescriptionIntoCurrentLocaleItem(updatedView);
}
break;
default:
@@ -441,9 +439,4 @@
: View.TEXT_DIRECTION_LTR);
}
}
-
- private void addStateDescriptionIntoCurrentLocaleItem(View root) {
- String description = root.getContext().getResources().getString(R.string.checked);
- root.setStateDescription(description);
- }
}
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index e014ab0..6c17e9e 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -65,6 +65,7 @@
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemProperties;
import android.provider.Settings;
import android.text.TextUtils;
import android.transition.Scene;
@@ -183,6 +184,12 @@
private static final Transition USE_DEFAULT_TRANSITION = new TransitionSet();
/**
+ * Since which target SDK version this window should be edge-to-edge by default.
+ */
+ private static final int DEFAULT_EDGE_TO_EDGE_SDK_VERSION =
+ SystemProperties.getInt("persist.wm.debug.default_e2e_since_sdk", Integer.MAX_VALUE);
+
+ /**
* Simple callback used by the context menu and its submenus. The options
* menu submenus do not use this (their behavior is more complex).
*/
@@ -359,6 +366,8 @@
boolean mDecorFitsSystemWindows = true;
+ private final boolean mDefaultEdgeToEdge;
+
private final ProxyOnBackInvokedDispatcher mProxyOnBackInvokedDispatcher;
static class WindowManagerHolder {
@@ -377,6 +386,11 @@
mProxyOnBackInvokedDispatcher = new ProxyOnBackInvokedDispatcher(context);
mAllowFloatingWindowsFillScreen = context.getResources().getBoolean(
com.android.internal.R.bool.config_allowFloatingWindowsFillScreen);
+ mDefaultEdgeToEdge =
+ context.getApplicationInfo().targetSdkVersion >= DEFAULT_EDGE_TO_EDGE_SDK_VERSION;
+ if (mDefaultEdgeToEdge) {
+ mDecorFitsSystemWindows = false;
+ }
}
/**
@@ -2527,7 +2541,14 @@
final boolean targetPreQ = targetSdk < Build.VERSION_CODES.Q;
if (!mForcedStatusBarColor) {
- mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, Color.BLACK);
+ final int statusBarCompatibleColor = context.getColor(R.color.status_bar_compatible);
+ final int statusBarDefaultColor = context.getColor(R.color.status_bar_default);
+ final int statusBarColor = a.getColor(R.styleable.Window_statusBarColor,
+ statusBarDefaultColor);
+
+ mStatusBarColor = statusBarColor == statusBarDefaultColor && !mDefaultEdgeToEdge
+ ? statusBarCompatibleColor
+ : statusBarColor;
}
if (!mForcedNavigationBarColor) {
final int navBarCompatibleColor = context.getColor(R.color.navigation_bar_compatible);
@@ -2541,6 +2562,7 @@
&& Flags.navBarTransparentByDefault())
&& !context.getResources().getBoolean(
R.bool.config_navBarDefaultTransparent)
+ && !mDefaultEdgeToEdge
? navBarCompatibleColor
: navBarColor;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index d5d912f..6859f1f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2247,6 +2247,13 @@
<permission android:name="android.permission.MANAGE_LOWPAN_INTERFACES"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi @hide Allows changing Thread network state and access to Thread network
+ credentials such as Network Key and PSKc.
+ <p>Not for use by third-party applications.
+ @FlaggedApi("com.android.net.thread.flags.thread_enabled") -->
+ <permission android:name="android.permission.THREAD_NETWORK_PRIVILEGED"
+ android:protectionLevel="signature|privileged" />
+
<!-- #SystemApi @hide Allows an app to bypass Private DNS.
<p>Not for use by third-party applications.
TODO: publish as system API in next API release. -->
diff --git a/core/res/res/layout/app_language_picker_current_locale_item.xml b/core/res/res/layout/app_language_picker_current_locale_item.xml
index 990e26c..01b9cc5 100644
--- a/core/res/res/layout/app_language_picker_current_locale_item.xml
+++ b/core/res/res/layout/app_language_picker_current_locale_item.xml
@@ -39,6 +39,7 @@
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_check_24dp"
- app:tint="?attr/colorAccentPrimaryVariant"/>
+ app:tint="?attr/colorAccentPrimaryVariant"
+ android:contentDescription="@*android:string/checked"/>
</LinearLayout>
</LinearLayout>
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index c0c6e05..30beee0 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -568,6 +568,10 @@
<color name="side_fps_button_color">#00677E</color>
<!-- Color for system bars -->
+ <color name="status_bar_compatible">@android:color/black</color>
+ <!-- This uses non-regular transparent intentionally. It is used to tell if the transparent
+ color is set by the framework or not. -->
+ <color name="status_bar_default">#00808080</color>
<color name="navigation_bar_compatible">@android:color/black</color>
<!-- This uses non-regular transparent intentionally. It is used to tell if the transparent
color is set by the framework or not. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index e646548..8748ca1 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3074,6 +3074,8 @@
<java-symbol type="bool" name="config_navBarDefaultTransparent" />
<java-symbol type="color" name="navigation_bar_default"/>
<java-symbol type="color" name="navigation_bar_compatible"/>
+ <java-symbol type="color" name="status_bar_default"/>
+ <java-symbol type="color" name="status_bar_compatible"/>
<!-- EditText suggestion popup. -->
<java-symbol type="id" name="suggestionWindowContainer" />
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index bdbf96b..d5d67ab 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -190,7 +190,7 @@
<item name="windowTranslucentStatus">false</item>
<item name="windowTranslucentNavigation">false</item>
<item name="windowDrawsSystemBarBackgrounds">false</item>
- <item name="statusBarColor">@color/black</item>
+ <item name="statusBarColor">@color/status_bar_default</item>
<item name="navigationBarColor">@color/navigation_bar_default</item>
<item name="windowActionBarFullscreenDecorLayout">@layout/screen_action_bar</item>
<item name="windowContentTransitions">false</item>
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index e7117a7..dfe6cf8 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -49,6 +49,7 @@
import android.app.Instrumentation;
import android.app.UiModeManager;
import android.content.Context;
+import android.graphics.ForceDarkType;
import android.hardware.display.DisplayManagerGlobal;
import android.os.Binder;
import android.os.SystemProperties;
@@ -593,7 +594,7 @@
mViewRootImpl.updateConfiguration(sContext.getDisplayNoVerify().getDisplayId())
);
- assertThat(mViewRootImpl.isForceDarkEnabled()).isFalse();
+ assertThat(mViewRootImpl.determineForceDarkType()).isEqualTo(ForceDarkType.NONE);
}
@Test
@@ -613,7 +614,8 @@
mViewRootImpl.updateConfiguration(sContext.getDisplayNoVerify().getDisplayId())
);
- assertThat(mViewRootImpl.isForceDarkEnabled()).isTrue();
+ assertThat(mViewRootImpl.determineForceDarkType())
+ .isEqualTo(ForceDarkType.FORCE_INVERT_COLOR_DARK);
}
@Test
@@ -634,7 +636,7 @@
mViewRootImpl.updateConfiguration(sContext.getDisplayNoVerify().getDisplayId())
);
- assertThat(mViewRootImpl.isForceDarkEnabled()).isFalse();
+ assertThat(mViewRootImpl.determineForceDarkType()).isEqualTo(ForceDarkType.NONE);
}
@Test
@@ -654,7 +656,7 @@
mViewRootImpl.updateConfiguration(sContext.getDisplayNoVerify().getDisplayId())
);
- assertThat(mViewRootImpl.isForceDarkEnabled()).isTrue();
+ assertThat(mViewRootImpl.determineForceDarkType()).isEqualTo(ForceDarkType.FORCE_DARK);
}
private boolean setForceDarkSysProp(boolean isForceDarkEnabled) {
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index ab18a50..32186667 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -439,6 +439,8 @@
<permission name="android.permission.MANAGE_WIFI_NETWORK_SELECTION" />
<!-- Permission needed for CTS test - ConcurrencyTest#testP2pSetWfdInfo -->
<permission name="android.permission.CONFIGURE_WIFI_DISPLAY" />
+ <!-- Permission required for CTS test - CtsThreadNetworkTestCases -->
+ <permission name="android.permission.THREAD_NETWORK_PRIVILEGED"/>
<!-- Permission required for CTS test CarrierMessagingServiceWrapperTest -->
<permission name="android.permission.BIND_CARRIER_SERVICES"/>
<!-- Permission required for CTS test - MusicRecognitionManagerTest -->
diff --git a/graphics/java/android/graphics/ForceDarkType.java b/graphics/java/android/graphics/ForceDarkType.java
new file mode 100644
index 0000000..396b037
--- /dev/null
+++ b/graphics/java/android/graphics/ForceDarkType.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * The style of force dark to use in {@link HardwareRenderer}.
+ *
+ * You must keep this in sync with the C++ enum ForceDarkType in
+ * frameworks/base/libs/hwui/utils/ForceDark.h
+ *
+ * @hide
+ */
+public class ForceDarkType {
+ /**
+ * Force dark disabled: normal, default operation.
+ *
+ * @hide
+ */
+ public static final int NONE = 0;
+
+ /**
+ * Use force dark
+ * @hide
+ */
+ public static final int FORCE_DARK = 1;
+
+ /**
+ * Force force-dark. {@see Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED}
+ * @hide */
+ public static final int FORCE_INVERT_COLOR_DARK = 2;
+
+ /** @hide */
+ @IntDef({
+ NONE,
+ FORCE_DARK,
+ FORCE_INVERT_COLOR_DARK,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ForceDarkTypeDef {}
+
+}
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index 8cd262e..20e393e 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -182,7 +182,7 @@
/** @hide */
protected RenderNode mRootNode;
private boolean mOpaque = true;
- private boolean mForceDark = false;
+ private int mForceDark = ForceDarkType.NONE;
private @ActivityInfo.ColorMode int mColorMode = ActivityInfo.COLOR_MODE_DEFAULT;
private float mDesiredSdrHdrRatio = 1f;
@@ -571,10 +571,10 @@
* Whether or not the force-dark feature should be used for this renderer.
* @hide
*/
- public boolean setForceDark(boolean enable) {
- if (mForceDark != enable) {
- mForceDark = enable;
- nSetForceDark(mNativeProxy, enable);
+ public boolean setForceDark(@ForceDarkType.ForceDarkTypeDef int type) {
+ if (mForceDark != type) {
+ mForceDark = type;
+ nSetForceDark(mNativeProxy, type);
return true;
}
return false;
@@ -1597,7 +1597,7 @@
private static native void nAllocateBuffers(long nativeProxy);
- private static native void nSetForceDark(long nativeProxy, boolean enabled);
+ private static native void nSetForceDark(long nativeProxy, int type);
private static native void nSetDisplayDensityDpi(int densityDpi);
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 0e59e9a..29bdd5c 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -28,3 +28,10 @@
description: "Enables invoking split contextually"
bug: "276361926"
}
+
+flag {
+ name: "enable_taskbar_navbar_unification"
+ namespace: "multitasking"
+ description: "Enables taskbar / navbar unification"
+ bug: "309671494"
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
index 5cf9175..8241e1a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
@@ -136,6 +136,7 @@
/** Called on frame update. */
final void onAnimationUpdate(@NonNull SurfaceControl.Transaction t, long currentPlayTime) {
+ mTransformation.clear();
// Extract the transformation to the current time.
mAnimation.getTransformation(Math.min(currentPlayTime, mAnimation.getDuration()),
mTransformation);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index beae96e..54cf84c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -838,10 +838,12 @@
// Use optional-of-lazy for the dependency that this provider relies on.
// Lazy ensures that this provider will not be the cause the dependency is created
// when it will not be returned due to the condition below.
- if (DesktopModeStatus.isEnabled()) {
- return desktopTasksController.map(Lazy::get);
- }
- return Optional.empty();
+ return desktopTasksController.flatMap((lazy)-> {
+ if (DesktopModeStatus.isEnabled()) {
+ return Optional.of(lazy.get());
+ }
+ return Optional.empty();
+ });
}
@BindsOptionalOf
@@ -855,10 +857,12 @@
// Use optional-of-lazy for the dependency that this provider relies on.
// Lazy ensures that this provider will not be the cause the dependency is created
// when it will not be returned due to the condition below.
- if (DesktopModeStatus.isEnabled()) {
- return desktopModeTaskRepository.map(Lazy::get);
- }
- return Optional.empty();
+ return desktopModeTaskRepository.flatMap((lazy)-> {
+ if (DesktopModeStatus.isEnabled()) {
+ return Optional.of(lazy.get());
+ }
+ return Optional.empty();
+ });
}
//
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index d28bb49..3e131bc 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -40,6 +40,7 @@
#ifdef __ANDROID__
#include "include/gpu/ganesh/SkImageGanesh.h"
#endif
+#include "utils/ForceDark.h"
#include "utils/MathUtils.h"
#include "utils/StringUtils.h"
@@ -403,16 +404,21 @@
deleteDisplayList(observer, info);
mDisplayList = std::move(mStagingDisplayList);
if (mDisplayList) {
- WebViewSyncData syncData {
- .applyForceDark = info && !info->disableForceDark
- };
+ WebViewSyncData syncData{.applyForceDark = shouldEnableForceDark(info)};
mDisplayList.syncContents(syncData);
handleForceDark(info);
}
}
+inline bool RenderNode::shouldEnableForceDark(TreeInfo* info) {
+ return CC_UNLIKELY(
+ info &&
+ (!info->disableForceDark ||
+ info->forceDarkType == android::uirenderer::ForceDarkType::FORCE_INVERT_COLOR_DARK));
+}
+
void RenderNode::handleForceDark(android::uirenderer::TreeInfo *info) {
- if (CC_LIKELY(!info || info->disableForceDark)) {
+ if (!shouldEnableForceDark(info)) {
return;
}
auto usage = usageHint();
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index c959db3..1f3834be 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -233,6 +233,7 @@
void syncProperties();
void syncDisplayList(TreeObserver& observer, TreeInfo* info);
void handleForceDark(TreeInfo* info);
+ bool shouldEnableForceDark(TreeInfo* info);
void prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer);
void pushStagingPropertiesChanges(TreeInfo& info);
diff --git a/libs/hwui/TreeInfo.cpp b/libs/hwui/TreeInfo.cpp
index 750f869..717157c 100644
--- a/libs/hwui/TreeInfo.cpp
+++ b/libs/hwui/TreeInfo.cpp
@@ -24,7 +24,8 @@
: mode(mode)
, prepareTextures(mode == MODE_FULL)
, canvasContext(canvasContext)
- , disableForceDark(canvasContext.useForceDark() ? 0 : 1)
+ , disableForceDark(canvasContext.getForceDarkType() == ForceDarkType::NONE ? 1 : 0)
+ , forceDarkType(canvasContext.getForceDarkType())
, screenSize(canvasContext.getNextFrameSize()) {}
} // namespace android::uirenderer
diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h
index ea25f68..88449f3 100644
--- a/libs/hwui/TreeInfo.h
+++ b/libs/hwui/TreeInfo.h
@@ -24,6 +24,7 @@
#include "Properties.h"
#include "SkSize.h"
#include "SkippedFrameInfo.h"
+#include "utils/ForceDark.h"
#include "utils/Macros.h"
namespace android {
@@ -97,6 +98,7 @@
bool updateWindowPositions = false;
int disableForceDark;
+ ForceDarkType forceDarkType = ForceDarkType::NONE;
const SkISize screenSize;
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index 422ffea..d15b1680 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -35,6 +35,7 @@
#include <gui/TraceUtils.h>
#include <include/encode/SkPngEncoder.h>
#include <inttypes.h>
+#include <log/log.h>
#include <media/NdkImage.h>
#include <media/NdkImageReader.h>
#include <nativehelper/JNIPlatformHelp.h>
@@ -53,11 +54,11 @@
#include <algorithm>
#include <atomic>
-#include <log/log.h>
#include <vector>
#include "JvmErrorReporter.h"
#include "android_graphics_HardwareRendererObserver.h"
+#include "utils/ForceDark.h"
namespace android {
@@ -824,10 +825,10 @@
proxy->allocateBuffers();
}
-static void android_view_ThreadedRenderer_setForceDark(JNIEnv* env, jobject clazz,
- jlong proxyPtr, jboolean enable) {
+static void android_view_ThreadedRenderer_setForceDark(JNIEnv* env, jobject clazz, jlong proxyPtr,
+ jint type) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
- proxy->setForceDark(enable);
+ proxy->setForceDark(static_cast<ForceDarkType>(type));
}
static void android_view_ThreadedRenderer_preload(JNIEnv*, jclass) {
@@ -1016,7 +1017,7 @@
{"nSetIsolatedProcess", "(Z)V", (void*)android_view_ThreadedRenderer_setIsolatedProcess},
{"nSetContextPriority", "(I)V", (void*)android_view_ThreadedRenderer_setContextPriority},
{"nAllocateBuffers", "(J)V", (void*)android_view_ThreadedRenderer_allocateBuffers},
- {"nSetForceDark", "(JZ)V", (void*)android_view_ThreadedRenderer_setForceDark},
+ {"nSetForceDark", "(JI)V", (void*)android_view_ThreadedRenderer_setForceDark},
{"nSetDisplayDensityDpi", "(I)V",
(void*)android_view_ThreadedRenderer_setDisplayDensityDpi},
{"nInitDisplayInfo", "(IIFIJJZZ)V", (void*)android_view_ThreadedRenderer_initDisplayInfo},
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 37e4f7ec..be9b649 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -46,6 +46,7 @@
#include "renderstate/RenderState.h"
#include "renderthread/RenderTask.h"
#include "renderthread/RenderThread.h"
+#include "utils/ForceDark.h"
#include "utils/RingBuffer.h"
namespace android {
@@ -194,11 +195,9 @@
mRenderPipeline->setPictureCapturedCallback(callback);
}
- void setForceDark(bool enable) { mUseForceDark = enable; }
+ void setForceDark(ForceDarkType type) { mForceDarkType = type; }
- bool useForceDark() {
- return mUseForceDark;
- }
+ ForceDarkType getForceDarkType() { return mForceDarkType; }
SkISize getNextFrameSize() const;
@@ -321,7 +320,7 @@
nsecs_t mLastDropVsync = 0;
bool mOpaque;
- bool mUseForceDark = false;
+ ForceDarkType mForceDarkType = ForceDarkType::NONE;
LightInfo mLightInfo;
LightGeometry mLightGeometry = {{0, 0, 0}, 0};
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index be163ba..c3c136f 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -417,8 +417,8 @@
});
}
-void RenderProxy::setForceDark(bool enable) {
- mRenderThread.queue().post([this, enable]() { mContext->setForceDark(enable); });
+void RenderProxy::setForceDark(ForceDarkType type) {
+ mRenderThread.queue().post([this, type]() { mContext->setForceDark(type); });
}
void RenderProxy::copySurfaceInto(ANativeWindow* window, std::shared_ptr<CopyRequest>&& request) {
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 47c1b0c..f2d8e94 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -31,6 +31,7 @@
#include "DrawFrameTask.h"
#include "SwapBehavior.h"
#include "hwui/Bitmap.h"
+#include "utils/ForceDark.h"
class SkBitmap;
class SkPicture;
@@ -142,7 +143,7 @@
void addFrameMetricsObserver(FrameMetricsObserver* observer);
void removeFrameMetricsObserver(FrameMetricsObserver* observer);
- void setForceDark(bool enable);
+ void setForceDark(ForceDarkType type);
static void copySurfaceInto(ANativeWindow* window, std::shared_ptr<CopyRequest>&& request);
static void prepareToDraw(Bitmap& bitmap);
diff --git a/libs/hwui/utils/ForceDark.h b/libs/hwui/utils/ForceDark.h
new file mode 100644
index 0000000..28538c4b
--- /dev/null
+++ b/libs/hwui/utils/ForceDark.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FORCEDARKUTILS_H
+#define FORCEDARKUTILS_H
+
+namespace android {
+namespace uirenderer {
+
+/**
+ * The type of force dark set on the renderer, if any.
+ *
+ * This should stay in sync with the java @IntDef in
+ * frameworks/base/graphics/java/android/graphics/ForceDarkType.java
+ */
+enum class ForceDarkType : __uint8_t { NONE = 0, FORCE_DARK = 1, FORCE_INVERT_COLOR_DARK = 2 };
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif // FORCEDARKUTILS_H
\ No newline at end of file
diff --git a/packages/CredentialManager/wear/Android.bp b/packages/CredentialManager/wear/Android.bp
index c883b1f2..2a89a99 100644
--- a/packages/CredentialManager/wear/Android.bp
+++ b/packages/CredentialManager/wear/Android.bp
@@ -35,6 +35,7 @@
"androidx.compose.ui_ui",
"androidx.compose.ui_ui-tooling",
"androidx.core_core-ktx",
+ "androidx.hilt_hilt-navigation-compose",
"androidx.lifecycle_lifecycle-extensions",
"androidx.lifecycle_lifecycle-livedata",
"androidx.lifecycle_lifecycle-runtime-ktx",
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index 0a63cb7..f2df64a 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -21,16 +21,10 @@
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
import androidx.wear.compose.material.MaterialTheme
import com.android.credentialmanager.ui.WearApp
-import com.android.credentialmanager.ui.screens.single.password.SinglePasswordScreen
import com.google.android.horologist.annotations.ExperimentalHorologistApi
-import com.google.android.horologist.compose.layout.belowTimeTextPreview
import dagger.hilt.android.AndroidEntryPoint
-import kotlinx.coroutines.launch
@AndroidEntryPoint(ComponentActivity::class)
class CredentialSelectorActivity : Hilt_CredentialSelectorActivity() {
@@ -42,50 +36,14 @@
super.onCreate(savedInstanceState)
setTheme(android.R.style.Theme_DeviceDefault)
-
- // TODO: b/301027810 due to this issue with compose in Main platform, we are implementing a
- // workaround. Once the issue is fixed, remove the "else" bracket and leave only the
- // contents of the "if" bracket.
- if (false) {
- setContent {
- MaterialTheme {
- WearApp(
- viewModel = viewModel,
- onCloseApp = ::finish,
- )
- }
- }
- } else {
- // TODO: b/301027810 Remove the content of this "else" bracket fully once issue is fixed
- lifecycleScope.launch {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- viewModel.uiState.collect { uiState ->
- when (uiState) {
- CredentialSelectorUiState.Idle -> {
- // Don't display anything, assuming that there should be minimal latency
- // to parse the Credential Manager intent and define the state of the
- // app. If latency is big, then a "loading" screen should be displayed
- // to the user.
- }
-
- is CredentialSelectorUiState.Get -> {
- setContent {
- MaterialTheme {
- SinglePasswordScreen(
- columnState = belowTimeTextPreview(),
- onCloseApp = ::finish,
- )
- }
- }
- }
-
- else -> finish()
- }
- }
- }
+ setContent {
+ MaterialTheme {
+ WearApp(
+ viewModel = viewModel,
+ onCloseApp = ::finish,
+ )
}
}
-
viewModel.onNewIntent(intent)
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
index 81a0672..c28df3e8 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
@@ -27,8 +27,8 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import androidx.lifecycle.viewmodel.compose.viewModel
import com.android.credentialmanager.R
import com.android.credentialmanager.TAG
import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract
@@ -47,7 +47,7 @@
columnState: ScalingLazyColumnState,
onCloseApp: () -> Unit,
modifier: Modifier = Modifier,
- viewModel: SinglePasswordScreenViewModel = viewModel(),
+ viewModel: SinglePasswordScreenViewModel = hiltViewModel(),
) {
viewModel.initialize()
diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
index 6001155..2b5fcd8 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
@@ -24,8 +24,8 @@
import android.view.LayoutInflater;
import android.view.View;
import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.LinearLayout;
-import android.widget.Switch;
import android.widget.TextView;
import androidx.annotation.ColorInt;
@@ -41,9 +41,9 @@
* This component is used as the main switch of the page
* to enable or disable the prefereces on the page.
*/
-public class MainSwitchBar extends LinearLayout implements CompoundButton.OnCheckedChangeListener {
+public class MainSwitchBar extends LinearLayout implements OnCheckedChangeListener {
- private final List<OnMainSwitchChangeListener> mSwitchChangeListeners = new ArrayList<>();
+ private final List<OnCheckedChangeListener> mSwitchChangeListeners = new ArrayList<>();
@ColorInt
private int mBackgroundColor;
@@ -51,8 +51,8 @@
private int mBackgroundActivatedColor;
protected TextView mTextView;
- protected Switch mSwitch;
- private View mFrameView;
+ protected CompoundButton mSwitch;
+ private final View mFrameView;
public MainSwitchBar(Context context) {
this(context, null);
@@ -84,8 +84,8 @@
setClickable(true);
mFrameView = findViewById(R.id.frame);
- mTextView = (TextView) findViewById(R.id.switch_text);
- mSwitch = (Switch) findViewById(android.R.id.switch_widget);
+ mTextView = findViewById(R.id.switch_text);
+ mSwitch = findViewById(android.R.id.switch_widget);
addOnSwitchChangeListener((switchView, isChecked) -> setChecked(isChecked));
if (mSwitch.getVisibility() == VISIBLE) {
@@ -136,13 +136,6 @@
}
/**
- * Return the Switch
- */
- public final Switch getSwitch() {
- return mSwitch;
- }
-
- /**
* Set the title text
*/
public void setTitle(CharSequence text) {
@@ -192,7 +185,7 @@
/**
* Adds a listener for switch changes
*/
- public void addOnSwitchChangeListener(OnMainSwitchChangeListener listener) {
+ public void addOnSwitchChangeListener(OnCheckedChangeListener listener) {
if (!mSwitchChangeListeners.contains(listener)) {
mSwitchChangeListeners.add(listener);
}
@@ -201,7 +194,7 @@
/**
* Remove a listener for switch changes
*/
- public void removeOnSwitchChangeListener(OnMainSwitchChangeListener listener) {
+ public void removeOnSwitchChangeListener(OnCheckedChangeListener listener) {
mSwitchChangeListeners.remove(listener);
}
@@ -223,9 +216,8 @@
private void propagateChecked(boolean isChecked) {
setBackground(isChecked);
- final int count = mSwitchChangeListeners.size();
- for (int n = 0; n < count; n++) {
- mSwitchChangeListeners.get(n).onSwitchChanged(mSwitch, isChecked);
+ for (OnCheckedChangeListener changeListener : mSwitchChangeListeners) {
+ changeListener.onCheckedChanged(mSwitch, isChecked);
}
}
diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
index 11a6804..b294d4e 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
@@ -19,24 +19,25 @@
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
-import android.widget.Switch;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
import androidx.preference.PreferenceViewHolder;
import androidx.preference.TwoStatePreference;
+import com.android.settingslib.widget.mainswitch.R;
+
import java.util.ArrayList;
import java.util.List;
-import com.android.settingslib.widget.mainswitch.R;
-
/**
* MainSwitchPreference is a Preference with a customized Switch.
* This component is used as the main switch of the page
* to enable or disable the prefereces on the page.
*/
-public class MainSwitchPreference extends TwoStatePreference implements OnMainSwitchChangeListener {
+public class MainSwitchPreference extends TwoStatePreference implements OnCheckedChangeListener {
- private final List<OnMainSwitchChangeListener> mSwitchChangeListeners = new ArrayList<>();
+ private final List<OnCheckedChangeListener> mSwitchChangeListeners = new ArrayList<>();
private MainSwitchBar mMainSwitchBar;
@@ -120,7 +121,7 @@
}
@Override
- public void onSwitchChanged(Switch switchView, boolean isChecked) {
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
super.setChecked(isChecked);
}
@@ -138,7 +139,7 @@
/**
* Adds a listener for switch changes
*/
- public void addOnSwitchChangeListener(OnMainSwitchChangeListener listener) {
+ public void addOnSwitchChangeListener(OnCheckedChangeListener listener) {
if (!mSwitchChangeListeners.contains(listener)) {
mSwitchChangeListeners.add(listener);
}
@@ -151,7 +152,7 @@
/**
* Remove a listener for switch changes
*/
- public void removeOnSwitchChangeListener(OnMainSwitchChangeListener listener) {
+ public void removeOnSwitchChangeListener(OnCheckedChangeListener listener) {
mSwitchChangeListeners.remove(listener);
if (mMainSwitchBar != null) {
mMainSwitchBar.removeOnSwitchChangeListener(listener);
@@ -159,7 +160,7 @@
}
private void registerListenerToSwitchBar() {
- for (OnMainSwitchChangeListener listener : mSwitchChangeListeners) {
+ for (OnCheckedChangeListener listener : mSwitchChangeListeners) {
mMainSwitchBar.addOnSwitchChangeListener(listener);
}
}
diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/OnMainSwitchChangeListener.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/OnMainSwitchChangeListener.java
deleted file mode 100644
index 03868f9..0000000
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/OnMainSwitchChangeListener.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2020 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.settingslib.widget;
-
-import android.widget.Switch;
-
-import com.android.settingslib.widget.mainswitch.R;
-
-/**
- * Called when the checked state of the Switch has changed.
- */
-public interface OnMainSwitchChangeListener {
- /**
- * @param switchView The Switch view whose state has changed.
- * @param isChecked The new checked state of switchView.
- */
- void onSwitchChanged(Switch switchView, boolean isChecked);
-}
diff --git a/packages/SettingsLib/Spa/gallery/res/values/strings.xml b/packages/SettingsLib/Spa/gallery/res/values/strings.xml
index ec60f8c..18a6db0 100644
--- a/packages/SettingsLib/Spa/gallery/res/values/strings.xml
+++ b/packages/SettingsLib/Spa/gallery/res/values/strings.xml
@@ -26,4 +26,9 @@
<string name="single_line_summary_preference_summary" translatable="false">A very long summary to show case a preference which only shows a single line summary.</string>
<!-- Footer text with two links. [DO NOT TRANSLATE] -->
<string name="footer_with_two_links" translatable="false">Annotated string with <a href="https://www.android.com/">link 1</a> and <a href="https://source.android.com/">link 2</a>.</string>
+
+ <!-- Sample title -->
+ <string name="sample_title" translatable="false">Lorem ipsum</string>
+ <!-- Sample text -->
+ <string name="sample_text" translatable="false">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent a rhoncus tellus. Nulla facilisi. Pellentesque erat ex, maximus viae turpis</string>
</resources>
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index d62b490..b1e1585 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -22,6 +22,7 @@
import com.android.settingslib.spa.framework.common.SpaEnvironment
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
+import com.android.settingslib.spa.gallery.card.CardPageProvider
import com.android.settingslib.spa.gallery.chart.ChartPageProvider
import com.android.settingslib.spa.gallery.dialog.AlertDialogPageProvider
import com.android.settingslib.spa.gallery.editor.EditorMainPageProvider
@@ -98,6 +99,7 @@
SettingsExposedDropdownMenuCheckBoxProvider,
SettingsTextFieldPasswordPageProvider,
SearchScaffoldPageProvider,
+ CardPageProvider,
),
rootPages = listOf(
HomePageProvider.createSettingsPage(),
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt
new file mode 100644
index 0000000..e914d5c
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.gallery.card
+
+import android.os.Bundle
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.WarningAmber
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.gallery.R
+import com.android.settingslib.spa.widget.card.CardButton
+import com.android.settingslib.spa.widget.card.SettingsCard
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+
+object CardPageProvider : SettingsPageProvider {
+ override val name = "ActionButton"
+
+ override fun getTitle(arguments: Bundle?) = TITLE
+
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ RegularScaffold(title = TITLE) {
+ SettingsCardWithIcon()
+ SettingsCardWithoutIcon()
+ }
+ }
+
+ @Composable
+ private fun SettingsCardWithIcon() {
+ SettingsCard(
+ title = stringResource(R.string.sample_title),
+ text = stringResource(R.string.sample_text),
+ imageVector = Icons.Outlined.WarningAmber,
+ buttons = listOf(
+ CardButton(text = "Action") {},
+ CardButton(text = "Action", isMain = true) {},
+ )
+ )
+ }
+
+ @Composable
+ private fun SettingsCardWithoutIcon() {
+ SettingsCard(
+ title = stringResource(R.string.sample_title),
+ text = stringResource(R.string.sample_text),
+ buttons = listOf(
+ CardButton(text = "Action") {},
+ )
+ )
+ }
+
+ fun buildInjectEntry(): SettingsEntryBuilder {
+ return SettingsEntryBuilder.createInject(owner = createSettingsPage())
+ .setUiLayoutFn {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
+ }
+ }
+
+ private const val TITLE = "Sample Card"
+}
+
+@Preview
+@Composable
+private fun CardPagePreview() {
+ SettingsTheme {
+ CardPageProvider.Page(null)
+ }
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
index b339b44..f52ceec 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
@@ -28,6 +28,7 @@
import com.android.settingslib.spa.gallery.R
import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
+import com.android.settingslib.spa.gallery.card.CardPageProvider
import com.android.settingslib.spa.gallery.chart.ChartPageProvider
import com.android.settingslib.spa.gallery.dialog.AlertDialogPageProvider
import com.android.settingslib.spa.gallery.editor.EditorMainPageProvider
@@ -69,6 +70,7 @@
ChartPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
AlertDialogPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
EditorMainPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+ CardPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
new file mode 100644
index 0000000..98873e0
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.card
+
+import android.content.res.Configuration
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.WarningAmber
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedButton
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsShape.CornerExtraLarge
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.ui.SettingsBody
+import com.android.settingslib.spa.widget.ui.SettingsTitle
+
+data class CardButton(
+ val text: String,
+ val isMain: Boolean = false,
+ val onClick: () -> Unit,
+)
+
+@Composable
+fun SettingsCard(
+ title: String,
+ text: String,
+ imageVector: ImageVector? = null,
+ buttons: List<CardButton> = emptyList(),
+) {
+ Card(
+ shape = CornerExtraLarge,
+ colors = CardDefaults.cardColors(
+ containerColor = SettingsTheme.colorScheme.surface,
+ ),
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(
+ horizontal = SettingsDimension.itemPaddingEnd,
+ vertical = SettingsDimension.itemPaddingAround,
+ ),
+ ) {
+ Column(
+ modifier = Modifier.padding(SettingsDimension.itemPaddingStart),
+ verticalArrangement = Arrangement.spacedBy(SettingsDimension.itemPaddingAround)
+ ) {
+ CardIcon(imageVector)
+ SettingsTitle(title)
+ SettingsBody(text)
+ Buttons(buttons)
+ }
+ }
+}
+
+@Composable
+private fun CardIcon(imageVector: ImageVector?) {
+ if (imageVector != null) {
+ Icon(
+ imageVector = imageVector,
+ contentDescription = null,
+ modifier = Modifier.size(SettingsDimension.itemIconSize),
+ tint = MaterialTheme.colorScheme.primary,
+ )
+ }
+}
+
+@Composable
+private fun Buttons(buttons: List<CardButton>) {
+ if (buttons.isNotEmpty()) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = SettingsDimension.itemPaddingAround),
+ horizontalArrangement = Arrangement.spacedBy(
+ space = SettingsDimension.itemPaddingEnd,
+ alignment = Alignment.End,
+ ),
+ ) {
+ for (button in buttons) {
+ Button(button)
+ }
+ }
+ }
+}
+
+@Composable
+private fun Button(button: CardButton) {
+ if (button.isMain) {
+ Button(
+ onClick = button.onClick,
+ colors = ButtonDefaults.buttonColors(
+ containerColor = SettingsTheme.colorScheme.primaryContainer,
+ ),
+ ) {
+ Text(
+ text = button.text,
+ color = SettingsTheme.colorScheme.onPrimaryContainer,
+ )
+ }
+ } else {
+ OutlinedButton(onClick = button.onClick) {
+ Text(
+ text = button.text,
+ color = MaterialTheme.colorScheme.onSurface,
+ )
+ }
+ }
+}
+
+@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
+@Composable
+private fun SettingsCardPreviewLight() {
+ SettingsCardPreview()
+}
+
+@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Composable
+private fun SettingsCardPreviewDark() {
+ SettingsCardPreview()
+}
+
+@Composable
+private fun SettingsCardPreview() {
+ SettingsTheme {
+ SettingsCard(
+ title = "Lorem ipsum",
+ text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
+ imageVector = Icons.Outlined.WarningAmber,
+ buttons = listOf(
+ CardButton(text = "Action") {},
+ CardButton(text = "Action", isMain = true) {},
+ )
+ )
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCardTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCardTest.kt
new file mode 100644
index 0000000..0ec8507
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCardTest.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.card
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsCardTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun settingsCard_titleDisplayed() {
+ composeTestRule.setContent {
+ SettingsCard(
+ title = TITLE,
+ text = "",
+ )
+ }
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+ }
+
+ @Test
+ fun settingsCard_textDisplayed() {
+ composeTestRule.setContent {
+ SettingsCard(
+ title = "",
+ text = TEXT,
+ )
+ }
+
+ composeTestRule.onNodeWithText(TEXT).assertIsDisplayed()
+ }
+
+ @Test
+ fun settingsCard_buttonDisplayed() {
+ composeTestRule.setContent {
+ SettingsCard(
+ title = "",
+ text = "",
+ buttons = listOf(
+ CardButton(text = TEXT) {}
+ ),
+ )
+ }
+
+ composeTestRule.onNodeWithText(TEXT).assertIsDisplayed()
+ }
+
+ @Test
+ fun settingsCard_buttonCanBeClicked() {
+ var buttonClicked = false
+ composeTestRule.setContent {
+ SettingsCard(
+ title = "",
+ text = "",
+ buttons = listOf(
+ CardButton(text = TEXT) { buttonClicked = true }
+ ),
+ )
+ }
+
+ composeTestRule.onNodeWithText(TEXT).performClick()
+
+ assertThat(buttonClicked).isTrue()
+ }
+
+ private companion object {
+ const val TITLE = "Title"
+ const val TEXT = "Text"
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverAsUserFlow.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverAsUserFlow.kt
new file mode 100644
index 0000000..2c60db4
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverAsUserFlow.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.framework.common
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.UserHandle
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.flowOn
+
+/**
+ * A [BroadcastReceiver] flow for the given [intentFilter].
+ */
+fun Context.broadcastReceiverAsUserFlow(
+ intentFilter: IntentFilter,
+ userHandle: UserHandle,
+): Flow<Intent> = callbackFlow {
+ val broadcastReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ trySend(intent)
+ }
+ }
+ registerReceiverAsUser(
+ broadcastReceiver,
+ userHandle,
+ intentFilter,
+ null,
+ null,
+ Context.RECEIVER_NOT_EXPORTED,
+ )
+
+ awaitClose { unregisterReceiver(broadcastReceiver) }
+}.conflate().flowOn(Dispatchers.Default)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUser.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUser.kt
index ad907cf..7d6ee19 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUser.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUser.kt
@@ -17,14 +17,14 @@
package com.android.settingslib.spaprivileged.framework.compose
import android.content.BroadcastReceiver
-import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.UserHandle
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
-import com.android.settingslib.spa.framework.compose.LifecycleEffect
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
+import com.android.settingslib.spaprivileged.framework.common.broadcastReceiverAsUserFlow
/**
* A [BroadcastReceiver] which registered when on start and unregistered when on stop.
@@ -35,27 +35,6 @@
userHandle: UserHandle,
onReceive: (Intent) -> Unit,
) {
- val context = LocalContext.current
- val broadcastReceiver = remember {
- object : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- onReceive(intent)
- }
- }
- }
- LifecycleEffect(
- onStart = {
- context.registerReceiverAsUser(
- broadcastReceiver,
- userHandle,
- intentFilter,
- null,
- null,
- Context.RECEIVER_NOT_EXPORTED,
- )
- },
- onStop = {
- context.unregisterReceiver(broadcastReceiver)
- },
- )
+ LocalContext.current.broadcastReceiverAsUserFlow(intentFilter, userHandle)
+ .collectLatestWithLifecycle(LocalLifecycleOwner.current, action = onReceive)
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalBooleanRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalBooleanRepository.kt
index 8e702ea..8e28bf8 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalBooleanRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalBooleanRepository.kt
@@ -23,21 +23,24 @@
import kotlin.reflect.KProperty
import kotlinx.coroutines.flow.Flow
-fun Context.settingsGlobalBoolean(name: String): ReadWriteProperty<Any?, Boolean> =
- SettingsGlobalBooleanDelegate(this, name)
+fun Context.settingsGlobalBoolean(name: String, defaultValue: Boolean = false):
+ ReadWriteProperty<Any?, Boolean> = SettingsGlobalBooleanDelegate(this, name, defaultValue)
-fun Context.settingsGlobalBooleanFlow(name: String): Flow<Boolean> {
- val value by settingsGlobalBoolean(name)
+fun Context.settingsGlobalBooleanFlow(name: String, defaultValue: Boolean = false): Flow<Boolean> {
+ val value by settingsGlobalBoolean(name, defaultValue)
return settingsGlobalFlow(name) { value }
}
-private class SettingsGlobalBooleanDelegate(context: Context, private val name: String) :
- ReadWriteProperty<Any?, Boolean> {
+private class SettingsGlobalBooleanDelegate(
+ context: Context,
+ private val name: String,
+ private val defaultValue: Boolean = false,
+) : ReadWriteProperty<Any?, Boolean> {
private val contentResolver: ContentResolver = context.contentResolver
override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean =
- Settings.Global.getInt(contentResolver, name, 0) != 0
+ Settings.Global.getInt(contentResolver, name, if (defaultValue) 1 else 0) != 0
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Boolean) {
Settings.Global.putInt(contentResolver, name, if (value) 1 else 0)
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverAsUserFlowTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverAsUserFlowTest.kt
new file mode 100644
index 0000000..dfb8e22
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverAsUserFlowTest.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.framework.common
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.isNull
+import org.mockito.kotlin.mock
+
+@RunWith(AndroidJUnit4::class)
+class BroadcastReceiverAsUserFlowTest {
+
+ private var registeredBroadcastReceiver: BroadcastReceiver? = null
+
+ private val context = mock<Context> {
+ on {
+ registerReceiverAsUser(
+ any(),
+ eq(USER_HANDLE),
+ eq(INTENT_FILTER),
+ isNull(),
+ isNull(),
+ eq(Context.RECEIVER_NOT_EXPORTED),
+ )
+ } doAnswer {
+ registeredBroadcastReceiver = it.arguments[0] as BroadcastReceiver
+ null
+ }
+ }
+
+ @Test
+ fun broadcastReceiverAsUserFlow_registered() = runBlocking {
+ val flow = context.broadcastReceiverAsUserFlow(INTENT_FILTER, USER_HANDLE)
+
+ flow.firstWithTimeoutOrNull()
+
+ assertThat(registeredBroadcastReceiver).isNotNull()
+ }
+
+ @Test
+ fun broadcastReceiverAsUserFlow_isCalledOnReceive() = runBlocking {
+ var onReceiveIsCalled = false
+ launch {
+ context.broadcastReceiverAsUserFlow(INTENT_FILTER, USER_HANDLE).first {
+ onReceiveIsCalled = true
+ true
+ }
+ }
+
+ delay(100)
+ registeredBroadcastReceiver!!.onReceive(context, Intent())
+ delay(100)
+
+ assertThat(onReceiveIsCalled).isTrue()
+ }
+
+ private companion object {
+ val USER_HANDLE: UserHandle = UserHandle.of(0)
+
+ val INTENT_FILTER = IntentFilter()
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt
index 2c8fb66..f812f95 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt
@@ -23,38 +23,32 @@
import android.os.UserHandle
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.lifecycle.testing.TestLifecycleOwner
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
-import org.junit.Before
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.runBlocking
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.junit.MockitoJUnit
-import org.mockito.junit.MockitoRule
import org.mockito.kotlin.any
+import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.eq
import org.mockito.kotlin.isNull
-import org.mockito.kotlin.whenever
+import org.mockito.kotlin.mock
@RunWith(AndroidJUnit4::class)
class DisposableBroadcastReceiverAsUserTest {
@get:Rule
val composeTestRule = createComposeRule()
- @get:Rule
- val mockito: MockitoRule = MockitoJUnit.rule()
-
- @Mock
- private lateinit var context: Context
-
private var registeredBroadcastReceiver: BroadcastReceiver? = null
- @Before
- fun setUp() {
- whenever(
- context.registerReceiverAsUser(
+ private val context = mock<Context> {
+ on {
+ registerReceiverAsUser(
any(),
eq(USER_HANDLE),
eq(INTENT_FILTER),
@@ -62,7 +56,7 @@
isNull(),
eq(Context.RECEIVER_NOT_EXPORTED),
)
- ).then {
+ } doAnswer {
registeredBroadcastReceiver = it.arguments[0] as BroadcastReceiver
null
}
@@ -71,7 +65,10 @@
@Test
fun broadcastReceiver_registered() {
composeTestRule.setContent {
- CompositionLocalProvider(LocalContext provides context) {
+ CompositionLocalProvider(
+ LocalContext provides context,
+ LocalLifecycleOwner provides TestLifecycleOwner(),
+ ) {
DisposableBroadcastReceiverAsUser(INTENT_FILTER, USER_HANDLE) {}
}
}
@@ -80,10 +77,13 @@
}
@Test
- fun broadcastReceiver_isCalledOnReceive() {
+ fun broadcastReceiver_isCalledOnReceive() = runBlocking {
var onReceiveIsCalled = false
composeTestRule.setContent {
- CompositionLocalProvider(LocalContext provides context) {
+ CompositionLocalProvider(
+ LocalContext provides context,
+ LocalLifecycleOwner provides TestLifecycleOwner(),
+ ) {
DisposableBroadcastReceiverAsUser(INTENT_FILTER, USER_HANDLE) {
onReceiveIsCalled = true
}
@@ -91,6 +91,7 @@
}
registeredBroadcastReceiver!!.onReceive(context, Intent())
+ delay(100)
assertThat(onReceiveIsCalled).isTrue()
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
index 840bca8..44973a7 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
@@ -19,6 +19,8 @@
import android.content.Context
import android.content.pm.ActivityInfo
import android.content.pm.ApplicationInfo
+import android.content.pm.FakeFeatureFlagsImpl
+import android.content.pm.Flags
import android.content.pm.PackageManager
import android.content.pm.PackageManager.ApplicationInfoFlags
import android.content.pm.PackageManager.ResolveInfoFlags
@@ -29,76 +31,62 @@
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.internal.R
-import com.android.settingslib.spaprivileged.framework.common.userManager
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Spy
-import org.mockito.junit.MockitoJUnit
-import org.mockito.junit.MockitoRule
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.stub
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
-import android.content.pm.FakeFeatureFlagsImpl
-import android.content.pm.Flags
@RunWith(AndroidJUnit4::class)
class AppListRepositoryTest {
- @get:Rule
- val mockito: MockitoRule = MockitoJUnit.rule()
+ private val resources = mock<Resources> {
+ on { getStringArray(R.array.config_hideWhenDisabled_packageNames) } doReturn emptyArray()
+ }
- @Spy
- private val context: Context = ApplicationProvider.getApplicationContext()
-
- @Mock
- private lateinit var resources: Resources
-
- @Mock
- private lateinit var packageManager: PackageManager
-
- @Mock
- private lateinit var userManager: UserManager
-
- private lateinit var repository: AppListRepository
-
- @Before
- fun setUp() {
- whenever(context.resources).thenReturn(resources)
- whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames))
- .thenReturn(emptyArray())
- whenever(context.packageManager).thenReturn(packageManager)
- whenever(context.userManager).thenReturn(userManager)
- whenever(packageManager.getInstalledModules(any())).thenReturn(emptyList())
- whenever(packageManager.getHomeActivities(any())).thenAnswer {
+ private val packageManager = mock<PackageManager> {
+ on { getInstalledModules(any()) } doReturn emptyList()
+ on { getHomeActivities(any()) } doAnswer {
@Suppress("UNCHECKED_CAST")
val resolveInfos = it.arguments[0] as MutableList<ResolveInfo>
resolveInfos += resolveInfoOf(packageName = HOME_APP.packageName)
null
}
- whenever(
- packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), any<Int>())
- ).thenReturn(listOf(resolveInfoOf(packageName = IN_LAUNCHER_APP.packageName)))
- whenever(userManager.getUserInfo(ADMIN_USER_ID)).thenReturn(UserInfo().apply {
- flags = UserInfo.FLAG_ADMIN
- })
- whenever(userManager.getProfileIdsWithDisabled(ADMIN_USER_ID))
- .thenReturn(intArrayOf(ADMIN_USER_ID, MANAGED_PROFILE_USER_ID))
-
- repository = AppListRepositoryImpl(context)
+ on { queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), any<Int>()) } doReturn
+ listOf(resolveInfoOf(packageName = IN_LAUNCHER_APP.packageName))
}
+ private val mockUserManager = mock<UserManager> {
+ on { getUserInfo(ADMIN_USER_ID) } doReturn UserInfo().apply {
+ flags = UserInfo.FLAG_ADMIN
+ }
+ on { getProfileIdsWithDisabled(ADMIN_USER_ID) } doReturn
+ intArrayOf(ADMIN_USER_ID, MANAGED_PROFILE_USER_ID)
+ }
+
+ private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
+ on { resources } doReturn resources
+ on { packageManager } doReturn packageManager
+ on { getSystemService(UserManager::class.java) } doReturn mockUserManager
+ }
+
+ private val repository = AppListRepositoryImpl(context)
+
private fun mockInstalledApplications(apps: List<ApplicationInfo>, userId: Int) {
- whenever(
- packageManager.getInstalledApplicationsAsUser(any<ApplicationInfoFlags>(), eq(userId))
- ).thenReturn(apps)
+ packageManager.stub {
+ on { getInstalledApplicationsAsUser(any<ApplicationInfoFlags>(), eq(userId)) } doReturn
+ apps
+ }
}
@Test
@@ -135,13 +123,13 @@
)
assertThat(appList).containsExactly(NORMAL_APP)
- argumentCaptor<ApplicationInfoFlags> {
+ val flags = argumentCaptor<ApplicationInfoFlags> {
verify(packageManager).getInstalledApplicationsAsUser(capture(), eq(ADMIN_USER_ID))
- assertThat(firstValue.value).isEqualTo(
- PackageManager.MATCH_DISABLED_COMPONENTS or
- PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
- )
- }
+ }.firstValue
+ assertThat(flags.value).isEqualTo(
+ PackageManager.MATCH_DISABLED_COMPONENTS or
+ PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+ )
}
@Test
@@ -154,11 +142,10 @@
)
assertThat(appList).containsExactly(NORMAL_APP)
- argumentCaptor<ApplicationInfoFlags> {
+ val flags = argumentCaptor<ApplicationInfoFlags> {
verify(packageManager).getInstalledApplicationsAsUser(capture(), eq(ADMIN_USER_ID))
- assertThat(firstValue.value and PackageManager.MATCH_ANY_USER.toLong())
- .isGreaterThan(0L)
- }
+ }.firstValue
+ assertThat(flags.value and PackageManager.MATCH_ANY_USER.toLong()).isGreaterThan(0L)
}
@Test
@@ -278,14 +265,14 @@
val appList = repository.loadApps(userId = ADMIN_USER_ID)
assertThat(appList).containsExactly(NORMAL_APP, ARCHIVED_APP)
- argumentCaptor<ApplicationInfoFlags> {
+ val flags = argumentCaptor<ApplicationInfoFlags> {
verify(packageManager).getInstalledApplicationsAsUser(capture(), eq(ADMIN_USER_ID))
- assertThat(firstValue.value).isEqualTo(
- (PackageManager.MATCH_DISABLED_COMPONENTS or
- PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong() or
- PackageManager.MATCH_ARCHIVED_PACKAGES
- )
- }
+ }.firstValue
+ assertThat(flags.value).isEqualTo(
+ (PackageManager.MATCH_DISABLED_COMPONENTS or
+ PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong() or
+ PackageManager.MATCH_ARCHIVED_PACKAGES
+ )
}
@Test
@@ -294,13 +281,13 @@
val appList = repository.loadApps(userId = ADMIN_USER_ID)
assertThat(appList).containsExactly(NORMAL_APP)
- argumentCaptor<ApplicationInfoFlags> {
+ val flags = argumentCaptor<ApplicationInfoFlags> {
verify(packageManager).getInstalledApplicationsAsUser(capture(), eq(ADMIN_USER_ID))
- assertThat(firstValue.value).isEqualTo(
- PackageManager.MATCH_DISABLED_COMPONENTS or
- PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
- )
- }
+ }.firstValue
+ assertThat(flags.value).isEqualTo(
+ PackageManager.MATCH_DISABLED_COMPONENTS or
+ PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+ )
}
@Test
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
index 0e7b79b..119aef6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -136,7 +136,7 @@
/**
* create profile instance according to bluetooth supported profile list
*/
- void updateLocalProfiles() {
+ synchronized void updateLocalProfiles() {
List<Integer> supportedList = BluetoothAdapter.getDefaultAdapter().getSupportedProfiles();
if (CollectionUtils.isEmpty(supportedList)) {
if (DEBUG) Log.d(TAG, "supportedList is null");
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/MainSwitchBarTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/MainSwitchBarTest.java
index 942e915..74a282f 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/MainSwitchBarTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/MainSwitchBarTest.java
@@ -21,30 +21,25 @@
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
-import android.text.TextUtils;
import android.view.View;
-import android.widget.Switch;
+import android.widget.CompoundButton;
import android.widget.TextView;
+import androidx.test.core.app.ApplicationProvider;
+
import com.android.settingslib.widget.mainswitch.R;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
@RunWith(RobolectricTestRunner.class)
public class MainSwitchBarTest {
- private Context mContext;
- private MainSwitchBar mBar;
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ private final MainSwitchBar mBar = new MainSwitchBar(mContext);
- @Before
- public void setUp() {
- mContext = RuntimeEnvironment.application;
- mBar = new MainSwitchBar(mContext);
- }
+ private final CompoundButton mSwitch = mBar.findViewById(android.R.id.switch_widget);
@Test
public void setChecked_true_shouldChecked() {
@@ -60,7 +55,7 @@
mBar.setTitle(title);
final TextView textView = ((TextView) mBar.findViewById(R.id.switch_text));
- assertThat(textView.getText()).isEqualTo(title);
+ assertThat(textView.getText().toString()).isEqualTo(title);
}
@Test
@@ -69,23 +64,18 @@
mBar.setTitle(title);
- final Switch switchObj = mBar.getSwitch();
- assertThat(TextUtils.isEmpty(switchObj.getContentDescription())).isTrue();
+ assertThat(mSwitch.getContentDescription()).isNull();
}
@Test
public void getSwitch_shouldNotNull() {
- final Switch switchObj = mBar.getSwitch();
-
- assertThat(switchObj).isNotNull();
+ assertThat(mSwitch).isNotNull();
}
@Test
public void getSwitch_shouldNotFocusableAndClickable() {
- final Switch switchObj = mBar.getSwitch();
-
- assertThat(switchObj.isFocusable()).isFalse();
- assertThat(switchObj.isClickable()).isFalse();
+ assertThat(mSwitch.isFocusable()).isFalse();
+ assertThat(mSwitch.isClickable()).isFalse();
}
@Test
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 36e1bfa..ed03d94 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -592,6 +592,9 @@
<!-- Permission needed for CTS test - ConcurrencyTest#testP2pSetWfdInfo -->
<uses-permission android:name="android.permission.CONFIGURE_WIFI_DISPLAY" />
+ <!-- Permission required for CTS test - CtsThreadNetworkTestCases -->
+ <uses-permission android:name="android.permission.THREAD_NETWORK_PRIVILEGED"/>
+
<!-- Permission required for CTS tests to enable/disable rate limiting toasts. -->
<uses-permission android:name="android.permission.MANAGE_TOAST_RATE_LIMITING" />
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 88abf69..0e9f8b1 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -187,6 +187,8 @@
"androidx.dynamicanimation_dynamicanimation",
"androidx-constraintlayout_constraintlayout",
"androidx.exifinterface_exifinterface",
+ "androidx.room_room-runtime",
+ "androidx.room_room-ktx",
"com.google.android.material_material",
"kotlinx_coroutines_android",
"kotlinx_coroutines",
@@ -207,10 +209,16 @@
],
manifest: "AndroidManifest.xml",
- javacflags: ["-Adagger.fastInit=enabled"],
+ javacflags: [
+ "-Adagger.fastInit=enabled",
+ "-Aroom.schemaLocation=frameworks/base/packages/SystemUI/schemas",
+ ],
kotlincflags: ["-Xjvm-default=all"],
- plugins: ["dagger2-compiler"],
+ plugins: [
+ "androidx.room_room-compiler-plugin",
+ "dagger2-compiler",
+ ],
lint: {
extra_check_modules: ["SystemUILintChecker"],
@@ -466,6 +474,8 @@
"androidx.dynamicanimation_dynamicanimation",
"androidx-constraintlayout_constraintlayout",
"androidx.exifinterface_exifinterface",
+ "androidx.room_room-runtime",
+ "androidx.room_room-ktx",
"kotlinx-coroutines-android",
"kotlinx-coroutines-core",
"kotlinx_coroutines_test",
@@ -530,7 +540,10 @@
"--extra-packages",
"com.android.systemui",
],
- plugins: ["dagger2-compiler"],
+ plugins: [
+ "androidx.room_room-compiler-plugin",
+ "dagger2-compiler",
+ ],
lint: {
test: true,
extra_check_modules: ["SystemUILintChecker"],
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
index 96e1e3f..085fc29 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
@@ -63,7 +63,7 @@
private static final String TAG = "A11yMenuService";
private static final long BUFFER_MILLISECONDS_TO_PREVENT_UPDATE_FAILURE = 100L;
- private static final long TAKE_SCREENSHOT_DELAY_MS = 100L;
+ private static final long HIDE_UI_DELAY_MS = 100L;
private static final int BRIGHTNESS_UP_INCREMENT_GAMMA =
(int) Math.ceil(BrightnessUtils.GAMMA_SPACE_MAX * 0.11f);
@@ -296,7 +296,14 @@
} else if (viewTag == ShortcutId.ID_RECENT_VALUE.ordinal()) {
performGlobalActionInternal(GLOBAL_ACTION_RECENTS);
} else if (viewTag == ShortcutId.ID_LOCKSCREEN_VALUE.ordinal()) {
- performGlobalActionInternal(GLOBAL_ACTION_LOCK_SCREEN);
+ if (Flags.a11yMenuHideBeforeTakingAction()) {
+ // Delay before locking the screen to give time for the UI to close.
+ mHandler.postDelayed(
+ () -> performGlobalActionInternal(GLOBAL_ACTION_LOCK_SCREEN),
+ HIDE_UI_DELAY_MS);
+ } else {
+ performGlobalActionInternal(GLOBAL_ACTION_LOCK_SCREEN);
+ }
} else if (viewTag == ShortcutId.ID_QUICKSETTING_VALUE.ordinal()) {
performGlobalActionInternal(GLOBAL_ACTION_QUICK_SETTINGS);
} else if (viewTag == ShortcutId.ID_NOTIFICATION_VALUE.ordinal()) {
@@ -306,7 +313,7 @@
// Delay before taking a screenshot to give time for the UI to close.
mHandler.postDelayed(
() -> performGlobalActionInternal(GLOBAL_ACTION_TAKE_SCREENSHOT),
- TAKE_SCREENSHOT_DELAY_MS);
+ HIDE_UI_DELAY_MS);
} else {
performGlobalActionInternal(GLOBAL_ACTION_TAKE_SCREENSHOT);
}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 8019b38..9700bc6 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -46,6 +46,14 @@
}
flag {
+ name: "notifications_live_data_store_refactor"
+ namespace: "systemui"
+ description: "Replaces NotifLiveDataStore with ActiveNotificationListRepository, and updates consumers. "
+ "Should not bring any behavior changes."
+ bug: "308623704"
+}
+
+flag {
name: "scene_container"
namespace: "systemui"
description: "Enables the scene container framework go/flexiglass."
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index 56970d7..d668c69 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -154,7 +154,7 @@
Bouncer(
viewModel = viewModel,
dialogFactory = dialogFactory,
- isUserInputAreaVisible = true,
+ userInputAreaVisibility = UserInputAreaVisibility.FULL,
modifier = childModifier,
)
Layout.SIDE_BY_SIDE ->
@@ -189,7 +189,7 @@
private fun Bouncer(
viewModel: BouncerViewModel,
dialogFactory: BouncerSceneDialogFactory,
- isUserInputAreaVisible: Boolean,
+ userInputAreaVisibility: UserInputAreaVisibility,
modifier: Modifier = Modifier,
) {
val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState()
@@ -214,12 +214,11 @@
}
Box(Modifier.weight(1f)) {
- if (isUserInputAreaVisible) {
- UserInputArea(
- viewModel = viewModel,
- modifier = Modifier.align(Alignment.Center),
- )
- }
+ UserInputArea(
+ viewModel = viewModel,
+ visibility = userInputAreaVisibility,
+ modifier = Modifier.align(Alignment.Center),
+ )
}
if (viewModel.isEmergencyButtonVisible) {
@@ -269,6 +268,7 @@
@Composable
private fun UserInputArea(
viewModel: BouncerViewModel,
+ visibility: UserInputAreaVisibility,
modifier: Modifier = Modifier,
) {
val authMethodViewModel: AuthMethodBouncerViewModel? by
@@ -276,21 +276,46 @@
when (val nonNullViewModel = authMethodViewModel) {
is PinBouncerViewModel ->
- PinBouncer(
- viewModel = nonNullViewModel,
- modifier = modifier,
- )
+ when (visibility) {
+ UserInputAreaVisibility.FULL ->
+ PinBouncer(
+ viewModel = nonNullViewModel,
+ modifier = modifier,
+ )
+ UserInputAreaVisibility.INPUT_ONLY ->
+ PinPad(
+ viewModel = nonNullViewModel,
+ modifier = modifier,
+ )
+ UserInputAreaVisibility.OUTPUT_ONLY ->
+ PinInputDisplay(
+ viewModel = nonNullViewModel,
+ modifier = modifier,
+ )
+ UserInputAreaVisibility.NONE -> {}
+ }
is PasswordBouncerViewModel ->
- PasswordBouncer(
- viewModel = nonNullViewModel,
- modifier = modifier,
- )
+ when (visibility) {
+ UserInputAreaVisibility.FULL,
+ UserInputAreaVisibility.INPUT_ONLY ->
+ PasswordBouncer(
+ viewModel = nonNullViewModel,
+ modifier = modifier,
+ )
+ else -> {}
+ }
is PatternBouncerViewModel ->
- PatternBouncer(
- viewModel = nonNullViewModel,
- modifier =
- Modifier.aspectRatio(1f, matchHeightConstraintsFirst = false).then(modifier)
- )
+ when (visibility) {
+ UserInputAreaVisibility.FULL,
+ UserInputAreaVisibility.INPUT_ONLY ->
+ PatternBouncer(
+ viewModel = nonNullViewModel,
+ modifier =
+ Modifier.aspectRatio(1f, matchHeightConstraintsFirst = false)
+ .then(modifier)
+ )
+ else -> {}
+ }
else -> Unit
}
}
@@ -435,13 +460,14 @@
Bouncer(
viewModel = viewModel,
dialogFactory = dialogFactory,
- isUserInputAreaVisible = false,
+ userInputAreaVisibility = UserInputAreaVisibility.OUTPUT_ONLY,
modifier = startContentModifier,
)
},
endContent = { endContentModifier ->
UserInputArea(
viewModel = viewModel,
+ visibility = UserInputAreaVisibility.INPUT_ONLY,
modifier = endContentModifier,
)
},
@@ -545,7 +571,7 @@
Bouncer(
viewModel = viewModel,
dialogFactory = dialogFactory,
- isUserInputAreaVisible = true,
+ userInputAreaVisibility = UserInputAreaVisibility.FULL,
modifier = endContentModifier,
)
},
@@ -574,7 +600,7 @@
Bouncer(
viewModel = viewModel,
dialogFactory = dialogFactory,
- isUserInputAreaVisible = true,
+ userInputAreaVisibility = UserInputAreaVisibility.FULL,
modifier = Modifier.fillMaxWidth().weight(1f),
)
}
@@ -630,6 +656,27 @@
SPLIT,
}
+/** Enumerates all supported user-input area visibilities. */
+private enum class UserInputAreaVisibility {
+ /**
+ * The entire user input area is shown, including where the user enters input and where it's
+ * reflected to the user.
+ */
+ FULL,
+ /**
+ * Only the area where the user enters the input is shown; the area where the input is reflected
+ * back to the user is not shown.
+ */
+ INPUT_ONLY,
+ /**
+ * Only the area where the input is reflected back to the user is shown; the area where the
+ * input is entered by the user is not shown.
+ */
+ OUTPUT_ONLY,
+ /** The entire user input area is hidden. */
+ NONE,
+}
+
/**
* Calculates an alpha for the user switcher and bouncer such that it's at `1` when the offset of
* the two reaches a stopping point but `0` in the middle of the transition.
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
index 6491b70..84e0167 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
@@ -55,12 +55,12 @@
import com.android.compose.animation.Easings
import com.android.compose.grid.VerticalGrid
import com.android.compose.modifiers.thenIf
-import com.android.systemui.res.R
import com.android.systemui.bouncer.ui.viewmodel.ActionButtonAppearance
import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.res.R
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.DurationUnit
import kotlinx.coroutines.async
@@ -93,7 +93,10 @@
}
@Composable
-private fun PinPad(viewModel: PinBouncerViewModel) {
+fun PinPad(
+ viewModel: PinBouncerViewModel,
+ modifier: Modifier = Modifier,
+) {
val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState()
val backspaceButtonAppearance by viewModel.backspaceButtonAppearance.collectAsState()
val confirmButtonAppearance by viewModel.confirmButtonAppearance.collectAsState()
@@ -112,6 +115,7 @@
columns = 3,
verticalSpacing = 12.dp,
horizontalSpacing = 20.dp,
+ modifier = modifier,
) {
repeat(9) { index ->
DigitButton(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt
index 055ece3..814ea31 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt
@@ -51,10 +51,10 @@
import androidx.compose.ui.unit.dp
import com.android.compose.animation.Easings
import com.android.keyguard.PinShapeAdapter
-import com.android.systemui.res.R
import com.android.systemui.bouncer.ui.viewmodel.EntryToken.Digit
import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.PinInputViewModel
+import com.android.systemui.res.R
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
@@ -65,7 +65,10 @@
import kotlinx.coroutines.launch
@Composable
-fun PinInputDisplay(viewModel: PinBouncerViewModel) {
+fun PinInputDisplay(
+ viewModel: PinBouncerViewModel,
+ modifier: Modifier = Modifier,
+) {
val hintedPinLength: Int? by viewModel.hintedPinLength.collectAsState()
val shapeAnimations = rememberShapeAnimations(viewModel.pinShapes)
@@ -81,8 +84,8 @@
// unifying into a single, more complex implementation.
when (val length = hintedPinLength) {
- null -> RegularPinInputDisplay(viewModel, shapeAnimations)
- else -> HintingPinInputDisplay(viewModel, shapeAnimations, length)
+ null -> RegularPinInputDisplay(viewModel, shapeAnimations, modifier)
+ else -> HintingPinInputDisplay(viewModel, shapeAnimations, length, modifier)
}
}
@@ -97,6 +100,7 @@
viewModel: PinBouncerViewModel,
shapeAnimations: ShapeAnimations,
hintedPinLength: Int,
+ modifier: Modifier = Modifier,
) {
val pinInput: PinInputViewModel by viewModel.pinInput.collectAsState()
// [ClearAll] marker pointing at the beginning of the current pin input.
@@ -151,7 +155,7 @@
LaunchedEffect(Unit) { playAnimation = true }
val dotColor = MaterialTheme.colorScheme.onSurfaceVariant
- Row(modifier = Modifier.heightIn(min = shapeAnimations.shapeSize)) {
+ Row(modifier = modifier.heightIn(min = shapeAnimations.shapeSize)) {
pinEntryDrawable.forEachIndexed { index, drawable ->
// Key the loop by [index] and [drawable], so that updating a shape drawable at the same
// index will play the new animation (by remembering a new [atEnd]).
@@ -183,6 +187,7 @@
private fun RegularPinInputDisplay(
viewModel: PinBouncerViewModel,
shapeAnimations: ShapeAnimations,
+ modifier: Modifier = Modifier,
) {
// Holds all currently [VisiblePinEntry] composables. This cannot be simply derived from
// `viewModel.pinInput` at composition, since deleting a pin entry needs to play a remove
@@ -226,7 +231,7 @@
}
}
- pinInputRow.Content()
+ pinInputRow.Content(modifier)
}
private class PinInputRow(
@@ -235,10 +240,11 @@
private val entries = mutableStateListOf<PinInputEntry>()
@Composable
- fun Content() {
+ fun Content(modifier: Modifier) {
Row(
modifier =
- Modifier.heightIn(min = shapeAnimations.shapeSize)
+ modifier
+ .heightIn(min = shapeAnimations.shapeSize)
// Pins overflowing horizontally should still be shown as scrolling.
.wrapContentSize(unbounded = true),
) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 87a8c35..17726ab 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.systemui.communal.ui.compose
import android.appwidget.AppWidgetHostView
@@ -12,19 +28,26 @@
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.Close
import androidx.compose.material3.Card
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.ui.model.CommunalContentUiModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.res.R
@Composable
fun CommunalHub(
@@ -64,10 +87,17 @@
ContentCard(
modifier = Modifier.size(Dimensions.CardWidth, widget.size.dp()),
model = widget,
+ deleteOnClick = viewModel::onDeleteWidget
)
}
}
}
+ IconButton(onClick = viewModel::onOpenWidgetPicker) {
+ Icon(
+ Icons.Default.Add,
+ LocalContext.current.getString(R.string.button_to_open_widget_picker)
+ )
+ }
}
}
@@ -80,19 +110,36 @@
@Composable
private fun ContentCard(
model: CommunalContentUiModel,
+ deleteOnClick: (id: Int) -> Unit,
modifier: Modifier = Modifier,
) {
- AndroidView(
- modifier = modifier,
- factory = {
- model.view.apply {
- if (this is AppWidgetHostView) {
- val size = SizeF(Dimensions.CardWidth.value, model.size.dp().value)
- updateAppWidgetSize(Bundle.EMPTY, listOf(size))
- }
+ // TODO(b/309009246): update background color
+ Box(
+ modifier = modifier.fillMaxSize().background(Color.White),
+ ) {
+ // TODO(b/308148193): this will be cleaned up soon once the change to convert to
+ // CommunalContentUiModel interface is merged
+ val widgetId = getWidgetId(model.id)
+ widgetId?.let {
+ IconButton(onClick = { deleteOnClick(it) }) {
+ Icon(
+ Icons.Default.Close,
+ LocalContext.current.getString(R.string.button_to_remove_widget)
+ )
}
- },
- )
+ }
+ AndroidView(
+ modifier = modifier,
+ factory = {
+ model.view.apply {
+ if (this is AppWidgetHostView) {
+ val size = SizeF(Dimensions.CardWidth.value, model.size.dp().value)
+ updateAppWidgetSize(Bundle.EMPTY, listOf(size))
+ }
+ }
+ },
+ )
+ }
}
private fun CommunalContentSize.dp(): Dp {
@@ -103,6 +150,10 @@
}
}
+private fun getWidgetId(id: String): Int? {
+ return if (id.startsWith("widget_")) id.substring("widget_".length).toInt() else null
+}
+
// Sizes for the tutorial placeholders.
private val tutorialContentSizes =
listOf(
diff --git a/packages/SystemUI/plugin/Android.bp b/packages/SystemUI/plugin/Android.bp
index 1beb55b..0537f17 100644
--- a/packages/SystemUI/plugin/Android.bp
+++ b/packages/SystemUI/plugin/Android.bp
@@ -26,12 +26,19 @@
name: "SystemUIPluginLib",
srcs: [
- "src/**/*.java",
- "src/**/*.kt",
"bcsmartspace/src/**/*.java",
"bcsmartspace/src/**/*.kt",
+ "src/**/*.java",
+ "src/**/*.kt",
],
+ optimize: {
+ proguard_flags_files: [
+ "proguard_plugins.flags",
+ ],
+ export_proguard_flags_files: true,
+ },
+
// If you add a static lib here, you may need to also add the package to the ClassLoaderFilter
// in PluginInstance. That will ensure that loaded plugins have access to the related classes.
// You should also add it to proguard_common.flags so that proguard does not remove the portions
@@ -43,6 +50,7 @@
"SystemUIAnimationLib",
"SystemUICommon",
"SystemUILogLib",
+ "androidx.annotation_annotation",
],
}
diff --git a/packages/SystemUI/plugin/proguard_plugins.flags b/packages/SystemUI/plugin/proguard_plugins.flags
new file mode 100644
index 0000000..abac27f
--- /dev/null
+++ b/packages/SystemUI/plugin/proguard_plugins.flags
@@ -0,0 +1,9 @@
+# The plugins and core log subpackages act as shared libraries that might be referenced in
+# dynamically-loaded plugin APKs.
+-keep class com.android.systemui.plugins.** {
+ *;
+}
+
+-keep class com.android.systemui.log.core.** {
+ *;
+}
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index b534fcec..42b5923 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -4,4 +4,4 @@
*;
}
--keep,allowoptimization,allowaccessmodification class com.android.systemui.dagger.DaggerReferenceGlobalRootComponent** { !synthetic *; }
\ No newline at end of file
+-keep,allowoptimization,allowaccessmodification class com.android.systemui.dagger.DaggerReferenceGlobalRootComponent** { !synthetic *; }
diff --git a/packages/SystemUI/proguard_common.flags b/packages/SystemUI/proguard_common.flags
index 73ae59a..21b019e 100644
--- a/packages/SystemUI/proguard_common.flags
+++ b/packages/SystemUI/proguard_common.flags
@@ -1,3 +1,4 @@
+-include proguard_kotlin.flags
-keep class com.android.systemui.VendorServices
# Needed to ensure callback field references are kept in their respective
@@ -20,14 +21,6 @@
public <init>(android.content.Context, android.util.AttributeSet);
}
-# The plugins and core log subpackages act as shared libraries that might be referenced in
-# dynamically-loaded plugin APKs.
--keep class com.android.systemui.plugins.** {
- *;
-}
--keep class com.android.systemui.log.core.** {
- *;
-}
-keep class androidx.core.app.CoreComponentFactory
# Keep the wm shell lib
@@ -49,45 +42,6 @@
# part of optimization. This lets proguard inline trivial getter/setter methods.
-allowaccessmodification
-# Removes runtime checks added through Kotlin to JVM code genereration to
-# avoid linear growth as more Kotlin code is converted / added to the codebase.
-# These checks are generally applied to Java platform types (values returned
-# from Java code that don't have nullness annotations), but we remove them to
-# avoid code size increases.
-#
-# See also https://kotlinlang.org/docs/reference/java-interop.html
-#
-# TODO(b/199941987): Consider standardizing these rules in a central place as
-# Kotlin gains adoption with other platform targets.
--assumenosideeffects class kotlin.jvm.internal.Intrinsics {
- # Remove check for method parameters being null
- static void checkParameterIsNotNull(java.lang.Object, java.lang.String);
-
- # When a Java platform type is returned and passed to Kotlin NonNull method,
- # remove the null check
- static void checkExpressionValueIsNotNull(java.lang.Object, java.lang.String);
- static void checkNotNullExpressionValue(java.lang.Object, java.lang.String);
-
- # Remove check that final value returned from method is null, if passing
- # back Java platform type.
- static void checkReturnedValueIsNotNull(java.lang.Object, java.lang.String, java.lang.String);
- static void checkReturnedValueIsNotNull(java.lang.Object, java.lang.String);
-
- # Null check for accessing a field from a parent class written in Java.
- static void checkFieldIsNotNull(java.lang.Object, java.lang.String, java.lang.String);
- static void checkFieldIsNotNull(java.lang.Object, java.lang.String);
-
- # Removes code generated from !! operator which converts Nullable type to
- # NonNull type. These would throw an NPE immediate after on access.
- static void checkNotNull(java.lang.Object, java.lang.String);
- static void checkNotNullParameter(java.lang.Object, java.lang.String);
-
- # Removes lateinit var check being used before being set. Check is applied
- # on every field access without this.
- static void throwUninitializedPropertyAccessException(java.lang.String);
-}
-
-
# Strip verbose logs.
-assumenosideeffects class android.util.Log {
static *** v(...);
diff --git a/packages/SystemUI/proguard_kotlin.flags b/packages/SystemUI/proguard_kotlin.flags
new file mode 100644
index 0000000..ceea3c8
--- /dev/null
+++ b/packages/SystemUI/proguard_kotlin.flags
@@ -0,0 +1,37 @@
+# Removes runtime checks added through Kotlin to JVM code genereration to
+# avoid linear growth as more Kotlin code is converted / added to the codebase.
+# These checks are generally applied to Java platform types (values returned
+# from Java code that don't have nullness annotations), but we remove them to
+# avoid code size increases.
+#
+# See also https://kotlinlang.org/docs/reference/java-interop.html
+#
+# TODO(b/199941987): Consider standardizing these rules in a central place as
+# Kotlin gains adoption with other platform targets.
+-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
+ # Remove check for method parameters being null
+ static void checkParameterIsNotNull(java.lang.Object, java.lang.String);
+
+ # When a Java platform type is returned and passed to Kotlin NonNull method,
+ # remove the null check
+ static void checkExpressionValueIsNotNull(java.lang.Object, java.lang.String);
+ static void checkNotNullExpressionValue(java.lang.Object, java.lang.String);
+
+ # Remove check that final value returned from method is null, if passing
+ # back Java platform type.
+ static void checkReturnedValueIsNotNull(java.lang.Object, java.lang.String, java.lang.String);
+ static void checkReturnedValueIsNotNull(java.lang.Object, java.lang.String);
+
+ # Null check for accessing a field from a parent class written in Java.
+ static void checkFieldIsNotNull(java.lang.Object, java.lang.String, java.lang.String);
+ static void checkFieldIsNotNull(java.lang.Object, java.lang.String);
+
+ # Removes code generated from !! operator which converts Nullable type to
+ # NonNull type. These would throw an NPE immediate after on access.
+ static void checkNotNull(java.lang.Object, java.lang.String);
+ static void checkNotNullParameter(java.lang.Object, java.lang.String);
+
+ # Removes lateinit var check being used before being set. Check is applied
+ # on every field access without this.
+ static void throwUninitializedPropertyAccessException(java.lang.String);
+}
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 1a37e2d..73ee50d 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -736,6 +736,8 @@
<!-- Whether the communal service should be enabled -->
<bool name="config_communalServiceEnabled">false</bool>
+ <!-- Name of the database that stores info of widgets shown on glanceable hub -->
+ <string name="config_communalDatabase" translatable="false">communal_db</string>
<!-- Component names of allowed communal widgets -->
<string-array name="config_communalWidgetAllowlist" translatable="false" />
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 12bff4a..9a3c6d5 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1048,6 +1048,11 @@
<!-- Indicator on keyguard to start the communal tutorial. [CHAR LIMIT=100] -->
<string name="communal_tutorial_indicator_text">Swipe left to start the communal tutorial</string>
+ <!-- Description for the button that opens the widget picker on click. [CHAR LIMIT=50] -->
+ <string name="button_to_open_widget_picker">Open the widget picker</string>
+ <!-- Description for the button that removes a widget on click. [CHAR LIMIT=50] -->
+ <string name="button_to_remove_widget">Remove a widget</string>
+
<!-- Related to user switcher --><skip/>
<!-- Accessibility label for the button that opens the user switcher. -->
@@ -3261,4 +3266,9 @@
<string name="privacy_dialog_active_app_usage_2">In use by <xliff:g id="app_name" example="Gmail">%1$s</xliff:g> (<xliff:g id="attribution_label" example="For Wallet">%2$s</xliff:g> \u2022 <xliff:g id="proxy_label" example="Speech services">%3$s</xliff:g>)</string>
<!-- Label for recent app usage of a phone sensor with sub-attribution and proxy label in the privacy dialog [CHAR LIMIT=NONE] -->
<string name="privacy_dialog_recent_app_usage_2">Recently used by <xliff:g id="app_name" example="Gmail">%1$s</xliff:g> (<xliff:g id="attribution_label" example="For Wallet">%2$s</xliff:g> \u2022 <xliff:g id="proxy_label" example="Speech services">%3$s</xliff:g>)</string>
+
+ <!-- Content description for keyboard backlight brightness dialog [CHAR LIMIT=NONE] -->
+ <string name="keyboard_backlight_dialog_title">Keyboard backlight</string>
+ <!-- Content description for keyboard backlight brightness value [CHAR LIMIT=NONE] -->
+ <string name="keyboard_backlight_value">Level %1$d of %2$d</string>
</resources>
diff --git a/packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/1.json b/packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/1.json
new file mode 100644
index 0000000..ffc4d91
--- /dev/null
+++ b/packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/1.json
@@ -0,0 +1,79 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 1,
+ "identityHash": "38f223811a414587ee1b6445ae19385d",
+ "entities": [
+ {
+ "tableName": "communal_widget_table",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `widget_id` INTEGER NOT NULL, `component_name` TEXT NOT NULL, `item_id` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "uid",
+ "columnName": "uid",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "widgetId",
+ "columnName": "widget_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "componentName",
+ "columnName": "component_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "itemId",
+ "columnName": "item_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "uid"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "communal_item_rank_table",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `rank` INTEGER NOT NULL DEFAULT 0)",
+ "fields": [
+ {
+ "fieldPath": "uid",
+ "columnName": "uid",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "rank",
+ "columnName": "rank",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "uid"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ }
+ ],
+ "views": [],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '38f223811a414587ee1b6445ae19385d')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java b/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
index a14f971..f005af3 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
@@ -28,6 +28,7 @@
import android.graphics.RecordingCanvas;
import android.graphics.drawable.Drawable;
import android.os.Handler;
+import android.os.Looper;
import android.os.Trace;
import android.view.RenderNodeAnimator;
import android.view.View;
@@ -48,6 +49,7 @@
private static final float GLOW_MAX_ALPHA_DARK = 0.1f;
private static final int ANIMATION_DURATION_SCALE = 350;
private static final int ANIMATION_DURATION_FADE = 450;
+ private static final int ANIMATION_DURATION_FADE_FAST = 80;
private static final Interpolator ALPHA_OUT_INTERPOLATOR =
new PathInterpolator(0f, 0f, 0.8f, 1f);
@@ -71,6 +73,9 @@
private boolean mLastDark;
private boolean mDark;
private boolean mDelayTouchFeedback;
+ private boolean mSpeedUpNextFade;
+ // When non-null, this runs the next time this ripple is drawn invisibly.
+ private Runnable mOnInvisibleRunnable;
private final Interpolator mInterpolator = new LogInterpolator();
private boolean mSupportHardware;
@@ -112,6 +117,18 @@
mDelayTouchFeedback = delay;
}
+ /** Next time we fade out (pressed==false), use a shorter duration than the standard. */
+ public void speedUpNextFade() {
+ mSpeedUpNextFade = true;
+ }
+
+ /**
+ * @param onInvisibleRunnable run after we are next drawn invisibly. Only used once.
+ */
+ void setOnInvisibleRunnable(Runnable onInvisibleRunnable) {
+ mOnInvisibleRunnable = onInvisibleRunnable;
+ }
+
public void setType(Type type) {
mType = type;
}
@@ -161,6 +178,11 @@
} else {
drawSoftware(canvas);
}
+
+ if (!mPressed && !mVisible && mOnInvisibleRunnable != null) {
+ new Handler(Looper.getMainLooper()).post(mOnInvisibleRunnable);
+ mOnInvisibleRunnable = null;
+ }
}
@Override
@@ -270,7 +292,7 @@
return true;
}
- public void setPressed(boolean pressed) {
+ private void setPressed(boolean pressed) {
if (mDark != mLastDark && pressed) {
mRipplePaint = null;
mLastDark = mDark;
@@ -350,7 +372,7 @@
private void exitSoftware() {
ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(this, "glowAlpha", mGlowAlpha, 0f);
alphaAnimator.setInterpolator(ALPHA_OUT_INTERPOLATOR);
- alphaAnimator.setDuration(ANIMATION_DURATION_FADE);
+ alphaAnimator.setDuration(getFadeDuration());
alphaAnimator.addListener(mAnimatorListener);
alphaAnimator.start();
mRunningAnimations.add(alphaAnimator);
@@ -414,6 +436,12 @@
return Math.min(size, mMaxWidth);
}
+ private int getFadeDuration() {
+ int duration = mSpeedUpNextFade ? ANIMATION_DURATION_FADE_FAST : ANIMATION_DURATION_FADE;
+ mSpeedUpNextFade = false;
+ return duration;
+ }
+
private void enterHardware() {
endAnimations("enterHardware", true /* cancel */);
mVisible = true;
@@ -471,7 +499,7 @@
mPaintProp = CanvasProperty.createPaint(getRipplePaint());
final RenderNodeAnimator opacityAnim = new RenderNodeAnimator(mPaintProp,
RenderNodeAnimator.PAINT_ALPHA, 0);
- opacityAnim.setDuration(ANIMATION_DURATION_FADE);
+ opacityAnim.setDuration(getFadeDuration());
opacityAnim.setInterpolator(ALPHA_OUT_INTERPOLATOR);
opacityAnim.addListener(mAnimatorListener);
opacityAnim.addListener(mExitHwTraceAnimator);
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 181aa5c..f091558 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -22,6 +22,7 @@
import static com.android.keyguard.LockIconView.ICON_FINGERPRINT;
import static com.android.keyguard.LockIconView.ICON_LOCK;
import static com.android.keyguard.LockIconView.ICON_UNLOCK;
+import static com.android.systemui.Flags.keyguardBottomAreaRefactor;
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
import static com.android.systemui.flags.Flags.DOZING_MIGRATION_1;
import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
@@ -459,7 +460,7 @@
private void updateLockIconLocation() {
final float scaleFactor = mAuthController.getScaleFactor();
final int scaledPadding = (int) (mDefaultPaddingPx * scaleFactor);
- if (mFeatureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) {
+ if (keyguardBottomAreaRefactor()) {
mView.getLockIcon().setPadding(scaledPadding, scaledPadding, scaledPadding,
scaledPadding);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/TEST_MAPPING b/packages/SystemUI/src/com/android/systemui/accessibility/TEST_MAPPING
index 055fad1..be26b43 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/TEST_MAPPING
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/TEST_MAPPING
@@ -5,6 +5,18 @@
"options": [
{
"include-filter": "com.android.systemui.accessibility"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "android.platform.test.annotations.Postsubmit"
+ },
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
index b8e2de4..c3421de 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -16,6 +16,7 @@
package com.android.systemui.communal.dagger
+import com.android.systemui.communal.data.db.CommunalDatabaseModule
import com.android.systemui.communal.data.repository.CommunalRepositoryModule
import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule
import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule
@@ -27,6 +28,7 @@
CommunalRepositoryModule::class,
CommunalTutorialRepositoryModule::class,
CommunalWidgetRepositoryModule::class,
+ CommunalDatabaseModule::class,
]
)
class CommunalModule
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
new file mode 100644
index 0000000..595d320
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.data.db
+
+import androidx.room.Database
+import androidx.room.RoomDatabase
+
+@Database(entities = [CommunalWidgetItem::class, CommunalItemRank::class], version = 1)
+abstract class CommunalDatabase : RoomDatabase() {
+ abstract fun communalWidgetDao(): CommunalWidgetDao
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabaseModule.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabaseModule.kt
new file mode 100644
index 0000000..e766290
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabaseModule.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.data.db
+
+import android.content.Context
+import androidx.room.Room
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.res.R
+import dagger.Module
+import dagger.Provides
+
+@Module
+interface CommunalDatabaseModule {
+ companion object {
+ @SysUISingleton
+ @Provides
+ fun provideCommunalDatabase(
+ @Application context: Context,
+ defaultWidgetPopulation: DefaultWidgetPopulation,
+ ): CommunalDatabase {
+ return Room.databaseBuilder(
+ context,
+ CommunalDatabase::class.java,
+ context.resources.getString(R.string.config_communalDatabase)
+ )
+ .fallbackToDestructiveMigration()
+ .addCallback(defaultWidgetPopulation)
+ .build()
+ }
+
+ @SysUISingleton
+ @Provides
+ fun provideCommunalWidgetDao(database: CommunalDatabase): CommunalWidgetDao =
+ database.communalWidgetDao()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalEntities.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalEntities.kt
new file mode 100644
index 0000000..0d5336a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalEntities.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.data.db
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity(tableName = "communal_widget_table")
+data class CommunalWidgetItem(
+ @PrimaryKey(autoGenerate = true) val uid: Long,
+ /** Id of an app widget */
+ @ColumnInfo(name = "widget_id") val widgetId: Int,
+ /** Component name of the app widget provider */
+ @ColumnInfo(name = "component_name") val componentName: String,
+ /** Reference the id of an item persisted in the glanceable hub */
+ @ColumnInfo(name = "item_id") val itemId: Long,
+)
+
+@Entity(tableName = "communal_item_rank_table")
+data class CommunalItemRank(
+ /** Unique id of an item persisted in the glanceable hub */
+ @PrimaryKey(autoGenerate = true) val uid: Long,
+ /** Order in which the item will be displayed */
+ @ColumnInfo(name = "rank", defaultValue = "0") val rank: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
new file mode 100644
index 0000000..e50850d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.data.db
+
+import android.content.ComponentName
+import androidx.room.Dao
+import androidx.room.Delete
+import androidx.room.Query
+import androidx.room.RoomDatabase
+import androidx.room.Transaction
+import androidx.sqlite.db.SupportSQLiteDatabase
+import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule.Companion.DEFAULT_WIDGETS
+import com.android.systemui.communal.shared.CommunalWidgetHost
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.CommunalLog
+import javax.inject.Inject
+import javax.inject.Named
+import javax.inject.Provider
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+/**
+ * Callback that will be invoked when the Room database is created. Then the database will be
+ * populated with pre-configured default widgets to be rendered in the glanceable hub.
+ */
+class DefaultWidgetPopulation
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ private val communalWidgetHost: CommunalWidgetHost,
+ private val communalWidgetDaoProvider: Provider<CommunalWidgetDao>,
+ @Named(DEFAULT_WIDGETS) private val defaultWidgets: Array<String>,
+ @CommunalLog logBuffer: LogBuffer,
+) : RoomDatabase.Callback() {
+ companion object {
+ private const val TAG = "DefaultWidgetPopulation"
+ }
+ private val logger = Logger(logBuffer, TAG)
+
+ override fun onCreate(db: SupportSQLiteDatabase) {
+ super.onCreate(db)
+ applicationScope.launch {
+ addDefaultWidgets()
+ logger.i("Default widgets were populated in the database.")
+ }
+ }
+
+ // Read default widgets from config.xml and populate the database.
+ private suspend fun addDefaultWidgets() =
+ withContext(bgDispatcher) {
+ defaultWidgets.forEachIndexed { index, name ->
+ val provider = ComponentName.unflattenFromString(name)
+ provider?.let {
+ val id = communalWidgetHost.allocateIdAndBindWidget(provider)
+ id?.let {
+ communalWidgetDaoProvider
+ .get()
+ .addWidget(
+ widgetId = id,
+ provider = provider,
+ priority = defaultWidgets.size - index
+ )
+ }
+ }
+ }
+ }
+}
+
+@Dao
+interface CommunalWidgetDao {
+ @Query(
+ "SELECT * FROM communal_widget_table JOIN communal_item_rank_table " +
+ "ON communal_item_rank_table.uid = communal_widget_table.item_id"
+ )
+ fun getWidgets(): Flow<Map<CommunalItemRank, CommunalWidgetItem>>
+
+ @Query("SELECT * FROM communal_widget_table WHERE widget_id = :id")
+ fun getWidgetByIdNow(id: Int): CommunalWidgetItem
+
+ @Delete fun deleteWidgets(vararg widgets: CommunalWidgetItem)
+
+ @Query("DELETE FROM communal_item_rank_table WHERE uid = :itemId")
+ fun deleteItemRankById(itemId: Long)
+
+ @Query(
+ "INSERT INTO communal_widget_table(widget_id, component_name, item_id) " +
+ "VALUES(:widgetId, :componentName, :itemId)"
+ )
+ fun insertWidget(widgetId: Int, componentName: String, itemId: Long): Long
+
+ @Query("INSERT INTO communal_item_rank_table(rank) VALUES(:rank)")
+ fun insertItemRank(rank: Int): Long
+
+ @Transaction
+ fun addWidget(widgetId: Int, provider: ComponentName, priority: Int): Long {
+ return insertWidget(
+ widgetId = widgetId,
+ componentName = provider.flattenToString(),
+ insertItemRank(priority),
+ )
+ }
+
+ @Transaction
+ fun deleteWidgetById(widgetId: Int) {
+ val widget = getWidgetByIdNow(widgetId)
+ deleteItemRankById(widget.itemId)
+ deleteWidgets(widget)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt
deleted file mode 100644
index 1a214ba..0000000
--- a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.communal.data.model
-
-import com.android.systemui.communal.shared.model.CommunalContentSize
-
-/** Metadata for the default widgets */
-data class CommunalWidgetMetadata(
- /* Widget provider component name */
- val componentName: String,
-
- /* Defines the order in which the widget will be rendered in the grid. */
- val priority: Int,
-
- /* Supported sizes */
- val sizes: List<CommunalContentSize>
-)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
index 5c4ee35..b40570b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.systemui.communal.data.repository
import com.android.systemui.Flags.communalHub
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepositoryModule.kt
index 9d95b9e..1de3459 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepositoryModule.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.systemui.communal.data.repository
import dagger.Binds
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index 77025dc..6b27ce0 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -28,47 +28,62 @@
import android.os.UserManager
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.communal.data.model.CommunalWidgetMetadata
+import com.android.systemui.communal.data.db.CommunalItemRank
+import com.android.systemui.communal.data.db.CommunalWidgetDao
+import com.android.systemui.communal.data.db.CommunalWidgetItem
+import com.android.systemui.communal.shared.CommunalWidgetHost
import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
-import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
-import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
/** Encapsulates the state of widgets for communal mode. */
interface CommunalWidgetRepository {
/** A flow of provider info for the stopwatch widget, or null if widget is unavailable. */
val stopwatchAppWidgetInfo: Flow<CommunalAppWidgetInfo?>
- /** Widgets that are allowed to render in the glanceable hub */
- val communalWidgetAllowlist: List<CommunalWidgetMetadata>
-
- /** A flow of information about all the communal widgets to show. */
+ /** A flow of information about active communal widgets stored in database. */
val communalWidgets: Flow<List<CommunalWidgetContentModel>>
+
+ /** Add a widget at the specified position in the app widget service and the database. */
+ fun addWidget(provider: ComponentName, priority: Int) {}
+
+ /** Delete a widget by id from app widget service and the database. */
+ fun deleteWidget(widgetId: Int) {}
}
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class CommunalWidgetRepositoryImpl
@Inject
constructor(
- @Application private val applicationContext: Context,
private val appWidgetManager: AppWidgetManager,
private val appWidgetHost: AppWidgetHost,
+ @Application private val applicationScope: CoroutineScope,
+ @Background private val bgDispatcher: CoroutineDispatcher,
broadcastDispatcher: BroadcastDispatcher,
communalRepository: CommunalRepository,
+ private val communalWidgetHost: CommunalWidgetHost,
+ private val communalWidgetDao: CommunalWidgetDao,
private val packageManager: PackageManager,
private val userManager: UserManager,
private val userTracker: UserTracker,
@@ -79,18 +94,12 @@
const val TAG = "CommunalWidgetRepository"
const val WIDGET_LABEL = "Stopwatch"
}
- override val communalWidgetAllowlist: List<CommunalWidgetMetadata>
private val logger = Logger(logBuffer, TAG)
// Whether the [AppWidgetHost] is listening for updates.
private var isHostListening = false
- init {
- communalWidgetAllowlist =
- if (communalRepository.isCommunalEnabled) getWidgetAllowlist() else emptyList()
- }
-
// Widgets that should be rendered in communal mode.
private val widgets: HashMap<Int, CommunalAppWidgetInfo> = hashMapOf()
@@ -136,7 +145,6 @@
true
} else {
stopListening()
- clearWidgets()
false
}
}
@@ -157,57 +165,50 @@
return@map null
}
- return@map addWidget(providerInfo)
+ return@map addStopWatchWidget(providerInfo)
}
override val communalWidgets: Flow<List<CommunalWidgetContentModel>> =
- isHostActive.map { isHostActive ->
+ isHostActive.flatMapLatest { isHostActive ->
if (!isHostActive) {
- return@map emptyList()
+ return@flatMapLatest flowOf(emptyList())
}
-
- // The allowlist should be fetched from the local database with all the metadata tied to
- // a widget, including an appWidgetId if it has been bound. Before the database is set
- // up, we are going to use the app widget host as the source of truth for bound widgets,
- // and rebind each time on boot.
-
- // Remove all previously bound widgets.
- appWidgetHost.appWidgetIds.forEach { appWidgetHost.deleteAppWidgetId(it) }
-
- val inventory = mutableListOf<CommunalWidgetContentModel>()
-
- // Bind all widgets from the allowlist.
- communalWidgetAllowlist.forEach {
- val id = appWidgetHost.allocateAppWidgetId()
- appWidgetManager.bindAppWidgetId(
- id,
- ComponentName.unflattenFromString(it.componentName),
- )
-
- inventory.add(
- CommunalWidgetContentModel(
- appWidgetId = id,
- providerInfo = appWidgetManager.getAppWidgetInfo(id),
- priority = it.priority,
- )
- )
- }
-
- return@map inventory.toList()
+ communalWidgetDao.getWidgets().map { it.map(::mapToContentModel) }
}
- private fun getWidgetAllowlist(): List<CommunalWidgetMetadata> {
- val componentNames =
- applicationContext.resources.getStringArray(R.array.config_communalWidgetAllowlist)
- return componentNames.mapIndexed { index, name ->
- CommunalWidgetMetadata(
- componentName = name,
- priority = componentNames.size - index,
- sizes = listOf(CommunalContentSize.HALF),
- )
+ override fun addWidget(provider: ComponentName, priority: Int) {
+ applicationScope.launch(bgDispatcher) {
+ val id = communalWidgetHost.allocateIdAndBindWidget(provider)
+ id?.let {
+ communalWidgetDao.addWidget(
+ widgetId = it,
+ provider = provider,
+ priority = priority,
+ )
+ }
+ logger.i("Added widget ${provider.flattenToString()} at position $priority.")
}
}
+ override fun deleteWidget(widgetId: Int) {
+ applicationScope.launch(bgDispatcher) {
+ communalWidgetDao.deleteWidgetById(widgetId)
+ appWidgetHost.deleteAppWidgetId(widgetId)
+ logger.i("Deleted widget with id $widgetId.")
+ }
+ }
+
+ private fun mapToContentModel(
+ entry: Map.Entry<CommunalItemRank, CommunalWidgetItem>
+ ): CommunalWidgetContentModel {
+ val (_, widgetId) = entry.value
+ return CommunalWidgetContentModel(
+ appWidgetId = widgetId,
+ providerInfo = appWidgetManager.getAppWidgetInfo(widgetId),
+ priority = entry.key.rank,
+ )
+ }
+
private fun startListening() {
if (isHostListening) {
return
@@ -226,7 +227,8 @@
isHostListening = false
}
- private fun addWidget(providerInfo: AppWidgetProviderInfo): CommunalAppWidgetInfo {
+ // TODO(b/306471933): remove this prototype that shows a stopwatch in the communal blueprint
+ private fun addStopWatchWidget(providerInfo: AppWidgetProviderInfo): CommunalAppWidgetInfo {
val existing = widgets.values.firstOrNull { it.providerInfo == providerInfo }
if (existing != null) {
return existing
@@ -241,9 +243,4 @@
widgets[appWidgetId] = widget
return widget
}
-
- private fun clearWidgets() {
- widgets.keys.forEach { appWidgetId -> appWidgetHost.deleteAppWidgetId(appWidgetId) }
- widgets.clear()
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt
index 3d1185b..5793f10 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt
@@ -20,16 +20,24 @@
import android.appwidget.AppWidgetHost
import android.appwidget.AppWidgetManager
import android.content.Context
+import android.content.res.Resources
+import com.android.systemui.communal.shared.CommunalWidgetHost
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.dagger.CommunalLog
+import com.android.systemui.res.R
import dagger.Binds
import dagger.Module
import dagger.Provides
+import javax.inject.Named
@Module
interface CommunalWidgetRepositoryModule {
companion object {
private const val APP_WIDGET_HOST_ID = 116
+ const val DEFAULT_WIDGETS = "default_widgets"
@SysUISingleton
@Provides
@@ -42,6 +50,22 @@
fun provideAppWidgetHost(@Application context: Context): AppWidgetHost {
return AppWidgetHost(context, APP_WIDGET_HOST_ID)
}
+
+ @SysUISingleton
+ @Provides
+ fun provideCommunalWidgetHost(
+ appWidgetManager: AppWidgetManager,
+ appWidgetHost: AppWidgetHost,
+ @CommunalLog logBuffer: LogBuffer,
+ ): CommunalWidgetHost {
+ return CommunalWidgetHost(appWidgetManager, appWidgetHost, logBuffer)
+ }
+
+ @Provides
+ @Named(DEFAULT_WIDGETS)
+ fun provideDefaultWidgets(@Main resources: Resources): Array<String> {
+ return resources.getStringArray(R.array.config_communalWidgetAllowlist)
+ }
}
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index ccccbb6..2c683ee 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -16,6 +16,7 @@
package com.android.systemui.communal.domain.interactor
+import android.content.ComponentName
import com.android.systemui.communal.data.repository.CommunalRepository
import com.android.systemui.communal.data.repository.CommunalWidgetRepository
import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
@@ -33,7 +34,7 @@
@Inject
constructor(
private val communalRepository: CommunalRepository,
- widgetRepository: CommunalWidgetRepository,
+ private val widgetRepository: CommunalWidgetRepository,
) {
/** Whether communal features are enabled. */
@@ -68,4 +69,11 @@
fun onSceneChanged(newScene: CommunalSceneKey) {
communalRepository.setDesiredScene(newScene)
}
+
+ /** Add a widget at the specified position. */
+ fun addWidget(componentName: ComponentName, priority: Int) =
+ widgetRepository.addWidget(componentName, priority)
+
+ /** Delete a widget by id. */
+ fun deleteWidget(id: Int) = widgetRepository.deleteWidget(id)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt
new file mode 100644
index 0000000..086d729
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.shared
+
+import android.appwidget.AppWidgetHost
+import android.appwidget.AppWidgetManager
+import android.content.ComponentName
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.CommunalLog
+import javax.inject.Inject
+
+/**
+ * Widget host that interacts with AppWidget service and host to manage and provide info for widgets
+ * shown in the glanceable hub.
+ */
+class CommunalWidgetHost
+@Inject
+constructor(
+ private val appWidgetManager: AppWidgetManager,
+ private val appWidgetHost: AppWidgetHost,
+ @CommunalLog logBuffer: LogBuffer,
+) {
+ companion object {
+ private const val TAG = "CommunalWidgetHost"
+ }
+ private val logger = Logger(logBuffer, TAG)
+
+ /**
+ * Allocate an app widget id and binds the widget.
+ *
+ * @return widgetId if binding is successful; otherwise return null
+ */
+ fun allocateIdAndBindWidget(provider: ComponentName): Int? {
+ val id = appWidgetHost.allocateAppWidgetId()
+ if (bindWidget(id, provider)) {
+ logger.d("Successfully bound the widget $provider")
+ return id
+ }
+ appWidgetHost.deleteAppWidgetId(id)
+ logger.d("Failed to bind the widget $provider")
+ return null
+ }
+
+ private fun bindWidget(widgetId: Int, provider: ComponentName): Boolean =
+ appWidgetManager.bindAppWidgetIdIfAllowed(widgetId, provider)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index de9b563..197dece 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.communal.ui.viewmodel
import android.appwidget.AppWidgetHost
+import android.content.ComponentName
import android.content.Context
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor
@@ -61,4 +62,23 @@
fun onSceneChanged(scene: CommunalSceneKey) {
communalInteractor.onSceneChanged(scene)
}
+
+ /** Delete a widget by id. */
+ fun onDeleteWidget(id: Int) = communalInteractor.deleteWidget(id)
+
+ /** Open the widget picker */
+ fun onOpenWidgetPicker() {
+ // STOPSHIP(b/306500486): refactor this when integrating with the widget picker.
+ // Eventually clicking on this button will bring up the widget picker and inside
+ // the widget picker, addWidget will be called to add the user selected widget.
+ // For now, a stopwatch widget will be added to the end of the grid.
+ communalInteractor.addWidget(
+ componentName =
+ ComponentName(
+ "com.google.android.deskclock",
+ "com.android.alarmclock.StopwatchAppWidgetProvider"
+ ),
+ priority = 0
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 77384c4..dd971b9 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -244,15 +244,6 @@
/** Keyguard Migration */
- /**
- * Migrate the bottom area to the new keyguard root view. Because there is no such thing as a
- * "bottom area" after this, this also breaks it up into many smaller, modular pieces.
- */
- // TODO(b/290652751): Tracking bug.
- @JvmField
- val MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA =
- unreleasedFlag("migrate_split_keyguard_bottom_area")
-
// TODO(b/297037052): Tracking bug.
@JvmField
val REMOVE_NPVC_BOTTOM_AREA_USAGE = unreleasedFlag("remove_npvc_bottom_area_usage")
@@ -264,10 +255,6 @@
// TODO(b/287268101): Tracking bug.
@JvmField val TRANSIT_CLOCK = releasedFlag("lockscreen_custom_transit_clock")
- /** Migrate the lock icon view to the new keyguard root view. */
- // TODO(b/286552209): Tracking bug.
- @JvmField val MIGRATE_LOCK_ICON = unreleasedFlag("migrate_lock_icon")
-
// TODO(b/288276738): Tracking bug.
@JvmField val WIDGET_ON_KEYGUARD = unreleasedFlag("widget_on_keyguard")
@@ -494,12 +481,6 @@
namespace = DeviceConfig.NAMESPACE_WINDOW_MANAGER,
)
- // TODO(b/254512674): Tracking Bug
- @Keep
- @JvmField
- val HIDE_NAVBAR_WINDOW =
- sysPropBooleanFlag("persist.wm.debug.hide_navbar_window", default = false)
-
@Keep
@JvmField
val WM_CAPTION_ON_SHELL =
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
index e16bb0b..1e9be09 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
@@ -30,6 +30,7 @@
import android.view.ViewGroup.MarginLayoutParams
import android.view.Window
import android.view.WindowManager
+import android.view.accessibility.AccessibilityEvent
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
@@ -78,23 +79,29 @@
private lateinit var stepProperties: StepViewProperties
@ColorInt
- var filledRectangleColor = getColorFromStyle(com.android.internal.R.attr.materialColorPrimary)
- @ColorInt
- var emptyRectangleColor =
- getColorFromStyle(com.android.internal.R.attr.materialColorOutlineVariant)
- @ColorInt
- var backgroundColor = getColorFromStyle(com.android.internal.R.attr.materialColorSurfaceBright)
- @ColorInt
- var defaultIconColor = getColorFromStyle(com.android.internal.R.attr.materialColorOnPrimary)
- @ColorInt
- var defaultIconBackgroundColor =
+ private val filledRectangleColor =
getColorFromStyle(com.android.internal.R.attr.materialColorPrimary)
@ColorInt
- var dimmedIconColor = getColorFromStyle(com.android.internal.R.attr.materialColorOnSurface)
+ private val emptyRectangleColor =
+ getColorFromStyle(com.android.internal.R.attr.materialColorOutlineVariant)
@ColorInt
- var dimmedIconBackgroundColor =
+ private val backgroundColor =
+ getColorFromStyle(com.android.internal.R.attr.materialColorSurfaceBright)
+ @ColorInt
+ private val defaultIconColor =
+ getColorFromStyle(com.android.internal.R.attr.materialColorOnPrimary)
+ @ColorInt
+ private val defaultIconBackgroundColor =
+ getColorFromStyle(com.android.internal.R.attr.materialColorPrimary)
+ @ColorInt
+ private val dimmedIconColor =
+ getColorFromStyle(com.android.internal.R.attr.materialColorOnSurface)
+ @ColorInt
+ private val dimmedIconBackgroundColor =
getColorFromStyle(com.android.internal.R.attr.materialColorSurfaceDim)
+ private val levelContentDescription = context.getString(R.string.keyboard_backlight_value)
+
init {
currentLevel = initialCurrentLevel
maxLevel = initialMaxLevel
@@ -103,6 +110,8 @@
override fun onCreate(savedInstanceState: Bundle?) {
setUpWindowProperties(this)
setWindowPosition()
+ // title is used for a11y announcement
+ window?.setTitle(context.getString(R.string.keyboard_backlight_dialog_title))
updateResources()
rootView = buildRootView()
setContentView(rootView)
@@ -159,6 +168,12 @@
currentLevel = current
updateIconTile()
updateStepColors()
+ updateAccessibilityInfo()
+ }
+
+ private fun updateAccessibilityInfo() {
+ rootView.contentDescription = String.format(levelContentDescription, currentLevel, maxLevel)
+ rootView.sendAccessibilityEvent(AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION)
}
private fun updateIconTile() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 61c8e1bb..1037b0e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -27,6 +27,7 @@
import com.android.keyguard.LockIconViewController
import com.android.keyguard.dagger.KeyguardStatusViewComponent
import com.android.systemui.CoreStartable
+import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
@@ -113,7 +114,7 @@
fun bindIndicationArea() {
indicationAreaHandle?.dispose()
- if (!featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ if (!keyguardBottomAreaRefactor()) {
keyguardRootView.findViewById<View?>(R.id.keyguard_indication_area)?.let {
keyguardRootView.removeView(it)
}
@@ -125,7 +126,6 @@
keyguardIndicationAreaViewModel,
keyguardRootViewModel,
indicationController,
- featureFlags,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
index f20a666..1a8f625 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
@@ -22,9 +22,8 @@
import android.widget.TextView
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.res.R
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
@@ -54,7 +53,6 @@
viewModel: KeyguardIndicationAreaViewModel,
keyguardRootViewModel: KeyguardRootViewModel,
indicationController: KeyguardIndicationController,
- featureFlags: FeatureFlags,
): DisposableHandle {
val indicationArea: ViewGroup = view.requireViewById(R.id.keyguard_indication_area)
indicationController.setIndicationArea(indicationArea)
@@ -71,7 +69,7 @@
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
- if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ if (keyguardBottomAreaRefactor()) {
keyguardRootViewModel.alpha.collect { alpha ->
indicationArea.apply {
this.importantForAccessibility =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 378656c..1f74bb6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -33,6 +33,7 @@
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD
import com.android.keyguard.KeyguardClockSwitch.MISSING_CLOCK_ID
+import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
import com.android.systemui.common.shared.model.TintedIcon
@@ -111,7 +112,7 @@
}
}
- if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ if (keyguardBottomAreaRefactor()) {
launch { viewModel.alpha.collect { alpha -> view.alpha = alpha } }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index b797c4b..bdd9a6bf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -41,6 +41,7 @@
import androidx.core.view.isInvisible
import com.android.keyguard.ClockEventController
import com.android.keyguard.KeyguardClockSwitch
+import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.animation.view.LaunchableImageView
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.broadcast.BroadcastDispatcher
@@ -156,7 +157,7 @@
private val shortcutsBindings = mutableSetOf<KeyguardQuickAffordanceViewBinder.Binding>()
init {
- if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ if (keyguardBottomAreaRefactor()) {
keyguardRootViewModel.enablePreviewMode()
quickAffordancesCombinedViewModel.enablePreviewMode(
initiallySelectedSlotId =
@@ -199,7 +200,7 @@
setupKeyguardRootView(previewContext, rootView)
- if (!featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ if (!keyguardBottomAreaRefactor()) {
setUpBottomArea(rootView)
}
@@ -243,7 +244,7 @@
}
fun onSlotSelected(slotId: String) {
- if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ if (keyguardBottomAreaRefactor()) {
quickAffordancesCombinedViewModel.onPreviewSlotSelected(slotId = slotId)
} else {
bottomAreaViewModel.onPreviewSlotSelected(slotId = slotId)
@@ -254,7 +255,7 @@
isDestroyed = true
lockscreenSmartspaceController.disconnect()
disposables.forEach { it.dispose() }
- if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ if (keyguardBottomAreaRefactor()) {
shortcutsBindings.forEach { it.destroy() }
}
}
@@ -363,7 +364,7 @@
disposables.add(
PreviewKeyguardBlueprintViewBinder.bind(keyguardRootView, keyguardBlueprintViewModel) {
- if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ if (keyguardBottomAreaRefactor()) {
setupShortcuts(keyguardRootView)
}
setUpUdfps(previewContext, rootView)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
index 28e6a95..eb01d4f6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
@@ -25,10 +25,9 @@
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.RIGHT
import androidx.constraintlayout.widget.ConstraintSet.TOP
+import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.res.R
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
@@ -41,7 +40,6 @@
@Inject
constructor(
@Main private val resources: Resources,
- private val featureFlags: FeatureFlags,
private val keyguardQuickAffordancesCombinedViewModel:
KeyguardQuickAffordancesCombinedViewModel,
private val keyguardRootViewModel: KeyguardRootViewModel,
@@ -50,14 +48,14 @@
private val vibratorHelper: VibratorHelper,
) : BaseShortcutSection() {
override fun addViews(constraintLayout: ConstraintLayout) {
- if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ if (keyguardBottomAreaRefactor()) {
addLeftShortcut(constraintLayout)
addRightShortcut(constraintLayout)
}
}
override fun bindData(constraintLayout: ConstraintLayout) {
- if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ if (keyguardBottomAreaRefactor()) {
leftShortcutHandle =
KeyguardQuickAffordanceViewBinder.bind(
constraintLayout.requireViewById(R.id.start_button),
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt
index 9371d4e..20cb9b0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt
@@ -29,9 +29,8 @@
import androidx.constraintlayout.widget.ConstraintSet.TOP
import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.res.R
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.binder.KeyguardAmbientIndicationAreaViewBinder
import com.android.systemui.keyguard.ui.viewmodel.KeyguardAmbientIndicationViewModel
@@ -42,14 +41,13 @@
@Inject
constructor(
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
- private val featureFlags: FeatureFlags,
private val keyguardAmbientIndicationViewModel: KeyguardAmbientIndicationViewModel,
private val keyguardRootViewModel: KeyguardRootViewModel,
) : KeyguardSection() {
private var ambientIndicationAreaHandle: KeyguardAmbientIndicationAreaViewBinder.Binding? = null
override fun addViews(constraintLayout: ConstraintLayout) {
- if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ if (keyguardBottomAreaRefactor()) {
val view =
LayoutInflater.from(constraintLayout.context)
.inflate(R.layout.ambient_indication, constraintLayout, false)
@@ -59,7 +57,7 @@
}
override fun bindData(constraintLayout: ConstraintLayout) {
- if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ if (keyguardBottomAreaRefactor()) {
ambientIndicationAreaHandle =
KeyguardAmbientIndicationAreaViewBinder.bind(
constraintLayout,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSection.kt
index 755549b..ace970a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSection.kt
@@ -29,6 +29,7 @@
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.LockIconView
import com.android.keyguard.LockIconViewController
+import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.biometrics.AuthController
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
@@ -60,7 +61,7 @@
private val deviceEntryIconViewId = R.id.device_entry_icon_view
override fun addViews(constraintLayout: ConstraintLayout) {
- if (!featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON) &&
+ if (!keyguardBottomAreaRefactor() &&
!featureFlags.isEnabled(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS)
) {
return
@@ -74,7 +75,7 @@
if (featureFlags.isEnabled(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS)) {
DeviceEntryIconView(context, null).apply { id = deviceEntryIconViewId }
} else {
- // Flags.MIGRATE_LOCK_ICON
+ // keyguardBottomAreaRefactor()
LockIconView(context, null).apply { id = R.id.lock_icon_view }
}
constraintLayout.addView(view)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt
index 623eac0..8aef7c2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt
@@ -21,9 +21,8 @@
import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.res.R
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder
import com.android.systemui.keyguard.ui.view.KeyguardIndicationArea
@@ -40,27 +39,25 @@
private val keyguardIndicationAreaViewModel: KeyguardIndicationAreaViewModel,
private val keyguardRootViewModel: KeyguardRootViewModel,
private val indicationController: KeyguardIndicationController,
- private val featureFlags: FeatureFlags,
) : KeyguardSection() {
private val indicationAreaViewId = R.id.keyguard_indication_area
private var indicationAreaHandle: DisposableHandle? = null
override fun addViews(constraintLayout: ConstraintLayout) {
- if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ if (keyguardBottomAreaRefactor()) {
val view = KeyguardIndicationArea(context, null)
constraintLayout.addView(view)
}
}
override fun bindData(constraintLayout: ConstraintLayout) {
- if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ if (keyguardBottomAreaRefactor()) {
indicationAreaHandle =
KeyguardIndicationAreaBinder.bind(
constraintLayout,
keyguardIndicationAreaViewModel,
keyguardRootViewModel,
indicationController,
- featureFlags,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt
index 6fd13e0..9a33f08 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt
@@ -28,6 +28,7 @@
import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
import androidx.core.view.isVisible
+import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.res.R
import com.android.systemui.animation.view.LaunchableLinearLayout
import com.android.systemui.dagger.qualifiers.Main
@@ -45,7 +46,6 @@
@Inject
constructor(
@Main private val resources: Resources,
- private val featureFlags: FeatureFlags,
private val keyguardSettingsMenuViewModel: KeyguardSettingsMenuViewModel,
private val vibratorHelper: VibratorHelper,
private val activityStarter: ActivityStarter,
@@ -53,7 +53,7 @@
private var settingsPopupMenuHandle: DisposableHandle? = null
override fun addViews(constraintLayout: ConstraintLayout) {
- if (!featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ if (!keyguardBottomAreaRefactor()) {
return
}
val view =
@@ -68,7 +68,7 @@
}
override fun bindData(constraintLayout: ConstraintLayout) {
- if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ if (keyguardBottomAreaRefactor()) {
settingsPopupMenuHandle =
KeyguardSettingsViewBinder.bind(
constraintLayout.requireViewById<View>(R.id.keyguard_settings_button),
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
index a679120..0f6a966 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
@@ -24,6 +24,7 @@
import androidx.constraintlayout.widget.ConstraintSet.LEFT
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.RIGHT
+import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.res.R
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
@@ -40,7 +41,6 @@
@Inject
constructor(
@Main private val resources: Resources,
- private val featureFlags: FeatureFlags,
private val keyguardQuickAffordancesCombinedViewModel:
KeyguardQuickAffordancesCombinedViewModel,
private val keyguardRootViewModel: KeyguardRootViewModel,
@@ -49,14 +49,14 @@
private val vibratorHelper: VibratorHelper,
) : BaseShortcutSection() {
override fun addViews(constraintLayout: ConstraintLayout) {
- if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ if (keyguardBottomAreaRefactor()) {
addLeftShortcut(constraintLayout)
addRightShortcut(constraintLayout)
}
}
override fun bindData(constraintLayout: ConstraintLayout) {
- if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ if (keyguardBottomAreaRefactor()) {
leftShortcutHandle =
KeyguardQuickAffordanceViewBinder.bind(
constraintLayout.requireViewById(R.id.start_button),
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
index 980cc1b..2327c02 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
@@ -16,9 +16,8 @@
package com.android.systemui.keyguard.ui.viewmodel
+import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.doze.util.BurnInHelperWrapper
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import javax.inject.Inject
@@ -36,7 +35,6 @@
keyguardBottomAreaViewModel: KeyguardBottomAreaViewModel,
private val burnInHelperWrapper: BurnInHelperWrapper,
private val shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel,
- private val featureFlags: FeatureFlags,
) {
/** Notifies when a new configuration is set */
@@ -47,7 +45,7 @@
/** An observable for whether the indication area should be padded. */
val isIndicationAreaPadded: Flow<Boolean> =
- if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ if (keyguardBottomAreaRefactor()) {
combine(shortcutsCombinedViewModel.startButton, shortcutsCombinedViewModel.endButton) {
startButtonModel,
endButtonModel ->
@@ -64,7 +62,7 @@
}
/** An observable for the x-offset by which the indication area should be translated. */
val indicationAreaTranslationX: Flow<Float> =
- if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ if (keyguardBottomAreaRefactor()) {
keyguardInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged()
} else {
bottomAreaInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index a985236..5e3a166 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -1383,7 +1383,17 @@
args.putInt(
AssistManager.INVOCATION_TYPE_KEY,
AssistManager.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS);
- mAssistManagerLazy.get().startAssist(args);
+ // If Launcher has requested to override long press home, add a delay for the ripple.
+ // TODO(b/304146255): Remove this delay once we can exclude 3-button nav from screenshot.
+ boolean delayAssistInvocation = mAssistManagerLazy.get().shouldOverrideAssist(
+ AssistManager.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS);
+ // In practice, I think v should always be a KeyButtonView, but just being safe.
+ if (delayAssistInvocation && v instanceof KeyButtonView) {
+ ((KeyButtonView) v).setOnRippleInvisibleRunnable(
+ () -> mAssistManagerLazy.get().startAssist(args));
+ } else {
+ mAssistManagerLazy.get().startAssist(args);
+ }
mCentralSurfacesOptionalLazy.get().ifPresent(CentralSurfaces::awakenDreams);
mView.abortCurrentGesture();
return true;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
index 2928cce..79aedff 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
@@ -21,6 +21,7 @@
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;
import static com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler.DEBUG_MISSING_GESTURE_TAG;
import static com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen;
+import static com.android.wm.shell.Flags.enableTaskbarNavbarUnification;
import android.content.Context;
import android.content.pm.ActivityInfo;
@@ -49,8 +50,6 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.model.SysUiState;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.settings.DisplayTracker;
@@ -83,7 +82,6 @@
private final Context mContext;
private final Handler mHandler;
private final NavigationBarComponent.Factory mNavigationBarComponentFactory;
- private FeatureFlags mFeatureFlags;
private final SecureSettings mSecureSettings;
private final DisplayTracker mDisplayTracker;
private final DisplayManager mDisplayManager;
@@ -118,13 +116,11 @@
TaskStackChangeListeners taskStackChangeListeners,
Optional<Pip> pipOptional,
Optional<BackAnimation> backAnimation,
- FeatureFlags featureFlags,
SecureSettings secureSettings,
DisplayTracker displayTracker) {
mContext = context;
mHandler = mainHandler;
mNavigationBarComponentFactory = navigationBarComponentFactory;
- mFeatureFlags = featureFlags;
mSecureSettings = secureSettings;
mDisplayTracker = displayTracker;
mDisplayManager = mContext.getSystemService(DisplayManager.class);
@@ -248,8 +244,8 @@
/** @return {@code true} if taskbar is enabled, false otherwise */
private boolean initializeTaskbarIfNecessary() {
// Enable for large screens or (phone AND flag is set); assuming phone = !mIsLargeScreen
- boolean taskbarEnabled = (mIsLargeScreen || mFeatureFlags.isEnabled(
- Flags.HIDE_NAVBAR_WINDOW)) && shouldCreateNavBarAndTaskBar(mContext.getDisplayId());
+ boolean taskbarEnabled = (mIsLargeScreen || enableTaskbarNavbarUnification())
+ && shouldCreateNavBarAndTaskBar(mContext.getDisplayId());
if (taskbarEnabled) {
Trace.beginSection("NavigationBarController#initializeTaskbarIfNecessary");
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index bc4f7f25..258208d 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -62,7 +62,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.Utils;
import com.android.systemui.Gefingerpoken;
-import com.android.systemui.res.R;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
import com.android.systemui.navigationbar.buttons.ContextualButton;
@@ -73,6 +72,7 @@
import com.android.systemui.navigationbar.buttons.RotationContextButton;
import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
import com.android.systemui.recents.Recents;
+import com.android.systemui.res.R;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.shared.rotation.FloatingRotationButton;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
index dcf1a8e..6ec46f6 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
@@ -58,8 +58,9 @@
import com.android.internal.logging.UiEventLoggerImpl;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Dependency;
-import com.android.systemui.res.R;
+import com.android.systemui.assist.AssistManager;
import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.res.R;
import com.android.systemui.shared.system.QuickStepContract;
public class KeyButtonView extends ImageView implements ButtonInterface {
@@ -439,11 +440,22 @@
if (mCode != KeyEvent.KEYCODE_UNKNOWN) {
sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED);
}
+ // When aborting long-press home and Launcher has requested to override it, fade out the
+ // ripple more quickly.
+ if (mCode == KeyEvent.KEYCODE_HOME && Dependency.get(AssistManager.class)
+ .shouldOverrideAssist(AssistManager.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS)) {
+ mRipple.speedUpNextFade();
+ }
setPressed(false);
mRipple.abortDelayedRipple();
mGestureAborted = true;
}
+ /** Run when the ripple for this button is next invisible. Only used once. */
+ public void setOnRippleInvisibleRunnable(Runnable onRippleInvisibleRunnable) {
+ mRipple.setOnInvisibleRunnable(onRippleInvisibleRunnable);
+ }
+
@Override
public void setDarkIntensity(float darkIntensity) {
mDarkIntensity = darkIntensity;
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index 093d098..d9a8080 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -325,7 +325,13 @@
} else {
// TODO(b/278729185): Replace fire and forget service with a bounded service.
val intent = NoteTaskControllerUpdateService.createIntent(context)
- context.startServiceAsUser(intent, user)
+ try {
+ // If the user is stopped before 'startServiceAsUser' kicks-in, a
+ // 'SecurityException' will be thrown.
+ context.startServiceAsUser(intent, user)
+ } catch (e: SecurityException) {
+ debugLog(error = e) { "Unable to start 'NoteTaskControllerUpdateService'." }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
index 4dc1c82..2074a14 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
@@ -24,7 +24,6 @@
import com.android.systemui.log.dagger.QSTilesLogBuffers
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.pipeline.shared.TileSpec
-import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
import com.android.systemui.statusbar.StatusBarState
@@ -34,7 +33,7 @@
class QSTileLogger
@Inject
constructor(
- @QSTilesLogBuffers logBuffers: Map<TileSpec, LogBuffer>,
+ @QSTilesLogBuffers logBuffers: Map<String, LogBuffer>,
private val factory: LogBufferFactory,
private val mStatusBarStateController: StatusBarStateController,
) {
@@ -163,22 +162,15 @@
private fun TileSpec.getLogBuffer(): LogBuffer =
synchronized(logBufferCache) {
- logBufferCache.getOrPut(this) {
+ logBufferCache.getOrPut(this.spec) {
factory.create(
- "QSTileLog_${this.getLogTag()}",
+ this.getLogTag(),
BUFFER_MAX_SIZE /* maxSize */,
false /* systrace */
)
}
}
- private fun DataUpdateTrigger.toLogString(): String =
- when (this) {
- is DataUpdateTrigger.ForceUpdate -> "force"
- is DataUpdateTrigger.InitialRequest -> "init"
- is DataUpdateTrigger.UserInput<*> -> input.action.toLogString()
- }
-
private fun QSTileUserAction.toLogString(): String =
when (this) {
is QSTileUserAction.Click -> "click"
@@ -198,7 +190,7 @@
"]"
private companion object {
- const val TAG_FORMAT_PREFIX = "QSLog"
+ const val TAG_FORMAT_PREFIX = "QSLog_tile_"
const val DATA_MAX_LENGTH = 50
const val BUFFER_MAX_SIZE = 25
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt
index 0bee48f..12a083e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt
@@ -39,7 +39,6 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
-import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -47,6 +46,8 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.cancellable
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
@@ -57,6 +58,7 @@
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
/**
* Provides a hassle-free way to implement new tiles according to current System UI architecture
@@ -83,10 +85,8 @@
private val users: MutableStateFlow<UserHandle> =
MutableStateFlow(userRepository.getSelectedUserInfo().userHandle)
- private val userInputs: MutableSharedFlow<QSTileUserAction> =
- MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
- private val forceUpdates: MutableSharedFlow<Unit> =
- MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
+ private val userInputs: MutableSharedFlow<QSTileUserAction> = MutableSharedFlow()
+ private val forceUpdates: MutableSharedFlow<Unit> = MutableSharedFlow()
private val spec
get() = config.tileSpec
@@ -130,7 +130,7 @@
tileData.replayCache.isNotEmpty(),
state.replayCache.isNotEmpty()
)
- userInputs.tryEmit(userAction)
+ tileScope.launch { userInputs.emit(userAction) }
}
override fun destroy() {
@@ -151,11 +151,16 @@
emit(DataUpdateTrigger.InitialRequest)
qsTileLogger.logInitialRequest(spec)
}
+ .shareIn(tileScope, SharingStarted.WhileSubscribed())
tileDataInteractor()
.tileData(user, updateTriggers)
+ // combine makes sure updateTriggers is always listened even if
+ // tileDataInteractor#tileData doesn't flatMapLatest on it
+ .combine(updateTriggers) { data, _ -> data }
.cancellable()
.flowOn(backgroundDispatcher)
}
+ .distinctUntilChanged()
.shareIn(
tileScope,
SharingStarted.WhileSubscribed(),
@@ -171,8 +176,8 @@
*
* Subscribing to the result flow twice will result in doubling all actions, logs and analytics.
*/
- private fun userInputFlow(user: UserHandle): Flow<DataUpdateTrigger> {
- return userInputs
+ private fun userInputFlow(user: UserHandle): Flow<DataUpdateTrigger> =
+ userInputs
.filterFalseActions()
.filterByPolicy(user)
.throttle(CLICK_THROTTLE_DURATION, systemClock)
@@ -187,7 +192,6 @@
}
.onEach { userActionInteractor().handleInput(it.input) }
.flowOn(backgroundDispatcher)
- }
private fun Flow<QSTileUserAction>.filterByPolicy(user: UserHandle): Flow<QSTileUserAction> =
config.policy.let { policy ->
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt
index c4d7dfb..18a4e2d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt
@@ -36,9 +36,9 @@
*/
sealed interface QSTileUIConfig {
- val tileIconRes: Int
+ val iconRes: Int
@DrawableRes get
- val tileLabelRes: Int
+ val labelRes: Int
@StringRes get
/**
@@ -46,16 +46,16 @@
* of [Resource]. Returns [Resources.ID_NULL] for each field.
*/
data object Empty : QSTileUIConfig {
- override val tileIconRes: Int
+ override val iconRes: Int
get() = Resources.ID_NULL
- override val tileLabelRes: Int
+ override val labelRes: Int
get() = Resources.ID_NULL
}
/** Config containing actual icon and label resources. */
data class Resource(
- @StringRes override val tileIconRes: Int,
- @StringRes override val tileLabelRes: Int,
+ @DrawableRes override val iconRes: Int,
+ @StringRes override val labelRes: Int,
) : QSTileUIConfig
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
index dc5cccc..30b87cc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.tiles.viewmodel
+import android.content.Context
import android.service.quicksettings.Tile
import com.android.systemui.common.shared.model.Icon
@@ -41,11 +42,19 @@
companion object {
+ fun build(
+ context: Context,
+ config: QSTileUIConfig,
+ build: Builder.() -> Unit
+ ): QSTileState =
+ build(
+ { Icon.Resource(config.iconRes, null) },
+ context.getString(config.labelRes),
+ build,
+ )
+
fun build(icon: () -> Icon, label: CharSequence, build: Builder.() -> Unit): QSTileState =
Builder(icon, label).apply(build).build()
-
- fun build(icon: Icon, label: CharSequence, build: Builder.() -> Unit): QSTileState =
- build({ icon }, label, build)
}
enum class ActivationState(val legacyState: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
index efa6da7..771d07c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
@@ -192,7 +192,7 @@
with(qsTileViewModel.config.uiConfig) {
when (this) {
is QSTileUIConfig.Empty -> qsTileViewModel.currentState?.label ?: ""
- is QSTileUIConfig.Resource -> context.getString(tileLabelRes)
+ is QSTileUIConfig.Resource -> context.getString(labelRes)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
index 018f31b..e40d2b7 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
@@ -17,6 +17,7 @@
package com.android.systemui.scene.shared.flag
import androidx.annotation.VisibleForTesting
+import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.Flags.sceneContainer
import com.android.systemui.compose.ComposeFacade
@@ -57,8 +58,6 @@
@VisibleForTesting
val classicFlagTokens: List<Flag<Boolean>> =
listOf(
- Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA,
- Flags.MIGRATE_LOCK_ICON,
Flags.MIGRATE_NSSL,
Flags.MIGRATE_KEYGUARD_STATUS_VIEW,
Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW,
@@ -72,6 +71,10 @@
flagName = AConfigFlags.FLAG_SCENE_CONTAINER,
flagValue = sceneContainer(),
),
+ AconfigFlagMustBeEnabled(
+ flagName = AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
+ flagValue = keyguardBottomAreaRefactor(),
+ ),
) +
classicFlagTokens.map { flagToken -> FlagMustBeEnabled(flagToken) } +
listOf(ComposeMustBeAvailable(), CompileTimeFlagMustBeEnabled())
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 85a4a7e..823caa0 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -24,6 +24,7 @@
import static com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE;
import static com.android.keyguard.KeyguardClockSwitch.LARGE;
import static com.android.keyguard.KeyguardClockSwitch.SMALL;
+import static com.android.systemui.Flags.keyguardBottomAreaRefactor;
import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
import static com.android.systemui.classifier.Classifier.GENERIC;
import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
@@ -1070,7 +1071,7 @@
mQsController.init();
mShadeHeadsUpTracker.addTrackingHeadsUpListener(
mNotificationStackScrollLayoutController::setTrackingHeadsUp);
- if (!mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ if (!keyguardBottomAreaRefactor()) {
setKeyguardBottomArea(mView.findViewById(R.id.keyguard_bottom_area));
}
@@ -1409,7 +1410,7 @@
updateViewControllers(userAvatarView, keyguardUserSwitcherView);
- if (!mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ if (!keyguardBottomAreaRefactor()) {
// Update keyguard bottom area
int index = mView.indexOfChild(mKeyguardBottomArea);
mView.removeView(mKeyguardBottomArea);
@@ -1443,7 +1444,7 @@
mBarState);
}
- if (!mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ if (!keyguardBottomAreaRefactor()) {
setKeyguardBottomAreaVisibility(mBarState, false);
}
@@ -1456,7 +1457,7 @@
}
private void initBottomArea() {
- if (!mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ if (!keyguardBottomAreaRefactor()) {
mKeyguardBottomArea.init(
mKeyguardBottomAreaViewModel,
mFalsingManager,
@@ -1643,7 +1644,7 @@
mKeyguardStatusViewController.setLockscreenClockY(
mClockPositionAlgorithm.getExpandedPreferredClockY());
}
- if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ if (keyguardBottomAreaRefactor()) {
mKeyguardInteractor.setClockPosition(
mClockPositionResult.clockX, mClockPositionResult.clockY);
} else {
@@ -2710,7 +2711,7 @@
float alpha = Math.min(expansionAlpha, 1 - mQsController.computeExpansionFraction());
alpha *= mBottomAreaShadeAlpha;
- if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ if (keyguardBottomAreaRefactor()) {
mKeyguardInteractor.setAlpha(alpha);
} else {
mKeyguardBottomAreaInteractor.setAlpha(alpha);
@@ -2936,7 +2937,7 @@
}
private void updateDozingVisibilities(boolean animate) {
- if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ if (keyguardBottomAreaRefactor()) {
mKeyguardInteractor.setAnimateDozingTransitions(animate);
} else {
mKeyguardBottomAreaInteractor.setAnimateDozingTransitions(animate);
@@ -3144,7 +3145,7 @@
mDozing = dozing;
// TODO (b/) make listeners for this
mNotificationStackScrollLayoutController.setDozing(mDozing, animate);
- if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ if (keyguardBottomAreaRefactor()) {
mKeyguardInteractor.setAnimateDozingTransitions(animate);
} else {
mKeyguardBottomAreaInteractor.setAnimateDozingTransitions(animate);
@@ -4441,7 +4442,7 @@
goingToFullShade,
mBarState);
- if (!mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ if (!keyguardBottomAreaRefactor()) {
setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade);
}
@@ -4698,7 +4699,7 @@
mKeyguardStatusViewController.setAlpha(alpha);
stackScroller.setAlpha(alpha);
- if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ if (keyguardBottomAreaRefactor()) {
mKeyguardInteractor.setAlpha(alpha);
} else {
mKeyguardBottomAreaInteractor.setAlpha(alpha);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 121aa42..e9779cd 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -52,6 +52,7 @@
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.dump.DumpsysTableLogger;
import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -59,6 +60,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.res.R;
import com.android.systemui.scene.ui.view.WindowRootViewComponent;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
@@ -150,6 +152,7 @@
ConfigurationController configurationController,
KeyguardViewMediator keyguardViewMediator,
KeyguardBypassController keyguardBypassController,
+ @Main Executor mainExecutor,
@Background Executor backgroundExecutor,
SysuiColorExtractor colorExtractor,
DumpManager dumpManager,
@@ -158,7 +161,8 @@
AuthController authController,
Lazy<ShadeInteractor> shadeInteractorLazy,
ShadeWindowLogger logger,
- Lazy<SelectedUserInteractor> userInteractor) {
+ Lazy<SelectedUserInteractor> userInteractor,
+ UserTracker userTracker) {
mContext = context;
mWindowRootViewComponentFactory = windowRootViewComponentFactory;
mWindowManager = windowManager;
@@ -184,7 +188,9 @@
.addCallback(mStateListener,
SysuiStatusBarStateController.RANK_STATUS_BAR_WINDOW_CONTROLLER);
configurationController.addCallback(this);
-
+ if (android.multiuser.Flags.useAllCpusDuringUserSwitch()) {
+ userTracker.addCallback(mUserTrackerCallback, mainExecutor);
+ }
float desiredPreferredRefreshRate = context.getResources()
.getInteger(R.integer.config_keyguardRefreshRate);
float actualPreferredRefreshRate = -1;
@@ -572,6 +578,7 @@
state.qsExpanded,
state.headsUpNotificationShowing,
state.lightRevealScrimOpaque,
+ state.isSwitchingUsers,
state.forceWindowCollapsed,
state.forceDozeBrightness,
state.forceUserActivity,
@@ -624,7 +631,8 @@
}
private void applyHasTopUi(NotificationShadeWindowState state) {
- mHasTopUiChanged = !state.componentsForcingTopUi.isEmpty() || isExpanded(state);
+ mHasTopUiChanged = !state.componentsForcingTopUi.isEmpty() || isExpanded(state)
+ || state.isSwitchingUsers;
}
private void applyNotTouchable(NotificationShadeWindowState state) {
@@ -954,4 +962,24 @@
setDreaming(isDreaming);
}
};
+
+ private final UserTracker.Callback mUserTrackerCallback = new UserTracker.Callback() {
+ @Override
+ public void onBeforeUserSwitching(int newUser) {
+ setIsSwitchingUsers(true);
+ }
+
+ @Override
+ public void onUserChanged(int newUser, Context userContext) {
+ setIsSwitchingUsers(false);
+ }
+
+ private void setIsSwitchingUsers(boolean isSwitchingUsers) {
+ if (mCurrentState.isSwitchingUsers == isSwitchingUsers) {
+ return;
+ }
+ mCurrentState.isSwitchingUsers = isSwitchingUsers;
+ apply(mCurrentState);
+ }
+ };
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
index fbe164a..0b20170 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
@@ -40,6 +40,7 @@
@JvmField var qsExpanded: Boolean = false,
@JvmField var headsUpNotificationShowing: Boolean = false,
@JvmField var lightRevealScrimOpaque: Boolean = false,
+ @JvmField var isSwitchingUsers: Boolean = false,
@JvmField var forceWindowCollapsed: Boolean = false,
@JvmField var forceDozeBrightness: Boolean = false,
// TODO: forceUserActivity seems to be unused, delete?
@@ -78,6 +79,7 @@
qsExpanded.toString(),
headsUpNotificationShowing.toString(),
lightRevealScrimOpaque.toString(),
+ isSwitchingUsers.toString(),
forceWindowCollapsed.toString(),
forceDozeBrightness.toString(),
forceUserActivity.toString(),
@@ -117,6 +119,7 @@
qsExpanded: Boolean,
headsUpShowing: Boolean,
lightRevealScrimOpaque: Boolean,
+ isSwitchingUsers: Boolean,
forceCollapsed: Boolean,
forceDozeBrightness: Boolean,
forceUserActivity: Boolean,
@@ -145,6 +148,7 @@
this.qsExpanded = qsExpanded
this.headsUpNotificationShowing = headsUpShowing
this.lightRevealScrimOpaque = lightRevealScrimOpaque
+ this.isSwitchingUsers = isSwitchingUsers
this.forceWindowCollapsed = forceCollapsed
this.forceDozeBrightness = forceDozeBrightness
this.forceUserActivity = forceUserActivity
@@ -191,6 +195,7 @@
"qsExpanded",
"headsUpShowing",
"lightRevealScrimOpaque",
+ "isSwitchingUsers",
"forceCollapsed",
"forceDozeBrightness",
"forceUserActivity",
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
index 966ff35..ec90a8d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
@@ -90,10 +90,10 @@
} else {
setWillBeGone(true);
}
- setContentVisible(visible, true /* animate */, null /* runAfter */);
+ setContentVisible(visible, true /* animate */, null /* onAnimationEnded */);
} else {
setVisibility(visible ? VISIBLE : GONE);
- setContentVisible(visible, false /* animate */, null /* runAfter */);
+ setContentVisible(visible, false /* animate */, null /* onAnimationEnded */);
setWillBeGone(false);
notifyHeightChanged(false /* needsAnimation */);
}
@@ -108,7 +108,7 @@
* Change content visibility to {@code visible}, animated.
*/
public void setContentVisibleAnimated(boolean visible) {
- setContentVisible(visible, true /* animate */, null /* runAfter */);
+ setContentVisible(visible, true /* animate */, null /* onAnimationEnded */);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsLiveDataRefactor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsLiveDataRefactor.kt
new file mode 100644
index 0000000..44387c2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsLiveDataRefactor.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the notifications live data store refactor flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object NotificationsLiveDataStoreRefactor {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_NOTIFICATIONS_LIVE_DATA_STORE_REFACTOR
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.notificationsLiveDataStoreRefactor()
+
+ /**
+ * 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 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)
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java
index 5c1149b..580431a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java
@@ -53,7 +53,7 @@
mContents = requireViewById(R.id.content);
bindContents();
super.onFinishInflate();
- setVisible(true /* nowVisible */, false /* animate */);
+ setVisible(true /* visible */, false /* animate */);
}
private void bindContents() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
index d61ca69..1d4f2cb 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
@@ -21,7 +21,6 @@
import static com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR;
import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
-import static com.android.systemui.flags.Flags.MIGRATE_LOCK_ICON;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
@@ -39,6 +38,7 @@
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.AuthRippleController;
@@ -148,9 +148,10 @@
when(mStatusBarStateController.isDozing()).thenReturn(false);
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
+ mSetFlagsRule.disableFlags(Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR);
+
mFeatureFlags = new FakeFeatureFlags();
mFeatureFlags.set(FACE_AUTH_REFACTOR, false);
- mFeatureFlags.set(MIGRATE_LOCK_ICON, false);
mFeatureFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false);
mFeatureFlags.set(LOCKSCREEN_ENABLE_LANDSCAPE, false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
new file mode 100644
index 0000000..14ec4d4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.data.db
+
+import android.content.ComponentName
+import androidx.room.Room
+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.lifecycle.InstantTaskExecutorRule
+import com.google.common.truth.Truth.assertThat
+import java.io.IOException
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalWidgetDaoTest : SysuiTestCase() {
+ @JvmField @Rule val instantTaskExecutor = InstantTaskExecutorRule()
+
+ private lateinit var db: CommunalDatabase
+ private lateinit var communalWidgetDao: CommunalWidgetDao
+
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ @Before
+ @Throws(IOException::class)
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ db =
+ Room.inMemoryDatabaseBuilder(context, CommunalDatabase::class.java)
+ .allowMainThreadQueries()
+ .build()
+ communalWidgetDao = db.communalWidgetDao()
+ }
+
+ @After
+ @Throws(IOException::class)
+ fun teardown() {
+ db.close()
+ }
+
+ @Test
+ fun addWidget_readValueInDb() =
+ testScope.runTest {
+ val (widgetId, provider, priority) = widgetInfo1
+ communalWidgetDao.addWidget(
+ widgetId = widgetId,
+ provider = provider,
+ priority = priority,
+ )
+ val entry = communalWidgetDao.getWidgetByIdNow(id = 1)
+ assertThat(entry).isEqualTo(communalWidgetItemEntry1)
+ }
+
+ @Test
+ fun addWidget_emitsActiveWidgetsInDb(): Unit =
+ testScope.runTest {
+ val widgetsToAdd = listOf(widgetInfo1, widgetInfo2)
+ val widgets = collectLastValue(communalWidgetDao.getWidgets())
+ widgetsToAdd.forEach {
+ val (widgetId, provider, priority) = it
+ communalWidgetDao.addWidget(
+ widgetId = widgetId,
+ provider = provider,
+ priority = priority,
+ )
+ }
+ assertThat(widgets())
+ .containsExactly(
+ communalItemRankEntry1,
+ communalWidgetItemEntry1,
+ communalItemRankEntry2,
+ communalWidgetItemEntry2
+ )
+ }
+
+ @Test
+ fun deleteWidget_emitsActiveWidgetsInDb() =
+ testScope.runTest {
+ val widgetsToAdd = listOf(widgetInfo1, widgetInfo2)
+ val widgets = collectLastValue(communalWidgetDao.getWidgets())
+
+ widgetsToAdd.forEach {
+ val (widgetId, provider, priority) = it
+ communalWidgetDao.addWidget(
+ widgetId = widgetId,
+ provider = provider,
+ priority = priority,
+ )
+ }
+ assertThat(widgets())
+ .containsExactly(
+ communalItemRankEntry1,
+ communalWidgetItemEntry1,
+ communalItemRankEntry2,
+ communalWidgetItemEntry2
+ )
+
+ communalWidgetDao.deleteWidgetById(communalWidgetItemEntry1.widgetId)
+ assertThat(widgets()).containsExactly(communalItemRankEntry2, communalWidgetItemEntry2)
+ }
+
+ data class FakeWidgetMetadata(
+ val widgetId: Int,
+ val provider: ComponentName,
+ val priority: Int
+ )
+
+ companion object {
+ val widgetInfo1 =
+ FakeWidgetMetadata(
+ widgetId = 1,
+ provider = ComponentName("pk_name", "cls_name_1"),
+ priority = 1
+ )
+ val widgetInfo2 =
+ FakeWidgetMetadata(
+ widgetId = 2,
+ provider = ComponentName("pk_name", "cls_name_2"),
+ priority = 2
+ )
+ val communalItemRankEntry1 = CommunalItemRank(uid = 1L, rank = widgetInfo1.priority)
+ val communalItemRankEntry2 = CommunalItemRank(uid = 2L, rank = widgetInfo2.priority)
+ val communalWidgetItemEntry1 =
+ CommunalWidgetItem(
+ uid = 1L,
+ widgetId = widgetInfo1.widgetId,
+ componentName = widgetInfo1.provider.flattenToString(),
+ itemId = communalItemRankEntry1.uid,
+ )
+ val communalWidgetItemEntry2 =
+ CommunalWidgetItem(
+ uid = 2L,
+ widgetId = widgetInfo2.widgetId,
+ componentName = widgetInfo2.provider.flattenToString(),
+ itemId = communalItemRankEntry2.uid,
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index fcb191b..ca8316d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -1,9 +1,26 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.systemui.communal.data.repository
import android.appwidget.AppWidgetHost
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProviderInfo
import android.content.BroadcastReceiver
+import android.content.ComponentName
import android.content.pm.PackageManager
import android.os.UserHandle
import android.os.UserManager
@@ -11,8 +28,10 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.communal.data.model.CommunalWidgetMetadata
-import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.data.db.CommunalItemRank
+import com.android.systemui.communal.data.db.CommunalWidgetDao
+import com.android.systemui.communal.data.db.CommunalWidgetItem
+import com.android.systemui.communal.shared.CommunalWidgetHost
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.FeatureFlagsClassic
@@ -28,6 +47,7 @@
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
@@ -66,9 +86,9 @@
@Mock private lateinit var providerInfoA: AppWidgetProviderInfo
- @Mock private lateinit var providerInfoB: AppWidgetProviderInfo
+ @Mock private lateinit var communalWidgetHost: CommunalWidgetHost
- @Mock private lateinit var providerInfoC: AppWidgetProviderInfo
+ @Mock private lateinit var communalWidgetDao: CommunalWidgetDao
private lateinit var communalRepository: FakeCommunalRepository
@@ -103,6 +123,92 @@
}
@Test
+ fun neverQueryDbForWidgets_whenFeatureIsDisabled() =
+ testScope.runTest {
+ communalEnabled(false)
+ val repository = initCommunalWidgetRepository()
+ collectLastValue(repository.communalWidgets)()
+ runCurrent()
+
+ verify(communalWidgetDao, Mockito.never()).getWidgets()
+ }
+
+ @Test
+ fun neverQueryDbForWidgets_whenFeatureEnabled_andUserLocked() =
+ testScope.runTest {
+ userUnlocked(false)
+ val repository = initCommunalWidgetRepository()
+ collectLastValue(repository.communalWidgets)()
+ runCurrent()
+
+ verify(communalWidgetDao, Mockito.never()).getWidgets()
+ }
+
+ @Test
+ fun communalWidgets_whenUserUnlocked_queryWidgetsFromDb() =
+ testScope.runTest {
+ userUnlocked(false)
+ val repository = initCommunalWidgetRepository()
+ val communalWidgets = collectLastValue(repository.communalWidgets)
+ communalWidgets()
+ runCurrent()
+ val communalItemRankEntry = CommunalItemRank(uid = 1L, rank = 1)
+ val communalWidgetItemEntry = CommunalWidgetItem(uid = 1L, 1, "pk_name/cls_name", 1L)
+ whenever(communalWidgetDao.getWidgets())
+ .thenReturn(flowOf(mapOf(communalItemRankEntry to communalWidgetItemEntry)))
+ whenever(appWidgetManager.getAppWidgetInfo(anyInt())).thenReturn(providerInfoA)
+
+ userUnlocked(true)
+ installedProviders(listOf(stopwatchProviderInfo))
+ broadcastReceiverUpdate()
+ runCurrent()
+
+ verify(communalWidgetDao).getWidgets()
+ assertThat(communalWidgets())
+ .containsExactly(
+ CommunalWidgetContentModel(
+ appWidgetId = communalWidgetItemEntry.widgetId,
+ providerInfo = providerInfoA,
+ priority = communalItemRankEntry.rank,
+ )
+ )
+ }
+
+ @Test
+ fun addWidget_allocateId_bindWidget_andAddToDb() =
+ testScope.runTest {
+ userUnlocked(true)
+ val repository = initCommunalWidgetRepository()
+ runCurrent()
+
+ val provider = ComponentName("pkg_name", "cls_name")
+ val id = 1
+ val priority = 1
+ whenever(communalWidgetHost.allocateIdAndBindWidget(any<ComponentName>()))
+ .thenReturn(id)
+ repository.addWidget(provider, priority)
+ runCurrent()
+
+ verify(communalWidgetHost).allocateIdAndBindWidget(provider)
+ verify(communalWidgetDao).addWidget(id, provider, priority)
+ }
+
+ @Test
+ fun deleteWidget_removeWidgetId_andDeleteFromDb() =
+ testScope.runTest {
+ userUnlocked(true)
+ val repository = initCommunalWidgetRepository()
+ runCurrent()
+
+ val id = 1
+ repository.deleteWidget(id)
+ runCurrent()
+
+ verify(communalWidgetDao).deleteWidgetById(id)
+ verify(appWidgetHost).deleteAppWidgetId(id)
+ }
+
+ @Test
fun broadcastReceiver_communalDisabled_doNotRegisterUserUnlockedBroadcastReceiver() =
testScope.runTest {
communalEnabled(false)
@@ -183,34 +289,6 @@
}
@Test
- fun appWidgetId_userLockedAgainAfterProviderInfoAvailable_deleteAppWidgetId() =
- testScope.runTest {
- whenever(appWidgetHost.allocateAppWidgetId()).thenReturn(123456)
- userUnlocked(false)
- val repository = initCommunalWidgetRepository()
- val lastStopwatchProviderInfo = collectLastValue(repository.stopwatchAppWidgetInfo)
- assertThat(lastStopwatchProviderInfo()).isNull()
-
- // User unlocks
- userUnlocked(true)
- installedProviders(listOf(stopwatchProviderInfo))
- broadcastReceiverUpdate()
-
- // Verify app widget id allocated
- assertThat(lastStopwatchProviderInfo()?.appWidgetId).isEqualTo(123456)
- verify(appWidgetHost).allocateAppWidgetId()
- verify(appWidgetHost, Mockito.never()).deleteAppWidgetId(anyInt())
-
- // User locked again
- userUnlocked(false)
- broadcastReceiverUpdate()
-
- // Verify app widget id deleted
- assertThat(lastStopwatchProviderInfo()).isNull()
- verify(appWidgetHost).deleteAppWidgetId(123456)
- }
-
- @Test
fun appWidgetHost_userUnlocked_startListening() =
testScope.runTest {
userUnlocked(false)
@@ -246,95 +324,16 @@
verify(appWidgetHost).stopListening()
}
- @Test
- fun getCommunalWidgetAllowList_onInit() {
- testScope.runTest {
- val repository = initCommunalWidgetRepository()
- val communalWidgetAllowlist = repository.communalWidgetAllowlist
- assertThat(
- listOf(
- CommunalWidgetMetadata(
- componentName = fakeAllowlist[0],
- priority = 3,
- sizes = listOf(CommunalContentSize.HALF),
- ),
- CommunalWidgetMetadata(
- componentName = fakeAllowlist[1],
- priority = 2,
- sizes = listOf(CommunalContentSize.HALF),
- ),
- CommunalWidgetMetadata(
- componentName = fakeAllowlist[2],
- priority = 1,
- sizes = listOf(CommunalContentSize.HALF),
- ),
- )
- )
- .containsExactly(*communalWidgetAllowlist.toTypedArray())
- }
- }
-
- // This behavior is temporary before the local database is set up.
- @Test
- fun communalWidgets_withPreviouslyBoundWidgets_removeEachBinding() =
- testScope.runTest {
- whenever(appWidgetHost.allocateAppWidgetId()).thenReturn(1, 2, 3)
- setAppWidgetIds(listOf(1, 2, 3))
- whenever(appWidgetManager.getAppWidgetInfo(anyInt())).thenReturn(providerInfoA)
- userUnlocked(true)
-
- val repository = initCommunalWidgetRepository()
-
- collectLastValue(repository.communalWidgets)()
-
- verify(appWidgetHost).deleteAppWidgetId(1)
- verify(appWidgetHost).deleteAppWidgetId(2)
- verify(appWidgetHost).deleteAppWidgetId(3)
- }
-
- @Test
- fun communalWidgets_allowlistNotEmpty_bindEachWidgetFromTheAllowlist() =
- testScope.runTest {
- whenever(appWidgetHost.allocateAppWidgetId()).thenReturn(0, 1, 2)
- userUnlocked(true)
-
- whenever(appWidgetManager.getAppWidgetInfo(0)).thenReturn(providerInfoA)
- whenever(appWidgetManager.getAppWidgetInfo(1)).thenReturn(providerInfoB)
- whenever(appWidgetManager.getAppWidgetInfo(2)).thenReturn(providerInfoC)
-
- val repository = initCommunalWidgetRepository()
-
- val inventory by collectLastValue(repository.communalWidgets)
-
- assertThat(
- listOf(
- CommunalWidgetContentModel(
- appWidgetId = 0,
- providerInfo = providerInfoA,
- priority = 3,
- ),
- CommunalWidgetContentModel(
- appWidgetId = 1,
- providerInfo = providerInfoB,
- priority = 2,
- ),
- CommunalWidgetContentModel(
- appWidgetId = 2,
- providerInfo = providerInfoC,
- priority = 1,
- ),
- )
- )
- .containsExactly(*inventory!!.toTypedArray())
- }
-
private fun initCommunalWidgetRepository(): CommunalWidgetRepositoryImpl {
return CommunalWidgetRepositoryImpl(
- context,
appWidgetManager,
appWidgetHost,
+ testScope.backgroundScope,
+ testDispatcher,
broadcastDispatcher,
communalRepository,
+ communalWidgetHost,
+ communalWidgetDao,
packageManager,
userManager,
userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
index b589a2a..a903d25 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
@@ -22,6 +22,7 @@
import android.content.res.Resources
import android.content.res.Resources.NotFoundException
import android.test.suitebuilder.annotation.SmallTest
+import com.android.systemui.Flags.FLAG_SYSUI_TEAMFOOD
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
@@ -72,6 +73,8 @@
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
+ mSetFlagsRule.disableFlags(FLAG_SYSUI_TEAMFOOD)
+
flagMap.put(teamfoodableFlagA.name, teamfoodableFlagA)
flagMap.put(teamfoodableFlagB.name, teamfoodableFlagB)
mFeatureFlagsClassicDebug =
@@ -130,7 +133,7 @@
@Test
fun teamFoodFlag_True() {
- mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_SYSUI_TEAMFOOD)
+ mSetFlagsRule.enableFlags(FLAG_SYSUI_TEAMFOOD)
assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)).isTrue()
assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagB)).isTrue()
@@ -145,7 +148,7 @@
.thenReturn(true)
whenever(flagManager.readFlagValue<Boolean>(eq(teamfoodableFlagB.name), any()))
.thenReturn(false)
- mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_SYSUI_TEAMFOOD)
+ mSetFlagsRule.enableFlags(FLAG_SYSUI_TEAMFOOD)
assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)).isTrue()
assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagB)).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialogTest.kt
new file mode 100644
index 0000000..8b572eb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialogTest.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyboard.backlight.ui.view
+
+import android.testing.TestableLooper.RunWithLooper
+import android.view.View
+import android.view.accessibility.AccessibilityEvent
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWithLooper
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyboardBacklightDialogTest : SysuiTestCase() {
+
+ private lateinit var dialog: KeyboardBacklightDialog
+ private lateinit var rootView: View
+ private val descriptionString = context.getString(R.string.keyboard_backlight_value)
+
+ @Before
+ fun setUp() {
+ dialog =
+ KeyboardBacklightDialog(context, initialCurrentLevel = 0, initialMaxLevel = MAX_LEVEL)
+ dialog.show()
+ rootView = dialog.requireViewById(R.id.keyboard_backlight_dialog_container)
+ }
+
+ @Test
+ fun rootViewContentDescription_containsInitialLevel() {
+ assertThat(rootView.contentDescription).isEqualTo(contentDescriptionForLevel(INITIAL_LEVEL))
+ }
+
+ @Test
+ fun contentDescriptionUpdated_afterEveryLevelUpdate() {
+ val events = startCollectingAccessibilityEvents(rootView)
+
+ dialog.updateState(current = 1, max = MAX_LEVEL)
+
+ assertThat(rootView.contentDescription).isEqualTo(contentDescriptionForLevel(1))
+ assertThat(events).contains(AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION)
+ }
+
+ private fun contentDescriptionForLevel(level: Int): String {
+ return String.format(descriptionString, level, MAX_LEVEL)
+ }
+
+ private fun startCollectingAccessibilityEvents(rootView: View): MutableList<Int> {
+ val events = mutableListOf<Int>()
+ rootView.accessibilityDelegate =
+ object : View.AccessibilityDelegate() {
+ override fun sendAccessibilityEvent(host: View, eventType: Int) {
+ super.sendAccessibilityEvent(host, eventType)
+ events.add(eventType)
+ }
+ }
+ return events
+ }
+
+ companion object {
+ private const val MAX_LEVEL = 5
+ private const val INITIAL_LEVEL = 0
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 814a317..b16c352 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -194,6 +194,7 @@
private @Captor ArgumentCaptor<KeyguardUpdateMonitorCallback>
mKeyguardUpdateMonitorCallbackCaptor;
private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake();
+ private FakeExecutor mUiMainExecutor = new FakeExecutor(new FakeSystemClock());
private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
private FalsingCollectorFake mFalsingCollector;
@@ -247,6 +248,7 @@
mConfigurationController,
mViewMediator,
mKeyguardBypassController,
+ mUiMainExecutor,
mUiBgExecutor,
mColorExtractor,
mDumpManager,
@@ -255,7 +257,8 @@
mAuthController,
() -> mShadeInteractor,
mShadeWindowLogger,
- () -> mSelectedUserInteractor);
+ () -> mSelectedUserInteractor,
+ mUserTracker);
mFeatureFlags = new FakeFeatureFlags();
mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false);
mFeatureFlags.set(Flags.REFACTOR_GETCURRENTUSER, true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSectionTest.kt
index c7f7c3c..71313c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSectionTest.kt
@@ -26,6 +26,7 @@
import com.android.keyguard.LockIconViewController
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
+import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
@@ -59,9 +60,11 @@
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
+
+ mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
+
featureFlags =
FakeFeatureFlagsClassic().apply {
- set(Flags.MIGRATE_LOCK_ICON, false)
set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, false)
set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false)
}
@@ -81,7 +84,7 @@
@Test
fun addViewsConditionally_migrateFlagOn() {
- featureFlags.set(Flags.MIGRATE_LOCK_ICON, true)
+ mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
val constraintLayout = ConstraintLayout(context, null)
underTest.addViews(constraintLayout)
assertThat(constraintLayout.childCount).isGreaterThan(0)
@@ -89,7 +92,7 @@
@Test
fun addViewsConditionally_migrateAndRefactorFlagsOn() {
- featureFlags.set(Flags.MIGRATE_LOCK_ICON, true)
+ mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
featureFlags.set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true)
val constraintLayout = ConstraintLayout(context, null)
underTest.addViews(constraintLayout)
@@ -98,7 +101,7 @@
@Test
fun addViewsConditionally_migrateFlagOff() {
- featureFlags.set(Flags.MIGRATE_LOCK_ICON, false)
+ mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
featureFlags.set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, false)
val constraintLayout = ConstraintLayout(context, null)
underTest.addViews(constraintLayout)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt
index 8b8c59b..8dd33d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt
@@ -23,12 +23,10 @@
import androidx.test.filters.SmallTest
import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
+import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import com.android.systemui.statusbar.KeyguardIndicationController
-import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
@@ -43,7 +41,6 @@
@Mock private lateinit var keyguardIndicationAreaViewModel: KeyguardIndicationAreaViewModel
@Mock private lateinit var keyguardRootViewModel: KeyguardRootViewModel
@Mock private lateinit var indicationController: KeyguardIndicationController
- @Mock private lateinit var featureFlags: FeatureFlags
private lateinit var underTest: DefaultIndicationAreaSection
@@ -56,13 +53,12 @@
keyguardIndicationAreaViewModel,
keyguardRootViewModel,
indicationController,
- featureFlags,
)
}
@Test
fun addViewsConditionally() {
- whenever(featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)).thenReturn(true)
+ mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
val constraintLayout = ConstraintLayout(context, null)
underTest.addViews(constraintLayout)
assertThat(constraintLayout.childCount).isGreaterThan(0)
@@ -70,7 +66,7 @@
@Test
fun addViewsConditionally_migrateFlagOff() {
- whenever(featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)).thenReturn(false)
+ mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
val constraintLayout = ConstraintLayout(context, null)
underTest.addViews(constraintLayout)
assertThat(constraintLayout.childCount).isEqualTo(0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
index 34d93fc..88a4aa5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
@@ -20,7 +20,6 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.doze.util.BurnInHelperWrapper
-import com.android.systemui.flags.FeatureFlags
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
@@ -29,7 +28,6 @@
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -40,14 +38,12 @@
import org.mockito.Mock
import org.mockito.MockitoAnnotations
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class KeyguardIndicationAreaViewModelTest : SysuiTestCase() {
@Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
@Mock private lateinit var shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel
- @Mock private lateinit var featureFlags: FeatureFlags
private lateinit var underTest: KeyguardIndicationAreaViewModel
private lateinit var repository: FakeKeyguardRepository
@@ -87,7 +83,6 @@
keyguardBottomAreaViewModel = bottomAreaViewModel,
burnInHelperWrapper = burnInHelperWrapper,
shortcutsCombinedViewModel = shortcutsCombinedViewModel,
- featureFlags = featureFlags,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index 1c6cc87..25d1419 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -29,6 +29,7 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.dock.DockManagerFake
+import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
@@ -123,9 +124,11 @@
FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
dockManager = DockManagerFake()
biometricSettingsRepository = FakeBiometricSettingsRepository()
+
+ mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
+
val featureFlags =
FakeFeatureFlags().apply {
- set(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA, true)
set(Flags.FACE_AUTH_REFACTOR, true)
set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false)
set(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP, false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 985b6fd..259c74f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -32,6 +32,7 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
+import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.FakeFeatureFlagsClassicModule
import com.android.systemui.flags.Flags
@@ -106,9 +107,10 @@
testScope = TestScope(testDispatcher)
MockitoAnnotations.initMocks(this)
+ mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
+
val featureFlags =
FakeFeatureFlagsClassic().apply {
- set(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA, true)
set(Flags.FACE_AUTH_REFACTOR, true)
}
@@ -351,7 +353,6 @@
featureFlags =
FakeFeatureFlagsClassicModule {
setDefault(Flags.NEW_AOD_TRANSITION)
- set(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA, true)
set(Flags.FACE_AUTH_REFACTOR, true)
},
mocks =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
index c835146..8a531fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
@@ -111,7 +111,6 @@
TaskStackChangeListeners.getTestInstance(),
Optional.of(mock(Pip.class)),
Optional.of(mock(BackAnimation.class)),
- mock(FeatureFlags.class),
mock(SecureSettings.class),
mDisplayTracker));
initializeNavigationBars();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index 48a36cb..ddceed6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -27,6 +27,7 @@
import static android.view.WindowInsets.Type.ime;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.HOME_BUTTON_LONG_PRESS_DURATION_MS;
+import static com.android.systemui.assist.AssistManager.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS;
import static com.android.systemui.navigationbar.NavigationBar.NavBarActionEvent.NAVBAR_ASSIST_LONGPRESS;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
@@ -42,6 +43,7 @@
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -86,6 +88,7 @@
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
import com.android.systemui.navigationbar.buttons.DeadZone;
+import com.android.systemui.navigationbar.buttons.KeyButtonView;
import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.OverviewProxyService;
@@ -120,6 +123,7 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -143,6 +147,8 @@
@Mock
ButtonDispatcher mHomeButton;
@Mock
+ KeyButtonView mHomeButtonView;
+ @Mock
ButtonDispatcher mRecentsButton;
@Mock
ButtonDispatcher mAccessibilityButton;
@@ -294,11 +300,38 @@
@Test
public void testHomeLongPress() {
+ when(mAssistManager.shouldOverrideAssist(INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS))
+ .thenReturn(false);
+
mNavigationBar.init();
mNavigationBar.onViewAttached();
- mNavigationBar.onHomeLongClick(mNavigationBar.getView());
+ mNavigationBar.onHomeLongClick(mHomeButtonView);
verify(mUiEventLogger, times(1)).log(NAVBAR_ASSIST_LONGPRESS);
+ verify(mAssistManager).startAssist(any());
+ }
+
+ @Test
+ public void testHomeLongPressOverride() {
+ when(mAssistManager.shouldOverrideAssist(INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS))
+ .thenReturn(true);
+
+ mNavigationBar.init();
+ mNavigationBar.onViewAttached();
+ mNavigationBar.onHomeLongClick(mHomeButtonView);
+
+ verify(mUiEventLogger, times(1)).log(NAVBAR_ASSIST_LONGPRESS);
+
+ ArgumentCaptor<Runnable> onRippleInvisibleRunnableCaptor = ArgumentCaptor.forClass(
+ Runnable.class);
+ // startAssist is not called initially
+ verify(mAssistManager, never()).startAssist(any());
+ // but a Runnable is added for when the ripple is invisible
+ verify(mHomeButtonView).setOnRippleInvisibleRunnable(
+ onRippleInvisibleRunnableCaptor.capture());
+ // and when that runs, startAssist is called
+ onRippleInvisibleRunnableCaptor.getValue().run();
+ verify(mAssistManager).startAssist(any());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java
index 078a917..a1010a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java
@@ -50,6 +50,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.assist.AssistManager;
import com.android.systemui.recents.OverviewProxyService;
import org.junit.Before;
@@ -76,6 +77,7 @@
MockitoAnnotations.initMocks(this);
mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
mDependency.injectMockDependency(OverviewProxyService.class);
+ mDependency.injectMockDependency(AssistManager.class);
mUiEventLogger = mDependency.injectMockDependency(UiEventLogger.class);
TestableLooper.get(this).runWithLooper(() -> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
index 31d02ed..8f27e4e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
@@ -57,7 +57,7 @@
whenever(logBufferFactory.create(any(), any(), any())).thenReturn(logBuffer)
underTest =
QSTileLogger(
- mapOf(TileSpec.create("chatty_tile") to chattyLogBuffer),
+ mapOf("chatty_tile" to chattyLogBuffer),
logBufferFactory,
statusBarController
)
@@ -117,7 +117,7 @@
underTest.logUserActionPipeline(
TileSpec.create("test_spec"),
QSTileUserAction.Click(null),
- QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {},
+ QSTileState.build({ Icon.Resource(0, ContentDescription.Resource(0)) }, "") {},
"test_data",
)
@@ -143,7 +143,7 @@
fun testLogStateUpdate() {
underTest.logStateUpdate(
TileSpec.create("test_spec"),
- QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {},
+ QSTileState.build({ Icon.Resource(0, ContentDescription.Resource(0)) }, "") {},
"test_data",
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt
index 9bf4a75..d3b7daa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt
@@ -97,7 +97,10 @@
{
object : QSTileDataToStateMapper<Any> {
override fun map(config: QSTileConfig, data: Any): QSTileState =
- QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {}
+ QSTileState.build(
+ { Icon.Resource(0, ContentDescription.Resource(0)) },
+ ""
+ ) {}
}
},
fakeDisabledByPolicyInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
index 32a38bd..4c8b562 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
@@ -67,6 +67,7 @@
listOf(
AconfigFlags.FLAG_SCENE_CONTAINER,
+ AconfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
)
.forEach { flagToken ->
setFlagsRule.enableFlags(flagToken)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index a7e1e9d..e6cd17f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -77,6 +77,7 @@
import com.android.systemui.scene.domain.interactor.SceneInteractor;
import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
import com.android.systemui.scene.shared.logger.SceneLogger;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.data.repository.FakeShadeRepository;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -136,8 +137,10 @@
@Mock private ShadeExpansionStateManager mShadeExpansionStateManager;
@Mock private ShadeWindowLogger mShadeWindowLogger;
@Mock private SelectedUserInteractor mSelectedUserInteractor;
+ @Mock private UserTracker mUserTracker;
@Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters;
@Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListener;
+ private final Executor mMainExecutor = MoreExecutors.directExecutor();
private final Executor mBackgroundExecutor = MoreExecutors.directExecutor();
private SceneTestUtils mUtils = new SceneTestUtils(this);
private TestScope mTestScope = mUtils.getTestScope();
@@ -261,6 +264,7 @@
mConfigurationController,
mKeyguardViewMediator,
mKeyguardBypassController,
+ mMainExecutor,
mBackgroundExecutor,
mColorExtractor,
mDumpManager,
@@ -269,7 +273,8 @@
mAuthController,
() -> mShadeInteractor,
mShadeWindowLogger,
- () -> mSelectedUserInteractor) {
+ () -> mSelectedUserInteractor,
+ mUserTracker) {
@Override
protected boolean isDebuggable() {
return false;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 4f19742..a580600 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -334,6 +334,8 @@
@Mock
private SelectedUserInteractor mSelectedUserInteractor;
@Mock
+ private UserTracker mUserTracker;
+ @Mock
private NotifPipelineFlags mNotifPipelineFlags;
@Mock
private Icon mAppBubbleIcon;
@@ -488,6 +490,7 @@
mKeyguardViewMediator,
mKeyguardBypassController,
syncExecutor,
+ syncExecutor,
mColorExtractor,
mDumpManager,
mKeyguardStateController,
@@ -495,7 +498,8 @@
mAuthController,
() -> mShadeInteractor,
mShadeWindowLogger,
- () -> mSelectedUserInteractor
+ () -> mSelectedUserInteractor,
+ mUserTracker
);
mNotificationShadeWindowController.fetchWindowRootView();
mNotificationShadeWindowController.attach();
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
index 08adda3..8a2ff50 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
@@ -1,6 +1,5 @@
package com.android.systemui.communal.data.repository
-import com.android.systemui.communal.data.model.CommunalWidgetMetadata
import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import kotlinx.coroutines.flow.Flow
@@ -10,7 +9,6 @@
class FakeCommunalWidgetRepository : CommunalWidgetRepository {
private val _stopwatchAppWidgetInfo = MutableStateFlow<CommunalAppWidgetInfo?>(null)
override val stopwatchAppWidgetInfo: Flow<CommunalAppWidgetInfo?> = _stopwatchAppWidgetInfo
- override var communalWidgetAllowlist: List<CommunalWidgetMetadata> = emptyList()
private val _communalWidgets = MutableStateFlow<List<CommunalWidgetContentModel>>(emptyList())
override val communalWidgets: Flow<List<CommunalWidgetContentModel>> = _communalWidgets
diff --git a/proto/src/criticalevents/critical_event_log.proto b/proto/src/criticalevents/critical_event_log.proto
index 25814ec..9cda267 100644
--- a/proto/src/criticalevents/critical_event_log.proto
+++ b/proto/src/criticalevents/critical_event_log.proto
@@ -59,8 +59,11 @@
AppNotResponding anr = 4;
JavaCrash java_crash = 5;
NativeCrash native_crash = 6;
+ SystemServerStarted system_server_started = 7;
}
+ message SystemServerStarted {}
+
message Watchdog {
// The watchdog subject.
// Required.
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java
index 46b4628..9c6dfc6 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java
@@ -31,7 +31,7 @@
* This class matches multi-finger multi-tap gestures. The number of fingers and the number of taps
* for each instance is specified in the constructor.
*/
-class MultiFingerMultiTap extends GestureMatcher {
+public class MultiFingerMultiTap extends GestureMatcher {
// The target number of taps.
final int mTargetTapCount;
@@ -56,7 +56,7 @@
* @throws IllegalArgumentException if <code>fingers<code/> is less than 2
* or <code>taps<code/> is not positive.
*/
- MultiFingerMultiTap(
+ public MultiFingerMultiTap(
Context context,
int fingers,
int taps,
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTapAndHold.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTapAndHold.java
index 9c54100..f586036f 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTapAndHold.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTapAndHold.java
@@ -23,9 +23,9 @@
* This class matches gestures of the form multi-finger multi-tap and hold. The number of fingers
* and taps for each instance is specified in the constructor.
*/
-class MultiFingerMultiTapAndHold extends MultiFingerMultiTap {
+public class MultiFingerMultiTapAndHold extends MultiFingerMultiTap {
- MultiFingerMultiTapAndHold(
+ public MultiFingerMultiTapAndHold(
Context context,
int fingers,
int taps,
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
index 5953d0d..36e75118 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
@@ -41,6 +41,8 @@
import com.android.server.accessibility.EventStreamTransformation;
import com.android.server.accessibility.Flags;
import com.android.server.accessibility.gestures.GestureMatcher;
+import com.android.server.accessibility.gestures.MultiFingerMultiTap;
+import com.android.server.accessibility.gestures.MultiFingerMultiTapAndHold;
import com.android.server.accessibility.gestures.MultiTap;
import com.android.server.accessibility.gestures.MultiTapAndHold;
@@ -476,6 +478,15 @@
null));
mGestureMatchers.add(new TwoFingersDownOrSwipe(context));
+ if (mDetectTwoFingerTripleTap) {
+ mGestureMatchers.add(new MultiFingerMultiTap(context, /* fingers= */ 2,
+ /* taps= */ 3, MagnificationGestureMatcher.GESTURE_TRIPLE_TAP,
+ null));
+ mGestureMatchers.add(new MultiFingerMultiTapAndHold(context, /* fingers= */ 2,
+ /* taps= */ 3, MagnificationGestureMatcher.GESTURE_TRIPLE_TAP_AND_HOLD,
+ null));
+ }
+
mGesturesObserver = new MagnificationGesturesObserver(this,
mGestureMatchers.toArray(new GestureMatcher[mGestureMatchers.size()]));
} else {
@@ -512,7 +523,9 @@
@Override
public boolean shouldStopDetection(MotionEvent motionEvent) {
return !mWindowMagnificationMgr.isWindowMagnifierEnabled(mDisplayId)
- && !mDetectSingleFingerTripleTap;
+ && !mDetectSingleFingerTripleTap
+ && !(mDetectTwoFingerTripleTap
+ && Flags.enableMagnificationMultipleFingerMultipleTapGesture());
}
@Override
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index c4cb816..256f2b3 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -180,14 +180,7 @@
# Snapshot rebuild instance
3131 pm_snapshot_rebuild (build_time|1|3),(lifetime|1|3)
# Caller information to clear application data
-1003160 pm_clear_app_data_caller (pid|1),(uid|1),(package|3)
-# ---------------------------
-# Installer.java
-# ---------------------------
-# Caller Information to clear application data
-1003200 installer_clear_app_data_caller (pid|1),(uid|1),(package|3),(flags|1)
-# Call stack to clear application data
-1003201 installer_clear_app_data_call_stack (method|3),(class|3),(file|3),(line|1)
+3132 pm_clear_app_data_caller (pid|1),(uid|1),(package|3)
# ---------------------------
# InputMethodManagerService.java
@@ -227,6 +220,14 @@
35000 auto_brightness_adj (old_lux|5),(old_brightness|5),(new_lux|5),(new_brightness|5)
# ---------------------------
+# Installer.java
+# ---------------------------
+# Caller Information to clear application data
+39000 installer_clear_app_data_caller (pid|1),(uid|1),(package|3),(flags|1)
+# Call stack to clear application data
+39001 installer_clear_app_data_call_stack (method|3),(class|3),(file|3),(line|1)
+
+# ---------------------------
# ConnectivityService.java
# ---------------------------
# Connectivity state changed
diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags
index 931914f..2aed847 100644
--- a/services/core/java/com/android/server/am/EventLogTags.logtags
+++ b/services/core/java/com/android/server/am/EventLogTags.logtags
@@ -131,4 +131,4 @@
30110 am_intent_sender_redirect_user (userId|1|5)
# Caller information to clear application data
-1030002 am_clear_app_data_caller (pid|1),(uid|1),(package|3)
+30120 am_clear_app_data_caller (pid|1),(uid|1),(package|3)
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 87633e9..47a99fe 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -2030,6 +2030,9 @@
mTargetUserId = targetUserId;
userSwitchUiEnabled = mUserSwitchUiEnabled;
}
+ if (android.multiuser.Flags.useAllCpusDuringUserSwitch()) {
+ mInjector.setHasTopUi(true);
+ }
if (userSwitchUiEnabled) {
UserInfo currentUserInfo = getUserInfo(currentUserId);
Pair<UserInfo, UserInfo> userNames = new Pair<>(currentUserInfo, targetUserInfo);
@@ -2098,6 +2101,9 @@
}
private void endUserSwitch() {
+ if (android.multiuser.Flags.useAllCpusDuringUserSwitch()) {
+ mInjector.setHasTopUi(false);
+ }
final int nextUserId;
synchronized (mLock) {
nextUserId = ObjectUtils.getOrElse(mPendingTargetUserIds.poll(), UserHandle.USER_NULL);
@@ -3781,6 +3787,15 @@
getSystemServiceManager().onUserStarting(TimingsTraceAndSlog.newAsyncLog(), userId);
}
+ void setHasTopUi(boolean hasTopUi) {
+ try {
+ Slogf.i(TAG, "Setting hasTopUi to " + hasTopUi);
+ mService.setHasTopUi(hasTopUi);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "Failed to allow using all CPU cores", e);
+ }
+ }
+
void onSystemUserVisibilityChanged(boolean visible) {
getUserManagerInternal().onSystemUserVisibilityChanged(visible);
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 2897075..5d4f711 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -70,7 +70,6 @@
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
-import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -172,7 +171,7 @@
@NonNull AudioSystemAdapter audioSystem) {
mContext = context;
mAudioService = service;
- mBtHelper = new BtHelper(this);
+ mBtHelper = new BtHelper(this, context);
mDeviceInventory = new AudioDeviceInventory(this);
mSystemServer = SystemServerAdapter.getDefaultAdapter(mContext);
mAudioSystem = audioSystem;
@@ -188,7 +187,7 @@
@NonNull AudioSystemAdapter audioSystem) {
mContext = context;
mAudioService = service;
- mBtHelper = new BtHelper(this);
+ mBtHelper = new BtHelper(this, context);
mDeviceInventory = mockDeviceInventory;
mSystemServer = mockSystemServer;
mAudioSystem = audioSystem;
@@ -1392,6 +1391,10 @@
return mAudioService.hasAudioFocusUsers();
}
+ /*package*/ void postInitSpatializerHeadTrackingSensors() {
+ mAudioService.postInitSpatializerHeadTrackingSensors();
+ }
+
//---------------------------------------------------------------------
// Message handling on behalf of helper classes.
// Each of these methods posts a message to mBrokerHandler message queue.
@@ -1475,6 +1478,15 @@
sendLMsgNoDelay(MSG_L_RECEIVED_BT_EVENT, SENDMSG_QUEUE, intent);
}
+ /*package*/ void postUpdateLeAudioGroupAddresses(int groupId) {
+ sendIMsgNoDelay(
+ MSG_I_UPDATE_LE_AUDIO_GROUP_ADDRESSES, SENDMSG_QUEUE, groupId);
+ }
+
+ /*package*/ void postSynchronizeLeDevicesInInventory(AdiDeviceState deviceState) {
+ sendLMsgNoDelay(MSG_L_SYNCHRONIZE_LE_DEVICES_IN_INVENTORY, SENDMSG_QUEUE, deviceState);
+ }
+
/*package*/ static final class CommunicationDeviceInfo {
final @NonNull IBinder mCb; // Identifies the requesting client for death handler
final int mUid; // Requester UID
@@ -1604,6 +1616,14 @@
}
}
+ /*package*/ int getLeAudioDeviceGroupId(BluetoothDevice device) {
+ return mBtHelper.getLeAudioDeviceGroupId(device);
+ }
+
+ /*package*/ List<String> getLeAudioGroupAddresses(int groupId) {
+ return mBtHelper.getLeAudioGroupAddresses(groupId);
+ }
+
/*package*/ void broadcastStickyIntentToCurrentProfileGroup(Intent intent) {
mSystemServer.broadcastStickyIntentToCurrentProfileGroup(intent);
}
@@ -1976,6 +1996,22 @@
onCheckCommunicationRouteClientState(msg.arg1, msg.arg2 == 1);
}
} break;
+
+ case MSG_I_UPDATE_LE_AUDIO_GROUP_ADDRESSES:
+ synchronized (mSetModeLock) {
+ synchronized (mDeviceStateLock) {
+ mDeviceInventory.onUpdateLeAudioGroupAddresses(msg.arg1);
+ }
+ } break;
+
+ case MSG_L_SYNCHRONIZE_LE_DEVICES_IN_INVENTORY:
+ synchronized (mSetModeLock) {
+ synchronized (mDeviceStateLock) {
+ mDeviceInventory.onSynchronizeLeDevicesInInventory(
+ (AdiDeviceState) msg.obj);
+ }
+ } break;
+
default:
Log.wtf(TAG, "Invalid message " + msg.what);
}
@@ -2058,6 +2094,10 @@
private static final int MSG_L_RECEIVED_BT_EVENT = 55;
private static final int MSG_CHECK_COMMUNICATION_ROUTE_CLIENT_STATE = 56;
+ private static final int MSG_I_UPDATE_LE_AUDIO_GROUP_ADDRESSES = 57;
+ private static final int MSG_L_SYNCHRONIZE_LE_DEVICES_IN_INVENTORY = 58;
+
+
private static boolean isMessageHandledUnderWakelock(int msgId) {
switch(msgId) {
@@ -2582,9 +2622,9 @@
}
}
- @Nullable UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
+ List<String> getDeviceAddresses(AudioDeviceAttributes device) {
synchronized (mDeviceStateLock) {
- return mDeviceInventory.getDeviceSensorUuid(device);
+ return mDeviceInventory.getDeviceAddresses(device);
}
}
@@ -2605,15 +2645,19 @@
* in order to be mocked by a test a the same package
* (see https://code.google.com/archive/p/mockito/issues/127)
*/
- public void persistAudioDeviceSettings() {
+ public void postPersistAudioDeviceSettings() {
sendMsg(MSG_PERSIST_AUDIO_DEVICE_SETTINGS, SENDMSG_REPLACE, /*delay*/ 1000);
}
void onPersistAudioDeviceSettings() {
final String deviceSettings = mDeviceInventory.getDeviceSettings();
- Log.v(TAG, "saving AdiDeviceState: " + deviceSettings);
- final SettingsAdapter settings = mAudioService.getSettings();
- boolean res = settings.putSecureStringForUser(mAudioService.getContentResolver(),
+ Log.v(TAG, "onPersistAudioDeviceSettings AdiDeviceState: " + deviceSettings);
+ String currentSettings = readDeviceSettings();
+ if (deviceSettings.equals(currentSettings)) {
+ return;
+ }
+ final SettingsAdapter settingsAdapter = mAudioService.getSettings();
+ boolean res = settingsAdapter.putSecureStringForUser(mAudioService.getContentResolver(),
Settings.Secure.AUDIO_DEVICE_INVENTORY,
deviceSettings, UserHandle.USER_CURRENT);
if (!res) {
@@ -2621,11 +2665,17 @@
}
}
+ private String readDeviceSettings() {
+ final SettingsAdapter settingsAdapter = mAudioService.getSettings();
+ final ContentResolver contentResolver = mAudioService.getContentResolver();
+ return settingsAdapter.getSecureStringForUser(contentResolver,
+ Settings.Secure.AUDIO_DEVICE_INVENTORY, UserHandle.USER_CURRENT);
+ }
+
void onReadAudioDeviceSettings() {
final SettingsAdapter settingsAdapter = mAudioService.getSettings();
final ContentResolver contentResolver = mAudioService.getContentResolver();
- String settings = settingsAdapter.getSecureStringForUser(contentResolver,
- Settings.Secure.AUDIO_DEVICE_INVENTORY, UserHandle.USER_CURRENT);
+ String settings = readDeviceSettings();
if (settings == null) {
Log.i(TAG, "reading AdiDeviceState from legacy key"
+ Settings.Secure.SPATIAL_AUDIO_ENABLED);
@@ -2688,8 +2738,8 @@
}
@Nullable
- AdiDeviceState findBtDeviceStateForAddress(String address, boolean isBle) {
- return mDeviceInventory.findBtDeviceStateForAddress(address, isBle);
+ AdiDeviceState findBtDeviceStateForAddress(String address, int deviceType) {
+ return mDeviceInventory.findBtDeviceStateForAddress(address, deviceType);
}
//------------------------------------------------
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index e59fd77..7ba0827 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -15,14 +15,23 @@
*/
package com.android.server.audio;
+import static android.media.AudioSystem.DEVICE_IN_ALL_SCO_SET;
import static android.media.AudioSystem.DEVICE_OUT_ALL_A2DP_SET;
import static android.media.AudioSystem.DEVICE_OUT_ALL_BLE_SET;
+import static android.media.AudioSystem.DEVICE_OUT_ALL_SCO_SET;
+import static android.media.AudioSystem.DEVICE_OUT_HEARING_AID;
+import static android.media.AudioSystem.isBluetoothA2dpOutDevice;
import static android.media.AudioSystem.isBluetoothDevice;
+import static android.media.AudioSystem.isBluetoothLeOutDevice;
+import static android.media.AudioSystem.isBluetoothOutDevice;
+import static android.media.AudioSystem.isBluetoothScoOutDevice;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeAudio;
import android.bluetooth.BluetoothProfile;
import android.content.Intent;
import android.media.AudioDeviceAttributes;
@@ -72,7 +81,6 @@
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
-import java.util.UUID;
import java.util.stream.Stream;
/**
@@ -118,6 +126,7 @@
return oldState;
});
}
+ mDeviceBroker.postSynchronizeLeDevicesInInventory(deviceState);
}
/**
@@ -125,23 +134,28 @@
* Bluetooth device and no corresponding entry already exists.
* @param ada the device to add if needed
*/
- void addAudioDeviceInInventoryIfNeeded(AudioDeviceAttributes ada) {
- if (!AudioSystem.isBluetoothOutDevice(ada.getInternalType())) {
+ void addAudioDeviceInInventoryIfNeeded(int deviceType, String address, String peerAddres) {
+ if (!isBluetoothOutDevice(deviceType)) {
return;
}
synchronized (mDeviceInventoryLock) {
- if (findDeviceStateForAudioDeviceAttributes(ada, ada.getType()) != null) {
+ AdiDeviceState ads = findBtDeviceStateForAddress(address, deviceType);
+ if (ads == null) {
+ ads = findBtDeviceStateForAddress(peerAddres, deviceType);
+ }
+ if (ads != null) {
+ mDeviceBroker.postSynchronizeLeDevicesInInventory(ads);
return;
}
- AdiDeviceState ads = new AdiDeviceState(
- ada.getType(), ada.getInternalType(), ada.getAddress());
+ ads = new AdiDeviceState(AudioDeviceInfo.convertInternalDeviceToDeviceType(deviceType),
+ deviceType, address);
mDeviceInventory.put(ads.getDeviceId(), ads);
+ mDeviceBroker.postPersistAudioDeviceSettings();
}
- mDeviceBroker.persistAudioDeviceSettings();
}
/**
- * Adds a new AdiDeviceState or updates the audio device cateogory of the matching
+ * Adds a new AdiDeviceState or updates the audio device category of the matching
* AdiDeviceState in the {@link AudioDeviceInventory#mDeviceInventory} list.
* @param deviceState the device to update
*/
@@ -152,6 +166,63 @@
return oldState;
});
}
+ mDeviceBroker.postSynchronizeLeDevicesInInventory(deviceState);
+ }
+
+ /**
+ * synchronize AdiDeviceState for LE devices in the same group
+ */
+ void onSynchronizeLeDevicesInInventory(AdiDeviceState updatedDevice) {
+ synchronized (mDevicesLock) {
+ synchronized (mDeviceInventoryLock) {
+ boolean found = false;
+ for (DeviceInfo di : mConnectedDevices.values()) {
+ if (di.mDeviceType != updatedDevice.getInternalDeviceType()) {
+ continue;
+ }
+ if (di.mDeviceAddress.equals(updatedDevice.getDeviceAddress())) {
+ for (AdiDeviceState ads2 : mDeviceInventory.values()) {
+ if (!(di.mDeviceType == ads2.getInternalDeviceType()
+ && di.mPeerDeviceAddress.equals(ads2.getDeviceAddress()))) {
+ continue;
+ }
+ ads2.setHasHeadTracker(updatedDevice.hasHeadTracker());
+ ads2.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
+ ads2.setSAEnabled(updatedDevice.isSAEnabled());
+ ads2.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory());
+ found = true;
+ AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
+ "onSynchronizeLeDevicesInInventory synced device pair ads1="
+ + updatedDevice + " ads2=" + ads2).printLog(TAG));
+ break;
+ }
+ }
+ if (di.mPeerDeviceAddress.equals(updatedDevice.getDeviceAddress())) {
+ for (AdiDeviceState ads2 : mDeviceInventory.values()) {
+ if (!(di.mDeviceType == ads2.getInternalDeviceType()
+ && di.mDeviceAddress.equals(ads2.getDeviceAddress()))) {
+ continue;
+ }
+ ads2.setHasHeadTracker(updatedDevice.hasHeadTracker());
+ ads2.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
+ ads2.setSAEnabled(updatedDevice.isSAEnabled());
+ ads2.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory());
+ found = true;
+ AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
+ "onSynchronizeLeDevicesInInventory synced device pair ads1="
+ + updatedDevice + " peer ads2=" + ads2).printLog(TAG));
+ break;
+ }
+ }
+ if (found) {
+ break;
+ }
+ }
+ if (found) {
+ mDeviceBroker.postPersistAudioDeviceSettings();
+ }
+ }
+ }
}
/**
@@ -163,9 +234,21 @@
* @return the found {@link AdiDeviceState} or {@code null} otherwise.
*/
@Nullable
- AdiDeviceState findBtDeviceStateForAddress(String address, boolean isBle) {
+ AdiDeviceState findBtDeviceStateForAddress(String address, int deviceType) {
+ Set<Integer> deviceSet;
+ if (isBluetoothA2dpOutDevice(deviceType)) {
+ deviceSet = DEVICE_OUT_ALL_A2DP_SET;
+ } else if (isBluetoothLeOutDevice(deviceType)) {
+ deviceSet = DEVICE_OUT_ALL_BLE_SET;
+ } else if (isBluetoothScoOutDevice(deviceType)) {
+ deviceSet = DEVICE_OUT_ALL_SCO_SET;
+ } else if (deviceType == DEVICE_OUT_HEARING_AID) {
+ deviceSet = new HashSet<>();
+ deviceSet.add(DEVICE_OUT_HEARING_AID);
+ } else {
+ return null;
+ }
synchronized (mDeviceInventoryLock) {
- final Set<Integer> deviceSet = isBle ? DEVICE_OUT_ALL_BLE_SET : DEVICE_OUT_ALL_A2DP_SET;
for (Integer internalType : deviceSet) {
AdiDeviceState deviceState = mDeviceInventory.get(
new Pair<>(internalType, address));
@@ -345,7 +428,8 @@
final @NonNull String mDeviceName;
final @NonNull String mDeviceAddress;
int mDeviceCodecFormat;
- final UUID mSensorUuid;
+ @NonNull String mPeerDeviceAddress;
+ final int mGroupId;
/** Disabled operating modes for this device. Use a negative logic so that by default
* an empty list means all modes are allowed.
@@ -353,12 +437,13 @@
@NonNull ArraySet<String> mDisabledModes = new ArraySet(0);
DeviceInfo(int deviceType, String deviceName, String deviceAddress,
- int deviceCodecFormat, @Nullable UUID sensorUuid) {
+ int deviceCodecFormat, String peerDeviceAddress, int groupId) {
mDeviceType = deviceType;
mDeviceName = deviceName == null ? "" : deviceName;
mDeviceAddress = deviceAddress == null ? "" : deviceAddress;
mDeviceCodecFormat = deviceCodecFormat;
- mSensorUuid = sensorUuid;
+ mPeerDeviceAddress = peerDeviceAddress == null ? "" : peerDeviceAddress;
+ mGroupId = groupId;
}
void setModeDisabled(String mode) {
@@ -379,7 +464,8 @@
DeviceInfo(int deviceType, String deviceName, String deviceAddress,
int deviceCodecFormat) {
- this(deviceType, deviceName, deviceAddress, deviceCodecFormat, null);
+ this(deviceType, deviceName, deviceAddress, deviceCodecFormat,
+ null, BluetoothLeAudio.GROUP_ID_INVALID);
}
DeviceInfo(int deviceType, String deviceName, String deviceAddress) {
@@ -393,7 +479,8 @@
+ ") name:" + mDeviceName
+ " addr:" + mDeviceAddress
+ " codec: " + Integer.toHexString(mDeviceCodecFormat)
- + " sensorUuid: " + Objects.toString(mSensorUuid)
+ + " peer addr:" + mPeerDeviceAddress
+ + " group:" + mGroupId
+ " disabled modes: " + mDisabledModes + "]";
}
@@ -714,6 +801,27 @@
}
}
+
+ /*package*/ void onUpdateLeAudioGroupAddresses(int groupId) {
+ synchronized (mDevicesLock) {
+ for (DeviceInfo di : mConnectedDevices.values()) {
+ if (di.mGroupId == groupId) {
+ List<String> addresses = mDeviceBroker.getLeAudioGroupAddresses(groupId);
+ if (di.mPeerDeviceAddress.equals("")) {
+ for (String addr : addresses) {
+ if (!addr.equals(di.mDeviceAddress)) {
+ di.mPeerDeviceAddress = addr;
+ break;
+ }
+ }
+ } else if (!addresses.contains(di.mPeerDeviceAddress)) {
+ di.mPeerDeviceAddress = "";
+ }
+ }
+ }
+ }
+ }
+
/*package*/ void onReportNewRoutes() {
int n = mRoutesObservers.beginBroadcast();
if (n > 0) {
@@ -1419,7 +1527,7 @@
if (!connect) {
purgeDevicesRoles_l();
} else {
- addAudioDeviceInInventoryIfNeeded(attributes);
+ addAudioDeviceInInventoryIfNeeded(device, address, "");
}
}
mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record();
@@ -1477,7 +1585,7 @@
final ArraySet<String> toRemove = new ArraySet<>();
// Disconnect ALL DEVICE_OUT_HEARING_AID devices
mConnectedDevices.values().forEach(deviceInfo -> {
- if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) {
+ if (deviceInfo.mDeviceType == DEVICE_OUT_HEARING_AID) {
toRemove.add(deviceInfo.mDeviceAddress);
}
});
@@ -1485,8 +1593,8 @@
.set(MediaMetrics.Property.EVENT, "disconnectHearingAid")
.record();
if (toRemove.size() > 0) {
- final int delay = checkSendBecomingNoisyIntentInt(
- AudioSystem.DEVICE_OUT_HEARING_AID, 0, AudioSystem.DEVICE_NONE);
+ final int delay = checkSendBecomingNoisyIntentInt(DEVICE_OUT_HEARING_AID,
+ AudioService.CONNECTION_STATE_DISCONNECTED, AudioSystem.DEVICE_NONE);
toRemove.stream().forEach(deviceAddress ->
// TODO delay not used?
makeHearingAidDeviceUnavailable(deviceAddress /*, delay*/)
@@ -1687,12 +1795,8 @@
// Reset A2DP suspend state each time a new sink is connected
mDeviceBroker.clearA2dpSuspended(true /* internalOnly */);
- // The convention for head tracking sensors associated with A2DP devices is to
- // use a UUID derived from the MAC address as follows:
- // time_low = 0, time_mid = 0, time_hi = 0, clock_seq = 0, node = MAC Address
- UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes(ada);
final DeviceInfo di = new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name,
- address, codec, sensorUuid);
+ address, codec);
final String diKey = di.getKey();
mConnectedDevices.put(diKey, di);
// on a connection always overwrite the device seen by AudioPolicy, see comment above when
@@ -1703,7 +1807,7 @@
setCurrentAudioRouteNameIfPossible(name, true /*fromA2dp*/);
updateBluetoothPreferredModes_l(btInfo.mDevice /*connectedDevice*/);
- addAudioDeviceInInventoryIfNeeded(ada);
+ addAudioDeviceInInventoryIfNeeded(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, "");
}
static final int[] CAPTURE_PRESETS = new int[] {AudioSource.MIC, AudioSource.CAMCORDER,
@@ -1723,15 +1827,15 @@
return;
}
DeviceInfo leOutDevice =
- getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_OUT_ALL_BLE_SET);
+ getFirstConnectedDeviceOfTypes(DEVICE_OUT_ALL_BLE_SET);
DeviceInfo leInDevice =
getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_IN_ALL_BLE_SET);
DeviceInfo a2dpDevice =
- getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_OUT_ALL_A2DP_SET);
+ getFirstConnectedDeviceOfTypes(DEVICE_OUT_ALL_A2DP_SET);
DeviceInfo scoOutDevice =
- getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_OUT_ALL_SCO_SET);
+ getFirstConnectedDeviceOfTypes(DEVICE_OUT_ALL_SCO_SET);
DeviceInfo scoInDevice =
- getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_IN_ALL_SCO_SET);
+ getFirstConnectedDeviceOfTypes(DEVICE_IN_ALL_SCO_SET);
boolean disableA2dp = (leOutDevice != null && leOutDevice.isOutputOnlyModeEnabled());
boolean disableSco = (leOutDevice != null && leOutDevice.isDuplexModeEnabled())
|| (leInDevice != null && leInDevice.isDuplexModeEnabled());
@@ -1765,7 +1869,7 @@
continue;
}
- if (AudioSystem.isBluetoothOutDevice(di.mDeviceType)) {
+ if (isBluetoothOutDevice(di.mDeviceType)) {
for (AudioProductStrategy strategy : mStrategies) {
boolean disable = false;
if (strategy.getId() == mDeviceBroker.mCommunicationStrategyId) {
@@ -1832,23 +1936,20 @@
int checkProfileIsConnected(int profile) {
switch (profile) {
case BluetoothProfile.HEADSET:
- if (getFirstConnectedDeviceOfTypes(
- AudioSystem.DEVICE_OUT_ALL_SCO_SET) != null
- || getFirstConnectedDeviceOfTypes(
- AudioSystem.DEVICE_IN_ALL_SCO_SET) != null) {
+ if (getFirstConnectedDeviceOfTypes(DEVICE_OUT_ALL_SCO_SET) != null
+ || getFirstConnectedDeviceOfTypes(DEVICE_IN_ALL_SCO_SET) != null) {
return profile;
}
break;
case BluetoothProfile.A2DP:
- if (getFirstConnectedDeviceOfTypes(
- AudioSystem.DEVICE_OUT_ALL_A2DP_SET) != null) {
+ if (getFirstConnectedDeviceOfTypes(DEVICE_OUT_ALL_A2DP_SET) != null) {
return profile;
}
break;
case BluetoothProfile.LE_AUDIO:
case BluetoothProfile.LE_AUDIO_BROADCAST:
if (getFirstConnectedDeviceOfTypes(
- AudioSystem.DEVICE_OUT_ALL_BLE_SET) != null
+ DEVICE_OUT_ALL_BLE_SET) != null
|| getFirstConnectedDeviceOfTypes(
AudioSystem.DEVICE_IN_ALL_BLE_SET) != null) {
return profile;
@@ -2006,28 +2107,28 @@
private void makeHearingAidDeviceAvailable(
String address, String name, int streamType, String eventSource) {
final int hearingAidVolIndex = mDeviceBroker.getVssVolumeForDevice(streamType,
- AudioSystem.DEVICE_OUT_HEARING_AID);
+ DEVICE_OUT_HEARING_AID);
mDeviceBroker.postSetHearingAidVolumeIndex(hearingAidVolIndex, streamType);
mDeviceBroker.setBluetoothA2dpOnInt(true, false /*fromA2dp*/, eventSource);
AudioDeviceAttributes ada = new AudioDeviceAttributes(
- AudioSystem.DEVICE_OUT_HEARING_AID, address, name);
+ DEVICE_OUT_HEARING_AID, address, name);
mAudioSystem.setDeviceConnectionState(ada,
AudioSystem.DEVICE_STATE_AVAILABLE,
AudioSystem.AUDIO_FORMAT_DEFAULT);
mConnectedDevices.put(
- DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address),
- new DeviceInfo(AudioSystem.DEVICE_OUT_HEARING_AID, name, address));
- mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_HEARING_AID);
+ DeviceInfo.makeDeviceListKey(DEVICE_OUT_HEARING_AID, address),
+ new DeviceInfo(DEVICE_OUT_HEARING_AID, name, address));
+ mDeviceBroker.postAccessoryPlugMediaUnmute(DEVICE_OUT_HEARING_AID);
mDeviceBroker.postApplyVolumeOnDevice(streamType,
- AudioSystem.DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable");
+ DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable");
setCurrentAudioRouteNameIfPossible(name, false /*fromA2dp*/);
- addAudioDeviceInInventoryIfNeeded(ada);
+ addAudioDeviceInInventoryIfNeeded(DEVICE_OUT_HEARING_AID, address, "");
new MediaMetrics.Item(mMetricsId + "makeHearingAidDeviceAvailable")
.set(MediaMetrics.Property.ADDRESS, address != null ? address : "")
.set(MediaMetrics.Property.DEVICE,
- AudioSystem.getDeviceName(AudioSystem.DEVICE_OUT_HEARING_AID))
+ AudioSystem.getDeviceName(DEVICE_OUT_HEARING_AID))
.set(MediaMetrics.Property.NAME, name)
.set(MediaMetrics.Property.STREAM_TYPE,
AudioSystem.streamToString(streamType))
@@ -2037,18 +2138,18 @@
@GuardedBy("mDevicesLock")
private void makeHearingAidDeviceUnavailable(String address) {
AudioDeviceAttributes ada = new AudioDeviceAttributes(
- AudioSystem.DEVICE_OUT_HEARING_AID, address);
+ DEVICE_OUT_HEARING_AID, address);
mAudioSystem.setDeviceConnectionState(ada,
AudioSystem.DEVICE_STATE_UNAVAILABLE,
AudioSystem.AUDIO_FORMAT_DEFAULT);
mConnectedDevices.remove(
- DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address));
+ DeviceInfo.makeDeviceListKey(DEVICE_OUT_HEARING_AID, address));
// Remove Hearing Aid routes as well
setCurrentAudioRouteNameIfPossible(null, false /*fromA2dp*/);
new MediaMetrics.Item(mMetricsId + "makeHearingAidDeviceUnavailable")
.set(MediaMetrics.Property.ADDRESS, address != null ? address : "")
.set(MediaMetrics.Property.DEVICE,
- AudioSystem.getDeviceName(AudioSystem.DEVICE_OUT_HEARING_AID))
+ AudioSystem.getDeviceName(DEVICE_OUT_HEARING_AID))
.record();
mDeviceBroker.postCheckCommunicationDeviceRemoval(ada);
}
@@ -2060,7 +2161,7 @@
*/
boolean isHearingAidConnected() {
return getFirstConnectedDeviceOfTypes(
- Sets.newHashSet(AudioSystem.DEVICE_OUT_HEARING_AID)) != null;
+ Sets.newHashSet(DEVICE_OUT_HEARING_AID)) != null;
}
/**
@@ -2102,6 +2203,20 @@
final String address = btInfo.mDevice.getAddress();
String name = BtHelper.getName(btInfo.mDevice);
+ // Find LE Group ID and peer headset address if available
+ final int groupId = mDeviceBroker.getLeAudioDeviceGroupId(btInfo.mDevice);
+ String peerAddress = "";
+ if (groupId != BluetoothLeAudio.GROUP_ID_INVALID) {
+ List<String> addresses = mDeviceBroker.getLeAudioGroupAddresses(groupId);
+ if (addresses.size() > 1) {
+ for (String addr : addresses) {
+ if (!addr.equals(address)) {
+ peerAddress = addr;
+ break;
+ }
+ }
+ }
+ }
// The BT Stack does not provide a name for LE Broadcast devices
if (device == AudioSystem.DEVICE_OUT_BLE_BROADCAST && name.equals("")) {
name = "Broadcast";
@@ -2127,14 +2242,12 @@
}
// Reset LEA suspend state each time a new sink is connected
mDeviceBroker.clearLeAudioSuspended(true /* internalOnly */);
-
- UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes(ada);
mConnectedDevices.put(DeviceInfo.makeDeviceListKey(device, address),
new DeviceInfo(device, name, address, AudioSystem.AUDIO_FORMAT_DEFAULT,
- sensorUuid));
+ peerAddress, groupId));
mDeviceBroker.postAccessoryPlugMediaUnmute(device);
setCurrentAudioRouteNameIfPossible(name, /*fromA2dp=*/false);
- addAudioDeviceInInventoryIfNeeded(ada);
+ addAudioDeviceInInventoryIfNeeded(device, address, peerAddress);
}
if (streamType == AudioSystem.STREAM_DEFAULT) {
@@ -2226,12 +2339,12 @@
BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_HDMI);
BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET);
BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_LINE);
- BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_HEARING_AID);
+ BECOMING_NOISY_INTENT_DEVICES_SET.add(DEVICE_OUT_HEARING_AID);
BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_BLE_HEADSET);
BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_BLE_BROADCAST);
- BECOMING_NOISY_INTENT_DEVICES_SET.addAll(AudioSystem.DEVICE_OUT_ALL_A2DP_SET);
+ BECOMING_NOISY_INTENT_DEVICES_SET.addAll(DEVICE_OUT_ALL_A2DP_SET);
BECOMING_NOISY_INTENT_DEVICES_SET.addAll(AudioSystem.DEVICE_OUT_ALL_USB_SET);
- BECOMING_NOISY_INTENT_DEVICES_SET.addAll(AudioSystem.DEVICE_OUT_ALL_BLE_SET);
+ BECOMING_NOISY_INTENT_DEVICES_SET.addAll(DEVICE_OUT_ALL_BLE_SET);
}
// must be called before removing the device from mConnectedDevices
@@ -2512,16 +2625,22 @@
mDevRoleCapturePresetDispatchers.finishBroadcast();
}
- @Nullable UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
+ List<String> getDeviceAddresses(AudioDeviceAttributes device) {
+ List<String> addresses = new ArrayList<String>();
final String key = DeviceInfo.makeDeviceListKey(device.getInternalType(),
device.getAddress());
synchronized (mDevicesLock) {
DeviceInfo di = mConnectedDevices.get(key);
- if (di == null) {
- return null;
+ if (di != null) {
+ if (!di.mDeviceAddress.isEmpty()) {
+ addresses.add(di.mDeviceAddress);
+ }
+ if (!di.mPeerDeviceAddress.isEmpty()) {
+ addresses.add(di.mPeerDeviceAddress);
+ }
}
- return di.mSensorUuid;
}
+ return addresses;
}
/*package*/ String getDeviceSettings() {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index b209fb0..99321c4 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -38,6 +38,7 @@
import static android.provider.Settings.Secure.VOLUME_HUSH_VIBRATE;
import static com.android.media.audio.Flags.bluetoothMacAddressAnonymization;
+import static com.android.media.audio.Flags.disablePrescaleAbsoluteVolume;
import static com.android.server.audio.SoundDoseHelper.ACTION_CHECK_MUSIC_ACTIVE;
import static com.android.server.utils.EventLogger.Event.ALOGE;
import static com.android.server.utils.EventLogger.Event.ALOGI;
@@ -236,7 +237,6 @@
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
-import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
@@ -1371,19 +1371,21 @@
sRingerAndZenModeMutedStreams, "onInitStreamsAndVolumes"));
setRingerModeInt(getRingerModeInternal(), false);
- final float[] preScale = new float[3];
- preScale[0] = mContext.getResources().getFraction(
- com.android.internal.R.fraction.config_prescaleAbsoluteVolume_index1,
- 1, 1);
- preScale[1] = mContext.getResources().getFraction(
- com.android.internal.R.fraction.config_prescaleAbsoluteVolume_index2,
- 1, 1);
- preScale[2] = mContext.getResources().getFraction(
- com.android.internal.R.fraction.config_prescaleAbsoluteVolume_index3,
- 1, 1);
- for (int i = 0; i < preScale.length; i++) {
- if (0.0f <= preScale[i] && preScale[i] <= 1.0f) {
- mPrescaleAbsoluteVolume[i] = preScale[i];
+ if (!disablePrescaleAbsoluteVolume()) {
+ final float[] preScale = new float[3];
+ preScale[0] = mContext.getResources().getFraction(
+ com.android.internal.R.fraction.config_prescaleAbsoluteVolume_index1,
+ 1, 1);
+ preScale[1] = mContext.getResources().getFraction(
+ com.android.internal.R.fraction.config_prescaleAbsoluteVolume_index2,
+ 1, 1);
+ preScale[2] = mContext.getResources().getFraction(
+ com.android.internal.R.fraction.config_prescaleAbsoluteVolume_index3,
+ 1, 1);
+ for (int i = 0; i < preScale.length; i++) {
+ if (0.0f <= preScale[i] && preScale[i] <= 1.0f) {
+ mPrescaleAbsoluteVolume[i] = preScale[i];
+ }
}
}
@@ -8618,7 +8620,7 @@
if (index == 0) {
// 0% for volume 0
index = 0;
- } else if (index > 0 && index <= 3) {
+ } else if (!disablePrescaleAbsoluteVolume() && index > 0 && index <= 3) {
// Pre-scale for volume steps 1 2 and 3
index = (int) (mIndexMax * mPrescaleAbsoluteVolume[index - 1]) / 10;
} else {
@@ -11051,7 +11053,9 @@
final String addr = Objects.requireNonNull(address);
- AdiDeviceState deviceState = mDeviceBroker.findBtDeviceStateForAddress(addr, isBle);
+ AdiDeviceState deviceState = mDeviceBroker.findBtDeviceStateForAddress(addr,
+ (isBle ? AudioSystem.DEVICE_OUT_BLE_HEADSET
+ : AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP));
int internalType = !isBle ? DEVICE_OUT_BLUETOOTH_A2DP
: ((btAudioDeviceCategory == AUDIO_DEVICE_CATEGORY_HEADPHONES)
@@ -11067,7 +11071,7 @@
deviceState.setAudioDeviceCategory(btAudioDeviceCategory);
mDeviceBroker.addOrUpdateBtAudioDeviceCategoryInInventory(deviceState);
- mDeviceBroker.persistAudioDeviceSettings();
+ mDeviceBroker.postPersistAudioDeviceSettings();
mSpatializerHelper.refreshDevice(deviceState.getAudioDeviceAttributes());
mSoundDoseHelper.setAudioDeviceCategory(addr, internalType,
@@ -11081,7 +11085,8 @@
super.getBluetoothAudioDeviceCategory_enforcePermission();
final AdiDeviceState deviceState = mDeviceBroker.findBtDeviceStateForAddress(
- Objects.requireNonNull(address), isBle);
+ Objects.requireNonNull(address), (isBle ? AudioSystem.DEVICE_OUT_BLE_HEADSET
+ : AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP));
if (deviceState == null) {
return AUDIO_DEVICE_CATEGORY_UNKNOWN;
}
@@ -11448,6 +11453,14 @@
pw.print(" adjust-only absolute volume devices="); pw.println(dumpDeviceTypes(
getAbsoluteVolumeDevicesWithBehavior(
AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY)));
+ pw.print(" pre-scale for bluetooth absolute volume ");
+ if (disablePrescaleAbsoluteVolume()) {
+ pw.println("= disabled");
+ } else {
+ pw.println("=" + mPrescaleAbsoluteVolume[0]
+ + ", " + mPrescaleAbsoluteVolume[1]
+ + ", " + mPrescaleAbsoluteVolume[2]);
+ }
pw.print(" mExtVolumeController="); pw.println(mExtVolumeController);
pw.print(" mHdmiAudioSystemClient="); pw.println(mHdmiAudioSystemClient);
pw.print(" mHdmiPlaybackClient="); pw.println(mHdmiPlaybackClient);
@@ -13585,8 +13598,8 @@
return activeAssistantUids;
}
- UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
- return mDeviceBroker.getDeviceSensorUuid(device);
+ List<String> getDeviceAddresses(AudioDeviceAttributes device) {
+ return mDeviceBroker.getDeviceAddresses(device);
}
//======================
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index cce6bd2..7b96215 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -26,7 +26,9 @@
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothLeAudio;
+import android.bluetooth.BluetoothLeAudioCodecStatus;
import android.bluetooth.BluetoothProfile;
+import android.content.Context;
import android.content.Intent;
import android.media.AudioDeviceAttributes;
import android.media.AudioManager;
@@ -43,6 +45,7 @@
import com.android.server.utils.EventLogger;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -57,9 +60,11 @@
private static final String TAG = "AS.BtHelper";
private final @NonNull AudioDeviceBroker mDeviceBroker;
+ private final @NonNull Context mContext;
- BtHelper(@NonNull AudioDeviceBroker broker) {
+ BtHelper(@NonNull AudioDeviceBroker broker, Context context) {
mDeviceBroker = broker;
+ mContext = context;
}
// BluetoothHeadset API to control SCO connection
@@ -498,6 +503,32 @@
}
}
+ // BluetoothLeAudio callback used to update the list of addresses in the same group as a
+ // connected LE Audio device
+ MyLeAudioCallback mLeAudioCallback = null;
+
+ class MyLeAudioCallback implements BluetoothLeAudio.Callback {
+ @Override
+ public void onCodecConfigChanged(int groupId,
+ @NonNull BluetoothLeAudioCodecStatus status) {
+ // Do nothing
+ }
+
+ @Override
+ public void onGroupNodeAdded(@NonNull BluetoothDevice device, int groupId) {
+ mDeviceBroker.postUpdateLeAudioGroupAddresses(groupId);
+ }
+
+ @Override
+ public void onGroupNodeRemoved(@NonNull BluetoothDevice device, int groupId) {
+ mDeviceBroker.postUpdateLeAudioGroupAddresses(groupId);
+ }
+ @Override
+ public void onGroupStatusChanged(int groupId, int groupStatus) {
+ mDeviceBroker.postUpdateLeAudioGroupAddresses(groupId);
+ }
+ }
+
// @GuardedBy("mDeviceBroker.mSetModeLock")
@GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
/*package*/ synchronized void onBtProfileConnected(int profile, BluetoothProfile proxy) {
@@ -519,6 +550,11 @@
mHearingAid = (BluetoothHearingAid) proxy;
break;
case BluetoothProfile.LE_AUDIO:
+ if (mLeAudio == null) {
+ mLeAudioCallback = new MyLeAudioCallback();
+ ((BluetoothLeAudio) proxy).registerCallback(
+ mContext.getMainExecutor(), mLeAudioCallback);
+ }
mLeAudio = (BluetoothLeAudio) proxy;
break;
case BluetoothProfile.A2DP_SINK:
@@ -977,6 +1013,28 @@
return result;
}
+ /*package*/ int getLeAudioDeviceGroupId(BluetoothDevice device) {
+ if (mLeAudio == null || device == null) {
+ return BluetoothLeAudio.GROUP_ID_INVALID;
+ }
+ return mLeAudio.getGroupId(device);
+ }
+
+ /*package*/ List<String> getLeAudioGroupAddresses(int groupId) {
+ List<String> addresses = new ArrayList<String>();
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ if (adapter == null || mLeAudio == null) {
+ return addresses;
+ }
+ List<BluetoothDevice> activeDevices = adapter.getActiveDevices(BluetoothProfile.LE_AUDIO);
+ for (BluetoothDevice device : activeDevices) {
+ if (device != null && mLeAudio.getGroupId(device) == groupId) {
+ addresses.add(device.getAddress());
+ }
+ }
+ return addresses;
+ }
+
/**
* Returns the String equivalent of the btCodecType.
*
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 7abd9c7..ea92154 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -564,7 +564,7 @@
}
if (updatedDevice != null) {
onRoutingUpdated();
- mDeviceBroker.persistAudioDeviceSettings();
+ mDeviceBroker.postPersistAudioDeviceSettings();
logDeviceState(updatedDevice, "addCompatibleAudioDevice");
}
}
@@ -614,7 +614,7 @@
if (deviceState != null && deviceState.isSAEnabled()) {
deviceState.setSAEnabled(false);
onRoutingUpdated();
- mDeviceBroker.persistAudioDeviceSettings();
+ mDeviceBroker.postPersistAudioDeviceSettings();
logDeviceState(deviceState, "removeCompatibleAudioDevice");
}
}
@@ -716,7 +716,7 @@
ada.getAddress());
initSAState(deviceState);
mDeviceBroker.addOrUpdateDeviceSAStateInInventory(deviceState);
- mDeviceBroker.persistAudioDeviceSettings();
+ mDeviceBroker.postPersistAudioDeviceSettings();
logDeviceState(deviceState, "addWirelessDeviceIfNew"); // may be updated later.
}
}
@@ -1206,7 +1206,7 @@
}
Log.i(TAG, "setHeadTrackerEnabled enabled:" + enabled + " device:" + ada);
deviceState.setHeadTrackerEnabled(enabled);
- mDeviceBroker.persistAudioDeviceSettings();
+ mDeviceBroker.postPersistAudioDeviceSettings();
logDeviceState(deviceState, "setHeadTrackerEnabled");
// check current routing to see if it affects the headtracking mode
@@ -1248,7 +1248,7 @@
if (deviceState != null) {
if (!deviceState.hasHeadTracker()) {
deviceState.setHasHeadTracker(true);
- mDeviceBroker.persistAudioDeviceSettings();
+ mDeviceBroker.postPersistAudioDeviceSettings();
logDeviceState(deviceState, "setHasHeadTracker");
}
return deviceState.isHeadTrackerEnabled();
@@ -1631,25 +1631,33 @@
return headHandle;
}
final AudioDeviceAttributes currentDevice = sRoutingDevices.get(0);
- UUID routingDeviceUuid = mAudioService.getDeviceSensorUuid(currentDevice);
+ List<String> deviceAddresses = mAudioService.getDeviceAddresses(currentDevice);
+
// We limit only to Sensor.TYPE_HEAD_TRACKER here to avoid confusion
// with gaming sensors. (Note that Sensor.TYPE_ROTATION_VECTOR
// and Sensor.TYPE_GAME_ROTATION_VECTOR are supported internally by
// SensorPoseProvider).
// Note: this is a dynamic sensor list right now.
List<Sensor> sensors = mSensorManager.getDynamicSensorList(Sensor.TYPE_HEAD_TRACKER);
- for (Sensor sensor : sensors) {
- final UUID uuid = sensor.getUuid();
- if (uuid.equals(routingDeviceUuid)) {
- headHandle = sensor.getHandle();
- if (!setHasHeadTracker(currentDevice)) {
- headHandle = -1;
+ for (String address : deviceAddresses) {
+ UUID routingDeviceUuid = UuidUtils.uuidFromAudioDeviceAttributes(
+ new AudioDeviceAttributes(currentDevice.getInternalType(), address));
+ for (Sensor sensor : sensors) {
+ final UUID uuid = sensor.getUuid();
+ if (uuid.equals(routingDeviceUuid)) {
+ headHandle = sensor.getHandle();
+ if (!setHasHeadTracker(currentDevice)) {
+ headHandle = -1;
+ }
+ break;
}
- break;
+ if (uuid.equals(UuidUtils.STANDALONE_UUID)) {
+ headHandle = sensor.getHandle();
+ // we do not break, perhaps we find a head tracker on device.
+ }
}
- if (uuid.equals(UuidUtils.STANDALONE_UUID)) {
- headHandle = sensor.getHandle();
- // we do not break, perhaps we find a head tracker on device.
+ if (headHandle != -1) {
+ break;
}
}
return headHandle;
diff --git a/services/core/java/com/android/server/criticalevents/CriticalEventLog.java b/services/core/java/com/android/server/criticalevents/CriticalEventLog.java
index ab480e8..0814375 100644
--- a/services/core/java/com/android/server/criticalevents/CriticalEventLog.java
+++ b/services/core/java/com/android/server/criticalevents/CriticalEventLog.java
@@ -31,6 +31,7 @@
import com.android.server.criticalevents.nano.CriticalEventProto.HalfWatchdog;
import com.android.server.criticalevents.nano.CriticalEventProto.JavaCrash;
import com.android.server.criticalevents.nano.CriticalEventProto.NativeCrash;
+import com.android.server.criticalevents.nano.CriticalEventProto.SystemServerStarted;
import com.android.server.criticalevents.nano.CriticalEventProto.Watchdog;
import java.io.File;
@@ -141,6 +142,13 @@
return System.currentTimeMillis();
}
+ /** Logs when system server started. */
+ public void logSystemServerStarted() {
+ CriticalEventProto event = new CriticalEventProto();
+ event.setSystemServerStarted(new SystemServerStarted());
+ log(event);
+ }
+
/** Logs a watchdog. */
public void logWatchdog(String subject, UUID uuid) {
Watchdog watchdog = new Watchdog();
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index a52870e..f8d27f1 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -476,20 +476,30 @@
pkgSetting.setLoadingProgress(1f);
}
+ // TODO: passes the package name as an argument in a message to the handler for V+
+ // so we don't need to rely on creating lambda objects so frequently.
+ if (UpdateOwnershipHelper.hasValidOwnershipDenyList(pkgSetting)) {
+ mPm.mHandler.post(() -> handleUpdateOwnerDenyList(pkgSetting));
+ }
+ return pkg;
+ }
+
+ private void handleUpdateOwnerDenyList(PackageSetting pkgSetting) {
ArraySet<String> listItems = mUpdateOwnershipHelper.readUpdateOwnerDenyList(pkgSetting);
if (listItems != null && !listItems.isEmpty()) {
- mUpdateOwnershipHelper.addToUpdateOwnerDenyList(pkgSetting.getPackageName(), listItems);
- for (String unownedPackage : listItems) {
- PackageSetting unownedSetting = mPm.mSettings.getPackageLPr(unownedPackage);
- SystemConfig config = SystemConfig.getInstance();
- if (unownedSetting != null
- && config.getSystemAppUpdateOwnerPackageName(unownedPackage) == null) {
- unownedSetting.setUpdateOwnerPackage(null);
+ mUpdateOwnershipHelper.addToUpdateOwnerDenyList(pkgSetting.getPackageName(),
+ listItems);
+ SystemConfig config = SystemConfig.getInstance();
+ synchronized (mPm.mLock) {
+ for (String unownedPackage : listItems) {
+ PackageSetting unownedSetting = mPm.mSettings.getPackageLPr(unownedPackage);
+ if (unownedSetting != null
+ && config.getSystemAppUpdateOwnerPackageName(unownedPackage) == null) {
+ unownedSetting.setUpdateOwnerPackage(null);
+ }
}
}
}
-
- return pkg;
}
/**
diff --git a/services/core/java/com/android/server/pm/UpdateOwnershipHelper.java b/services/core/java/com/android/server/pm/UpdateOwnershipHelper.java
index 43752f3..adac68b 100644
--- a/services/core/java/com/android/server/pm/UpdateOwnershipHelper.java
+++ b/services/core/java/com/android/server/pm/UpdateOwnershipHelper.java
@@ -48,7 +48,7 @@
private final Object mLock = new Object();
- private static boolean hasValidOwnershipDenyList(PackageSetting pkgSetting) {
+ static boolean hasValidOwnershipDenyList(PackageSetting pkgSetting) {
AndroidPackage pkg = pkgSetting.getPkg();
// we're checking for uses-permission for these priv permissions instead of grant as we're
// only considering system apps to begin with, so presumed to be granted.
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index cdaab46..2f9ef50 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -30,6 +30,7 @@
import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_FG_ONLY;
import static com.android.server.wm.ActivityTaskSupervisor.getApplicationLabel;
import static com.android.window.flags.Flags.balShowToasts;
+import static com.android.window.flags.Flags.balShowToastsBlocked;
import static com.android.server.wm.PendingRemoteAnimationRegistry.TIMEOUT_MS;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -321,6 +322,7 @@
.append(getDebugPackageName(mCallingPackage, mCallingUid));
sb.append("; callingUid: ").append(mCallingUid);
sb.append("; callingPid: ").append(mCallingPid);
+ sb.append("; isPendingIntent: ").append(isPendingIntent());
sb.append("; appSwitchState: ").append(mAppSwitchState);
sb.append("; callingUidHasAnyVisibleWindow: ").append(mCallingUidHasAnyVisibleWindow);
sb.append("; callingUidProcState: ").append(DebugUtils.valueToString(
@@ -328,7 +330,7 @@
sb.append("; isCallingUidPersistentSystemProcess: ")
.append(mIsCallingUidPersistentSystemProcess);
sb.append("; balAllowedByPiCreator: ").append(mBalAllowedByPiCreator);
- if (!isPendingIntent()) {
+ if (isPendingIntent()) {
sb.append("; balAllowedByPiSender: ").append(mBalAllowedByPiSender);
sb.append("; realCallingPackage: ")
.append(getDebugPackageName(mRealCallingPackage, mRealCallingUid));
@@ -340,16 +342,18 @@
ActivityManager.class, "PROCESS_STATE_", mRealCallingUidProcState));
sb.append("; isRealCallingUidPersistentSystemProcess: ")
.append(mIsRealCallingUidPersistentSystemProcess);
+ sb.append("; originatingPendingIntent: ").append(mOriginatingPendingIntent);
}
- sb.append("; originatingPendingIntent: ").append(mOriginatingPendingIntent);
sb.append("; backgroundStartPrivileges: ").append(mBackgroundStartPrivileges);
sb.append("; intent: ").append(mIntent);
sb.append("; callerApp: ").append(mCallerApp);
- sb.append("; realCallerApp: ").append(mRealCallerApp);
+ if (isPendingIntent()) {
+ sb.append("; realCallerApp: ").append(mRealCallerApp);
+ }
if (mCallerApp != null) {
sb.append("; inVisibleTask: ").append(mCallerApp.hasActivityInVisibleTask());
}
- if (!isPendingIntent()) {
+ if (isPendingIntent()) {
if (mRealCallerApp != null) {
sb.append("; realInVisibleTask: ")
.append(mRealCallerApp.hasActivityInVisibleTask());
@@ -469,7 +473,7 @@
// anything that has fallen through would currently be aborted
Slog.w(TAG, "Background activity launch blocked! "
+ state.dump(resultForCaller));
- showBalToast("BAL blocked", state);
+ showBalBlockedToast("BAL blocked", state);
return statsLog(BalVerdict.BLOCK, state);
}
@@ -509,10 +513,12 @@
== ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) {
// Both caller and real caller allow with system defined behavior
Slog.wtf(TAG,
- "With Android 15 BAL hardening this activity start would be blocked"
+ "With Android 15 BAL hardening this activity start may be blocked"
+ + " if the PI creator upgrades target_sdk to 35+"
+ + " AND the PI sender upgrades target_sdk to 34+! "
+ " (missing opt in by PI creator)! "
+ state.dump(resultForCaller, resultForRealCaller));
- showBalToast("BAL would be blocked", state);
+ showBalRiskToast("BAL would be blocked", state);
// return the realCaller result for backwards compatibility
return statsLog(resultForRealCaller, state);
}
@@ -521,10 +527,11 @@
== ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) {
// Allowed before V by creator
Slog.wtf(TAG,
- "With Android 15 BAL hardening this activity start would be blocked"
+ "With Android 15 BAL hardening this activity start may be blocked"
+ + " if the PI creator upgrades target_sdk to 35+! "
+ " (missing opt in by PI creator)! "
+ state.dump(resultForCaller, resultForRealCaller));
- showBalToast("BAL would be blocked", state);
+ showBalRiskToast("BAL would be blocked", state);
return statsLog(resultForCaller, state);
}
if (resultForRealCaller.allows()
@@ -533,10 +540,11 @@
// Allowed before U by sender
if (state.mBalAllowedByPiSender.allowsBackgroundActivityStarts()) {
Slog.wtf(TAG,
- "With Android 14 BAL hardening this activity start would be blocked"
+ "With Android 14 BAL hardening this activity start will be blocked"
+ + " if the PI sender upgrades target_sdk to 34+! "
+ " (missing opt in by PI sender)! "
+ state.dump(resultForCaller, resultForRealCaller));
- showBalToast("BAL would be blocked", state);
+ showBalBlockedToast("BAL would be blocked", state);
return statsLog(resultForRealCaller, state);
}
Slog.wtf(TAG, "Without Android 14 BAL hardening this activity start would be allowed"
@@ -547,7 +555,7 @@
// anything that has fallen through would currently be aborted
Slog.w(TAG, "Background activity launch blocked! "
+ state.dump(resultForCaller, resultForRealCaller));
- showBalToast("BAL blocked", state);
+ showBalBlockedToast("BAL blocked", state);
return statsLog(BalVerdict.BLOCK, state);
}
@@ -922,7 +930,15 @@
return true;
}
- private void showBalToast(String toastText, BalState state) {
+ private void showBalBlockedToast(String toastText, BalState state) {
+ if (balShowToastsBlocked()) {
+ showToast(toastText
+ + " caller:" + state.mCallingPackage
+ + " realCaller:" + state.mRealCallingPackage);
+ }
+ }
+
+ private void showBalRiskToast(String toastText, BalState state) {
if (balShowToasts()) {
showToast(toastText
+ " caller:" + state.mCallingPackage
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 00f2b89..2f52de4 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -274,11 +274,6 @@
boolean mClearedForReorderActivityToFront;
/**
- * Whether the TaskFragment surface is managed by a system {@link TaskFragmentOrganizer}.
- */
- boolean mIsSurfaceManagedBySystemOrganizer = false;
-
- /**
* When we are in the process of pausing an activity, before starting the
* next one, this variable holds the activity that is currently being paused.
*
@@ -453,21 +448,13 @@
void setTaskFragmentOrganizer(@NonNull TaskFragmentOrganizerToken organizer, int uid,
@NonNull String processName) {
- setTaskFragmentOrganizer(organizer, uid, processName,
- false /* isSurfaceManagedBySystemOrganizer */);
- }
-
- void setTaskFragmentOrganizer(@NonNull TaskFragmentOrganizerToken organizer, int uid,
- @NonNull String processName, boolean isSurfaceManagedBySystemOrganizer) {
mTaskFragmentOrganizer = ITaskFragmentOrganizer.Stub.asInterface(organizer.asBinder());
mTaskFragmentOrganizerUid = uid;
mTaskFragmentOrganizerProcessName = processName;
- mIsSurfaceManagedBySystemOrganizer = isSurfaceManagedBySystemOrganizer;
}
void onTaskFragmentOrganizerRemoved() {
mTaskFragmentOrganizer = null;
- mIsSurfaceManagedBySystemOrganizer = false;
}
/** Whether this TaskFragment is organized by the given {@code organizer}. */
@@ -2454,9 +2441,6 @@
if (mDelayOrganizedTaskFragmentSurfaceUpdate || mTaskFragmentOrganizer == null) {
return;
}
- if (mIsSurfaceManagedBySystemOrganizer) {
- return;
- }
if (mTransitionController.isShellTransitionsEnabled()
&& !mTransitionController.isCollecting(this)) {
// TaskFragmentOrganizer doesn't have access to the surface for security reasons, so
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 95e2515..89d47bc 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -2122,8 +2122,7 @@
// actions.
TaskFragmentOrganizerToken organizerToken = creationParams.getOrganizer();
taskFragment.setTaskFragmentOrganizer(organizerToken,
- ownerActivity.getUid(), ownerActivity.info.processName,
- mTaskFragmentOrganizerController.isSystemOrganizer(organizerToken.asBinder()));
+ ownerActivity.getUid(), ownerActivity.info.processName);
final int position;
if (creationParams.getPairedPrimaryFragmentToken() != null) {
// When there is a paired primary TaskFragment, we want to place the new TaskFragment
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 93dc219..34d6755 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -2008,6 +2008,10 @@
DeviceManagementResourcesProvider getDeviceManagementResourcesProvider() {
return new DeviceManagementResourcesProvider();
}
+
+ boolean isAdminInstalledCaCertAutoApproved() {
+ return false;
+ }
}
/**
@@ -6158,6 +6162,18 @@
.setAdmin(caller.getPackageName())
.setBoolean(/* isDelegate */ admin == null)
.write();
+
+ if (mInjector.isAdminInstalledCaCertAutoApproved()
+ && installedAlias != null
+ && admin != null) {
+ // If device admin called this, approve cert to avoid notifications
+ Slogf.i(LOG_TAG, "Approving admin installed cert");
+ approveCaCert(
+ installedAlias,
+ caller.getUserId(),
+ /* approved */ true);
+ }
+
return installedAlias;
});
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 56e385d..0a2e806 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -129,6 +129,7 @@
import com.android.server.contentcapture.ContentCaptureManagerInternal;
import com.android.server.coverage.CoverageService;
import com.android.server.cpu.CpuMonitorService;
+import com.android.server.criticalevents.CriticalEventLog;
import com.android.server.devicepolicy.DevicePolicyManagerService;
import com.android.server.devicestate.DeviceStateManagerService;
import com.android.server.display.DisplayManagerService;
@@ -964,6 +965,7 @@
// Only update the timeout after starting all the services so that we use
// the default timeout to start system server.
updateWatchdogTimeout(t);
+ CriticalEventLog.getInstance().logSystemServerStarted();
} catch (Throwable ex) {
Slog.e("System", "******************************************");
Slog.e("System", "************ Failure starting system services", ex);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java
index c88d6e4..612a091 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java
@@ -29,6 +29,9 @@
import android.graphics.Rect;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.TestableContext;
import android.util.DebugUtils;
import android.view.InputDevice;
@@ -40,6 +43,7 @@
import com.android.server.accessibility.AccessibilityTraceManager;
import com.android.server.accessibility.EventStreamTransformation;
+import com.android.server.accessibility.Flags;
import com.android.server.accessibility.utils.TouchEventGenerator;
import org.junit.After;
@@ -59,20 +63,29 @@
@RunWith(AndroidJUnit4.class)
public class WindowMagnificationGestureHandlerTest {
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
public static final int STATE_IDLE = 1;
public static final int STATE_SHOW_MAGNIFIER_SHORTCUT = 2;
public static final int STATE_TWO_FINGERS_DOWN = 3;
public static final int STATE_SHOW_MAGNIFIER_TRIPLE_TAP = 4;
public static final int STATE_NOT_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD = 5;
public static final int STATE_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD = 6;
+ public static final int STATE_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP = 7;
+ public static final int STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD = 8;
+ public static final int STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD = 9;
//TODO: Test it after can injecting Handler to GestureMatcher is available.
public static final int FIRST_STATE = STATE_IDLE;
public static final int LAST_STATE = STATE_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD;
+ public static final int LAST_STATE_WITH_MULTI_FINGER =
+ STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD;
// Co-prime x and y, to potentially catch x-y-swapped errors
public static final float DEFAULT_TAP_X = 301;
public static final float DEFAULT_TAP_Y = 299;
+ public static final PointF DEFAULT_POINT = new PointF(DEFAULT_TAP_X, DEFAULT_TAP_Y);
private static final int DISPLAY_0 = MockWindowMagnificationConnection.TEST_DISPLAY;
@Rule
@@ -141,7 +154,22 @@
throw new AssertionError("Failed while testing state " + stateToString(state),
e);
}
- });
+ }, LAST_STATE);
+ }
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
+ public void testEachState_enabledMultiFinger_isReachableAndRecoverable() {
+ forEachState(state -> {
+ goFromStateIdleTo(state);
+ assertIn(state);
+ returnToNormalFrom(state);
+ try {
+ assertIn(STATE_IDLE);
+ } catch (AssertionError e) {
+ throw new AssertionError("Failed while testing state " + stateToString(state),
+ e);
+ }
+ }, LAST_STATE_WITH_MULTI_FINGER);
}
@Test
@@ -159,8 +187,29 @@
returnToNormalFrom(state1);
}
}
- });
- });
+ }, LAST_STATE);
+ }, LAST_STATE);
+ }
+
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
+ public void testStates_enabledMultiFinger_areMutuallyExclusive() {
+ forEachState(state1 -> {
+ forEachState(state2 -> {
+ if (state1 < state2) {
+ goFromStateIdleTo(state1);
+ try {
+ assertIn(state2);
+ fail("State " + stateToString(state1) + " also implies state "
+ + stateToString(state2) + stateDump());
+ } catch (AssertionError e) {
+ // expected
+ returnToNormalFrom(state1);
+ }
+ }
+ }, LAST_STATE_WITH_MULTI_FINGER);
+ }, LAST_STATE_WITH_MULTI_FINGER);
}
@Test
@@ -187,8 +236,8 @@
returnToNormalFrom(STATE_SHOW_MAGNIFIER_TRIPLE_TAP);
}
- private void forEachState(IntConsumer action) {
- for (int state = FIRST_STATE; state <= LAST_STATE; state++) {
+ private void forEachState(IntConsumer action, int lastState) {
+ for (int state = FIRST_STATE; state <= lastState; state++) {
action.accept(state);
}
}
@@ -207,14 +256,16 @@
}
break;
case STATE_SHOW_MAGNIFIER_SHORTCUT:
- case STATE_SHOW_MAGNIFIER_TRIPLE_TAP: {
+ case STATE_SHOW_MAGNIFIER_TRIPLE_TAP:
+ case STATE_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP:
check(isWindowMagnifierEnabled(DISPLAY_0), state);
check(mWindowMagnificationGestureHandler.mCurrentState
== mWindowMagnificationGestureHandler.mDetectingState, state);
- }
break;
case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD:
- case STATE_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD: {
+ case STATE_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD:
+ case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD:
+ case STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD: {
check(isWindowMagnifierEnabled(DISPLAY_0), state);
check(mWindowMagnificationGestureHandler.mCurrentState
== mWindowMagnificationGestureHandler.mViewportDraggingState, state);
@@ -286,6 +337,29 @@
tapAndHold();
}
break;
+ case STATE_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP: {
+ twoFingerTap();
+ twoFingerTap();
+ twoFingerTap();
+ // Wait for two-finger tap gesture completed.
+ SystemClock.sleep(ViewConfiguration.getDoubleTapMinTime() + 500);
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
+ break;
+ case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD: {
+ twoFingerTap();
+ twoFingerTap();
+ twoFingerTapAndHold();
+ }
+ break;
+ case STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD: {
+ // enabled then perform two finger triple tap and hold gesture
+ goFromStateIdleTo(STATE_SHOW_MAGNIFIER_SHORTCUT);
+ twoFingerTap();
+ twoFingerTap();
+ twoFingerTapAndHold();
+ }
+ break;
default:
throw new IllegalArgumentException("Illegal state: " + state);
}
@@ -319,13 +393,22 @@
tap();
}
break;
- case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD: {
+ case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD:
+ case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD:
send(upEvent(DEFAULT_TAP_X, DEFAULT_TAP_Y));
- }
- break;
- case STATE_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD: {
+ break;
+ case STATE_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD:
+ case STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD:
send(upEvent(DEFAULT_TAP_X, DEFAULT_TAP_Y));
returnToNormalFrom(STATE_SHOW_MAGNIFIER_SHORTCUT);
+ break;
+ case STATE_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP: {
+ twoFingerTap();
+ twoFingerTap();
+ twoFingerTap();
+ // Wait for two-finger tap gesture completed.
+ SystemClock.sleep(ViewConfiguration.getDoubleTapMinTime() + 500);
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
break;
default:
@@ -365,6 +448,16 @@
return TouchEventGenerator.downEvent(DISPLAY_0, x, y);
}
+ private MotionEvent pointerDownEvent(float x, float y) {
+ return TouchEventGenerator.pointerDownEvent(DISPLAY_0,
+ new PointF[] {DEFAULT_POINT, new PointF(x, y)});
+ }
+
+ private MotionEvent pointerUpEvent(float x, float y) {
+ return TouchEventGenerator.pointerUpEvent(DISPLAY_0,
+ new PointF[] {DEFAULT_POINT, new PointF(x, y)});
+ }
+
private MotionEvent upEvent(float x, float y) {
return TouchEventGenerator.upEvent(DISPLAY_0, x, y);
}
@@ -379,6 +472,19 @@
SystemClock.sleep(ViewConfiguration.getLongPressTimeout() + 100);
}
+ private void twoFingerTap() {
+ send(downEvent(DEFAULT_TAP_X, DEFAULT_TAP_Y));
+ send(pointerDownEvent(DEFAULT_TAP_X * 2, DEFAULT_TAP_Y));
+ send(pointerUpEvent(DEFAULT_TAP_X * 2, DEFAULT_TAP_Y));
+ send(upEvent(DEFAULT_TAP_X, DEFAULT_TAP_Y));
+ }
+
+ private void twoFingerTapAndHold() {
+ send(downEvent(DEFAULT_TAP_X, DEFAULT_TAP_Y));
+ send(pointerDownEvent(DEFAULT_TAP_X * 2, DEFAULT_TAP_Y));
+ SystemClock.sleep(ViewConfiguration.getLongPressTimeout() + 100);
+ }
+
private String stateDump() {
return "\nCurrent state dump:\n" + mWindowMagnificationGestureHandler.mCurrentState;
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/utils/TouchEventGenerator.java b/services/tests/servicestests/src/com/android/server/accessibility/utils/TouchEventGenerator.java
index fbcde53..fcd16a0 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/utils/TouchEventGenerator.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/utils/TouchEventGenerator.java
@@ -39,10 +39,36 @@
return generateSingleTouchEvent(displayId, ACTION_DOWN, x, y);
}
+ /**
+ * Create a test {@link MotionEvent#ACTION_POINTER_DOWN}, filling in all the basic values that
+ * define the motion.
+ *
+ * @param displayId The id of the display
+ * @param pointFs location on the screen of the all pointers
+ */
+ public static MotionEvent pointerDownEvent(int displayId, PointF[] pointFs) {
+ final int actionIndex = 1 << MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+ final int action = ACTION_POINTER_DOWN | actionIndex;
+ return generateMultiplePointersEvent(displayId, action, pointFs);
+ }
+
public static MotionEvent moveEvent(int displayId, float x, float y) {
return generateSingleTouchEvent(displayId, ACTION_MOVE, x, y);
}
+ /**
+ * Create a test {@link MotionEvent#ACTION_POINTER_UP}, filling in all the basic values that
+ * define the motion.
+ *
+ * @param displayId the id of the display
+ * @param pointFs location on the screen of the all pointers
+ */
+ public static MotionEvent pointerUpEvent(int displayId, PointF[] pointFs) {
+ final int actionIndex = 1 << MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+ final int action = ACTION_POINTER_UP | actionIndex;
+ return generateMultiplePointersEvent(displayId, action, pointFs);
+ }
+
public static MotionEvent upEvent(int displayId, float x, float y) {
return generateSingleTouchEvent(displayId, ACTION_UP, x, y);
}
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
index 4e9ac7c..d4d3128 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
@@ -16,6 +16,8 @@
package com.android.server.audio;
+import static com.android.media.audio.Flags.FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME;
+
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
@@ -28,13 +30,22 @@
import android.media.AudioSystem;
import android.media.VolumeInfo;
import android.os.test.TestLooper;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import androidx.test.InstrumentationRegistry;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
public class AudioDeviceVolumeManagerTest {
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
private static final String TAG = "AudioDeviceVolumeManagerTest";
private static final AudioDeviceAttributes DEVICE_SPEAKER_OUT = new AudioDeviceAttributes(
@@ -96,4 +107,91 @@
verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
AudioManager.STREAM_MUSIC, midIndex, AudioSystem.DEVICE_OUT_USB_DEVICE);
}
+
+ @Test
+ @RequiresFlagsDisabled(FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME)
+ public void testConfigurablePreScaleAbsoluteVolume() throws Exception {
+ AudioManager am = mContext.getSystemService(AudioManager.class);
+ final int minIndex = am.getStreamMinVolume(AudioManager.STREAM_MUSIC);
+ final int maxIndex = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+ final VolumeInfo volMedia = new VolumeInfo.Builder(AudioManager.STREAM_MUSIC)
+ .setMinVolumeIndex(minIndex)
+ .setMaxVolumeIndex(maxIndex)
+ .build();
+ final AudioDeviceAttributes bleDevice = new AudioDeviceAttributes(
+ /*native type*/ AudioSystem.DEVICE_OUT_BLE_HEADSET, /*address*/ "fake_ble");
+ final int maxPreScaleIndex = 3;
+ final float[] preScale = new float[3];
+ preScale[0] = mContext.getResources().getFraction(
+ com.android.internal.R.fraction.config_prescaleAbsoluteVolume_index1,
+ 1, 1);
+ preScale[1] = mContext.getResources().getFraction(
+ com.android.internal.R.fraction.config_prescaleAbsoluteVolume_index2,
+ 1, 1);
+ preScale[2] = mContext.getResources().getFraction(
+ com.android.internal.R.fraction.config_prescaleAbsoluteVolume_index3,
+ 1, 1);
+
+ for (int i = 0; i < maxPreScaleIndex; i++) {
+ final int targetIndex = (int) (preScale[i] * maxIndex);
+ final VolumeInfo volCur = new VolumeInfo.Builder(volMedia)
+ .setVolumeIndex(i + 1).build();
+ // Adjust stream volume with FLAG_ABSOLUTE_VOLUME set (index:1~3)
+ mAudioService.setDeviceVolume(volCur, bleDevice, mPackageName);
+ mTestLooper.dispatchAll();
+
+ // Stream volume changes
+ verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
+ AudioManager.STREAM_MUSIC, targetIndex,
+ AudioSystem.DEVICE_OUT_BLE_HEADSET);
+ }
+
+ // Adjust stream volume with FLAG_ABSOLUTE_VOLUME set (index:4)
+ final VolumeInfo volIndex4 = new VolumeInfo.Builder(volMedia)
+ .setVolumeIndex(4).build();
+ mAudioService.setDeviceVolume(volIndex4, bleDevice, mPackageName);
+ mTestLooper.dispatchAll();
+
+ verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
+ AudioManager.STREAM_MUSIC, maxIndex,
+ AudioSystem.DEVICE_OUT_BLE_HEADSET);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME)
+ public void testDisablePreScaleAbsoluteVolume() throws Exception {
+ AudioManager am = mContext.getSystemService(AudioManager.class);
+ final int minIndex = am.getStreamMinVolume(AudioManager.STREAM_MUSIC);
+ final int maxIndex = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+ final VolumeInfo volMedia = new VolumeInfo.Builder(AudioManager.STREAM_MUSIC)
+ .setMinVolumeIndex(minIndex)
+ .setMaxVolumeIndex(maxIndex)
+ .build();
+ final AudioDeviceAttributes bleDevice = new AudioDeviceAttributes(
+ /*native type*/ AudioSystem.DEVICE_OUT_BLE_HEADSET, /*address*/ "bla");
+ final int maxPreScaleIndex = 3;
+
+ for (int i = 0; i < maxPreScaleIndex; i++) {
+ final VolumeInfo volCur = new VolumeInfo.Builder(volMedia)
+ .setVolumeIndex(i + 1).build();
+ // Adjust stream volume with FLAG_ABSOLUTE_VOLUME set (index:1~3)
+ mAudioService.setDeviceVolume(volCur, bleDevice, mPackageName);
+ mTestLooper.dispatchAll();
+
+ // Stream volume changes
+ verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
+ AudioManager.STREAM_MUSIC, maxIndex,
+ AudioSystem.DEVICE_OUT_BLE_HEADSET);
+ }
+
+ // Adjust stream volume with FLAG_ABSOLUTE_VOLUME set (index:4)
+ final VolumeInfo volIndex4 = new VolumeInfo.Builder(volMedia)
+ .setVolumeIndex(4).build();
+ mAudioService.setDeviceVolume(volIndex4, bleDevice, mPackageName);
+ mTestLooper.dispatchAll();
+
+ verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
+ AudioManager.STREAM_MUSIC, maxIndex,
+ AudioSystem.DEVICE_OUT_BLE_HEADSET);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java
index ad09ef0..061b8ff 100644
--- a/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java
@@ -79,7 +79,7 @@
final AudioDeviceAttributes dev3 =
new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, "R2:D2:bloop");
- doNothing().when(mSpyDeviceBroker).persistAudioDeviceSettings();
+ doNothing().when(mSpyDeviceBroker).postPersistAudioDeviceSettings();
mSpatHelper.initForTest(true /*binaural*/, true /*transaural*/);
// test with single device
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 7822071..ec068be 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -27,12 +27,8 @@
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyFloat;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.clearInvocations;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -130,17 +126,6 @@
}
@Test
- public void testUpdateOrganizedTaskFragmentSurface_noSurfaceUpdateWhenOrganizedBySystem() {
- clearInvocations(mTransaction);
- mTaskFragment.mIsSurfaceManagedBySystemOrganizer = true;
-
- mTaskFragment.updateOrganizedTaskFragmentSurface();
-
- verify(mTransaction, never()).setPosition(eq(mLeash), anyFloat(), anyFloat());
- verify(mTransaction, never()).setWindowCrop(eq(mLeash), anyInt(), anyInt());
- }
-
- @Test
public void testShouldStartChangeTransition_relativePositionChange() {
final Task task = createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW,
ACTIVITY_TYPE_STANDARD);
diff --git a/services/usb/Android.bp b/services/usb/Android.bp
index 9f3b52e..1dc5dcf 100644
--- a/services/usb/Android.bp
+++ b/services/usb/Android.bp
@@ -33,6 +33,6 @@
"android.hardware.usb-V1.1-java",
"android.hardware.usb-V1.2-java",
"android.hardware.usb-V1.3-java",
- "android.hardware.usb-V2-java",
+ "android.hardware.usb-V3-java",
],
}
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index 35e2fcf..fb13b33 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -1285,12 +1285,17 @@
pw.println(" dumpsys usb add-port \"matrix\" dual --compliance-warnings");
pw.println(" dumpsys usb set-compliance-reasons \"matrix\" <reason-list>");
pw.println(" dumpsys usb clear-compliance-reasons \"matrix\"");
- pw.println("<reason-list> is expected to be formatted as \"1, ..., 4\"");
+ pw.println("<reason-list> is expected to be formatted as \"1, ..., N\"");
pw.println("with reasons that need to be simulated.");
pw.println(" 1: other");
pw.println(" 2: debug accessory");
pw.println(" 3: bc12");
pw.println(" 4: missing rp");
+ pw.println(" 5: input power limited");
+ pw.println(" 6: missing data lines");
+ pw.println(" 7: enumeration fail");
+ pw.println(" 8: flaky connection");
+ pw.println(" 9: unreliable io");
pw.println();
pw.println("Example simulate DisplayPort Alt Mode Changes:");
pw.println(" dumpsys usb add-port \"matrix\" dual --displayport");
diff --git a/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java b/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java
index c7a7a9b..45b623b 100644
--- a/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java
+++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java
@@ -39,6 +39,7 @@
import android.hardware.usb.AltModeData;
import android.hardware.usb.AltModeData.DisplayPortAltModeData;
import android.hardware.usb.DisplayPortAltModePinAssignment;
+import android.hardware.usb.flags.Flags;
import android.os.Build;
import android.os.ServiceManager;
import android.os.IBinder;
@@ -593,11 +594,21 @@
for (int warning : complianceWarnings) {
if (newComplianceWarnings.indexOf(warning) == -1
&& warning >= UsbPortStatus.COMPLIANCE_WARNING_OTHER) {
- // ComplianceWarnings range from [1, 4] in Android U
- if (warning > UsbPortStatus.COMPLIANCE_WARNING_MISSING_RP) {
- newComplianceWarnings.add(UsbPortStatus.COMPLIANCE_WARNING_OTHER);
+ if (Flags.enableUsbDataComplianceWarning()) {
+ // ComplianceWarnings range extends to [1, 9] when feature flag is on
+ if (warning
+ > UsbPortStatus.COMPLIANCE_WARNING_UNRELIABLE_IO) {
+ newComplianceWarnings.add(UsbPortStatus.COMPLIANCE_WARNING_OTHER);
+ } else {
+ newComplianceWarnings.add(warning);
+ }
} else {
- newComplianceWarnings.add(warning);
+ // ComplianceWarnings range from [1, 4] in Android U
+ if (warning > UsbPortStatus.COMPLIANCE_WARNING_MISSING_RP) {
+ newComplianceWarnings.add(UsbPortStatus.COMPLIANCE_WARNING_OTHER);
+ } else {
+ newComplianceWarnings.add(warning);
+ }
}
}
}