Merge "Always launch BlockedAppStreamingActivity as SYSTEM user" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index eed248b..a80194c 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -335,6 +335,17 @@
mode: "exported",
}
+cc_aconfig_library {
+ name: "android.os.flags-aconfig-cc",
+ aconfig_declarations: "android.os.flags-aconfig",
+}
+
+cc_aconfig_library {
+ name: "android.os.flags-aconfig-cc-test",
+ aconfig_declarations: "android.os.flags-aconfig",
+ mode: "test",
+}
+
// VirtualDeviceManager
cc_aconfig_library {
name: "android.companion.virtualdevice.flags-aconfig-cc",
@@ -492,6 +503,13 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+java_aconfig_library {
+ name: "android.content.res.flags-aconfig-java-host",
+ aconfig_declarations: "android.content.res.flags-aconfig",
+ host_supported: true,
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Media BetterTogether
aconfig_declarations {
name: "com.android.media.flags.bettertogether-aconfig",
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index e96d07f..ee9400f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
@@ -46,8 +46,11 @@
import android.os.PowerManager;
import android.os.UserHandle;
import android.provider.DeviceConfig;
+import android.telephony.TelephonyManager;
+import android.telephony.UiccSlotMapping;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
+import android.util.IntArray;
import android.util.KeyValueListParser;
import android.util.Log;
import android.util.Slog;
@@ -68,6 +71,8 @@
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
+import java.util.Set;
import java.util.function.Predicate;
/**
@@ -1620,9 +1625,21 @@
private final Object mSatLock = new Object();
private DeviceIdleInternal mDeviceIdleInternal;
+ private TelephonyManager mTelephonyManager;
+
+ private final boolean mHasFeatureTelephonySubscription;
/** Set of all apps that have been deemed special, keyed by user ID. */
private final SparseSetArray<String> mSpecialApps = new SparseSetArray<>();
+ /**
+ * Set of carrier privileged apps, keyed by the logical ID of the SIM their privileged
+ * for.
+ */
+ @GuardedBy("mSatLock")
+ private final SparseSetArray<String> mCarrierPrivilegedApps = new SparseSetArray<>();
+ @GuardedBy("mSatLock")
+ private final SparseArray<LogicalIndexCarrierPrivilegesCallback>
+ mCarrierPrivilegedCallbacks = new SparseArray<>();
@GuardedBy("mSatLock")
private final ArraySet<String> mPowerAllowlistedApps = new ArraySet<>();
@@ -1630,6 +1647,10 @@
@Override
public void onReceive(Context context, Intent intent) {
switch (intent.getAction()) {
+ case TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED:
+ updateCarrierPrivilegedCallbackRegistration();
+ break;
+
case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED:
mHandler.post(SpecialAppTracker.this::updatePowerAllowlistCache);
break;
@@ -1637,6 +1658,11 @@
}
};
+ SpecialAppTracker() {
+ mHasFeatureTelephonySubscription = mContext.getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION);
+ }
+
public boolean isSpecialApp(final int userId, @NonNull String packageName) {
synchronized (mSatLock) {
if (mSpecialApps.contains(UserHandle.USER_ALL, packageName)) {
@@ -1654,6 +1680,12 @@
if (mPowerAllowlistedApps.contains(packageName)) {
return true;
}
+ for (int l = mCarrierPrivilegedApps.size() - 1; l >= 0; --l) {
+ if (mCarrierPrivilegedApps.contains(
+ mCarrierPrivilegedApps.keyAt(l), packageName)) {
+ return true;
+ }
+ }
}
return false;
}
@@ -1669,9 +1701,12 @@
private void onSystemServicesReady() {
mDeviceIdleInternal = LocalServices.getService(DeviceIdleInternal.class);
+ mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
synchronized (mLock) {
if (mFlexibilityEnabled) {
+ mHandler.post(
+ SpecialAppTracker.this::updateCarrierPrivilegedCallbackRegistration);
mHandler.post(SpecialAppTracker.this::updatePowerAllowlistCache);
}
}
@@ -1686,6 +1721,13 @@
private void startTracking() {
IntentFilter filter = new IntentFilter(
PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED);
+
+ if (mHasFeatureTelephonySubscription) {
+ filter.addAction(TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED);
+
+ updateCarrierPrivilegedCallbackRegistration();
+ }
+
mContext.registerReceiver(mBroadcastReceiver, filter);
updatePowerAllowlistCache();
@@ -1695,11 +1737,63 @@
mContext.unregisterReceiver(mBroadcastReceiver);
synchronized (mSatLock) {
+ mCarrierPrivilegedApps.clear();
mPowerAllowlistedApps.clear();
mSpecialApps.clear();
+
+ for (int i = mCarrierPrivilegedCallbacks.size() - 1; i >= 0; --i) {
+ mTelephonyManager.unregisterCarrierPrivilegesCallback(
+ mCarrierPrivilegedCallbacks.valueAt(i));
+ }
+ mCarrierPrivilegedCallbacks.clear();
}
}
+ private void updateCarrierPrivilegedCallbackRegistration() {
+ if (mTelephonyManager == null) {
+ return;
+ }
+ if (!mHasFeatureTelephonySubscription) {
+ return;
+ }
+
+ Collection<UiccSlotMapping> simSlotMapping = mTelephonyManager.getSimSlotMapping();
+ final ArraySet<String> changedPkgs = new ArraySet<>();
+ synchronized (mSatLock) {
+ final IntArray callbacksToRemove = new IntArray();
+ for (int i = mCarrierPrivilegedCallbacks.size() - 1; i >= 0; --i) {
+ callbacksToRemove.add(mCarrierPrivilegedCallbacks.keyAt(i));
+ }
+ for (UiccSlotMapping mapping : simSlotMapping) {
+ final int logicalIndex = mapping.getLogicalSlotIndex();
+ if (mCarrierPrivilegedCallbacks.contains(logicalIndex)) {
+ // Callback already exists. No need to create a new one or remove it.
+ callbacksToRemove.remove(logicalIndex);
+ continue;
+ }
+ final LogicalIndexCarrierPrivilegesCallback callback =
+ new LogicalIndexCarrierPrivilegesCallback(logicalIndex);
+ mCarrierPrivilegedCallbacks.put(logicalIndex, callback);
+ // Upon registration, the callbacks will be called with the current list of
+ // apps, so there's no need to query the app list synchronously.
+ mTelephonyManager.registerCarrierPrivilegesCallback(logicalIndex,
+ AppSchedulingModuleThread.getExecutor(), callback);
+ }
+
+ for (int i = callbacksToRemove.size() - 1; i >= 0; --i) {
+ final int logicalIndex = callbacksToRemove.get(i);
+ final LogicalIndexCarrierPrivilegesCallback callback =
+ mCarrierPrivilegedCallbacks.get(logicalIndex);
+ mTelephonyManager.unregisterCarrierPrivilegesCallback(callback);
+ mCarrierPrivilegedCallbacks.remove(logicalIndex);
+ changedPkgs.addAll(mCarrierPrivilegedApps.get(logicalIndex));
+ mCarrierPrivilegedApps.remove(logicalIndex);
+ }
+ }
+
+ updateSpecialAppSetUnlocked(UserHandle.USER_ALL, changedPkgs);
+ }
+
/**
* Update the processed special app set for the specified user ID, only looking at the
* specified set of apps. This method must <b>NEVER</b> be called while holding
@@ -1762,18 +1856,65 @@
updateSpecialAppSetUnlocked(UserHandle.USER_ALL, changedPkgs);
}
+ class LogicalIndexCarrierPrivilegesCallback implements
+ TelephonyManager.CarrierPrivilegesCallback {
+ public final int logicalIndex;
+
+ LogicalIndexCarrierPrivilegesCallback(int logicalIndex) {
+ this.logicalIndex = logicalIndex;
+ }
+
+ @Override
+ public void onCarrierPrivilegesChanged(@NonNull Set<String> privilegedPackageNames,
+ @NonNull Set<Integer> privilegedUids) {
+ final ArraySet<String> changedPkgs = new ArraySet<>();
+ synchronized (mSatLock) {
+ final ArraySet<String> oldPrivilegedSet =
+ mCarrierPrivilegedApps.get(logicalIndex);
+ if (oldPrivilegedSet != null) {
+ changedPkgs.addAll(oldPrivilegedSet);
+ mCarrierPrivilegedApps.remove(logicalIndex);
+ }
+ for (String pkgName : privilegedPackageNames) {
+ mCarrierPrivilegedApps.add(logicalIndex, pkgName);
+ if (!changedPkgs.remove(pkgName)) {
+ // The package wasn't in the previous set of privileged apps. Add it
+ // since its state has changed.
+ changedPkgs.add(pkgName);
+ }
+ }
+ }
+
+ // The carrier privileged list doesn't provide a simple userId correlation,
+ // so for now, use USER_ALL for these packages.
+ // TODO(141645789): use the UID list to narrow down to specific userIds
+ updateSpecialAppSetUnlocked(UserHandle.USER_ALL, changedPkgs);
+ }
+ }
+
public void dump(@NonNull IndentingPrintWriter pw) {
pw.println("Special apps:");
pw.increaseIndent();
synchronized (mSatLock) {
for (int u = 0; u < mSpecialApps.size(); ++u) {
+ pw.print("User ");
pw.print(mSpecialApps.keyAt(u));
pw.print(": ");
pw.println(mSpecialApps.valuesAt(u));
}
pw.println();
+ pw.println("Carrier privileged packages:");
+ pw.increaseIndent();
+ for (int i = 0; i < mCarrierPrivilegedApps.size(); ++i) {
+ pw.print(mCarrierPrivilegedApps.keyAt(i));
+ pw.print(": ");
+ pw.println(mCarrierPrivilegedApps.valuesAt(i));
+ }
+ pw.decreaseIndent();
+
+ pw.println();
pw.print("Power allowlisted packages: ");
pw.println(mPowerAllowlistedApps);
}
diff --git a/api/Android.bp b/api/Android.bp
index 8e06366..093ee4b 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -298,6 +298,28 @@
"org.xmlpull",
]
+// These are libs from framework-internal-utils that are required (i.e. being referenced)
+// from framework-non-updatable-sources. Add more here when there's a need.
+// DO NOT add the entire framework-internal-utils. It might cause unnecessary circular
+// dependencies gets bigger.
+android_non_updatable_stubs_libs = [
+ "android.hardware.cas-V1.2-java",
+ "android.hardware.health-V1.0-java-constants",
+ "android.hardware.thermal-V1.0-java-constants",
+ "android.hardware.thermal-V2.0-java",
+ "android.hardware.tv.input-V1.0-java-constants",
+ "android.hardware.usb-V1.0-java-constants",
+ "android.hardware.usb-V1.1-java-constants",
+ "android.hardware.usb.gadget-V1.0-java",
+ "android.hardware.vibrator-V1.3-java",
+ "framework-protos",
+]
+
+java_defaults {
+ name: "android-non-updatable-stubs-libs-defaults",
+ libs: android_non_updatable_stubs_libs,
+}
+
// Defaults for all stubs that include the non-updatable framework. These defaults do not include
// module symbols, so will not compile correctly on their own. Users must add module APIs to the
// classpath (or sources) somehow.
@@ -329,18 +351,7 @@
// from framework-non-updatable-sources. Add more here when there's a need.
// DO NOT add the entire framework-internal-utils. It might cause unnecessary circular
// dependencies gets bigger.
- libs: [
- "android.hardware.cas-V1.2-java",
- "android.hardware.health-V1.0-java-constants",
- "android.hardware.thermal-V1.0-java-constants",
- "android.hardware.thermal-V2.0-java",
- "android.hardware.tv.input-V1.0-java-constants",
- "android.hardware.usb-V1.0-java-constants",
- "android.hardware.usb-V1.1-java-constants",
- "android.hardware.usb.gadget-V1.0-java",
- "android.hardware.vibrator-V1.3-java",
- "framework-protos",
- ],
+ libs: android_non_updatable_stubs_libs,
flags: [
"--error NoSettingsProvider",
"--error UnhiddenSystemApi",
diff --git a/core/api/current.txt b/core/api/current.txt
index 62980ed..4c1b27d 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -5395,7 +5395,7 @@
public final class AutomaticZenRule implements android.os.Parcelable {
ctor @Deprecated public AutomaticZenRule(String, android.content.ComponentName, android.net.Uri, int, boolean);
- ctor public AutomaticZenRule(@NonNull String, @Nullable android.content.ComponentName, @Nullable android.content.ComponentName, @NonNull android.net.Uri, @Nullable android.service.notification.ZenPolicy, int, boolean);
+ ctor @Deprecated public AutomaticZenRule(@NonNull String, @Nullable android.content.ComponentName, @Nullable android.content.ComponentName, @NonNull android.net.Uri, @Nullable android.service.notification.ZenPolicy, int, boolean);
ctor public AutomaticZenRule(android.os.Parcel);
method public int describeContents();
method public android.net.Uri getConditionId();
@@ -6085,7 +6085,7 @@
public class GrammaticalInflectionManager {
method public int getApplicationGrammaticalGender();
- method @FlaggedApi("android.app.system_terms_of_address_enabled") public int getSystemGrammaticalGender();
+ method @FlaggedApi("android.app.system_terms_of_address_enabled") @RequiresPermission("android.permission.READ_SYSTEM_GRAMMATICAL_GENDER") public int getSystemGrammaticalGender();
method public void setRequestedApplicationGrammaticalGender(int);
}
@@ -16361,7 +16361,7 @@
ctor @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public Matrix44();
ctor @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public Matrix44(@NonNull android.graphics.Matrix);
method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public android.graphics.Matrix44 concat(@NonNull android.graphics.Matrix44);
- method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public float get(int, int);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public float get(@IntRange(from=0, to=3) int, @IntRange(from=0, to=3) int);
method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void getValues(@NonNull float[]);
method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public boolean invert();
method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public boolean isIdentity();
@@ -16370,7 +16370,7 @@
method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void reset();
method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public android.graphics.Matrix44 rotate(float, float, float, float);
method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public android.graphics.Matrix44 scale(float, float, float);
- method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void set(int, int, float);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void set(@IntRange(from=0, to=3) int, @IntRange(from=0, to=3) int, float);
method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void setValues(@NonNull float[]);
method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public android.graphics.Matrix44 translate(float, float, float);
}
@@ -41153,19 +41153,19 @@
method public final android.service.notification.StatusBarNotification[] getSnoozedNotifications();
method public final void migrateNotificationFilter(int, @Nullable java.util.List<java.lang.String>);
method public android.os.IBinder onBind(android.content.Intent);
- method public void onInterruptionFilterChanged(int);
- method public void onListenerConnected();
- method public void onListenerDisconnected();
- method public void onListenerHintsChanged(int);
- method public void onNotificationChannelGroupModified(String, android.os.UserHandle, android.app.NotificationChannelGroup, int);
- method public void onNotificationChannelModified(String, android.os.UserHandle, android.app.NotificationChannel, int);
- method public void onNotificationPosted(android.service.notification.StatusBarNotification);
- method public void onNotificationPosted(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap);
- method public void onNotificationRankingUpdate(android.service.notification.NotificationListenerService.RankingMap);
- method public void onNotificationRemoved(android.service.notification.StatusBarNotification);
- method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap);
- method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap, int);
- method public void onSilentStatusBarIconsVisibilityChanged(boolean);
+ method @UiThread public void onInterruptionFilterChanged(int);
+ method @UiThread public void onListenerConnected();
+ method @UiThread public void onListenerDisconnected();
+ method @UiThread public void onListenerHintsChanged(int);
+ method @UiThread public void onNotificationChannelGroupModified(String, android.os.UserHandle, android.app.NotificationChannelGroup, int);
+ method @UiThread public void onNotificationChannelModified(String, android.os.UserHandle, android.app.NotificationChannel, int);
+ method @UiThread public void onNotificationPosted(android.service.notification.StatusBarNotification);
+ method @UiThread public void onNotificationPosted(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap);
+ method @UiThread public void onNotificationRankingUpdate(android.service.notification.NotificationListenerService.RankingMap);
+ method @UiThread public void onNotificationRemoved(android.service.notification.StatusBarNotification);
+ method @UiThread public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap);
+ method @UiThread public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap, int);
+ method @UiThread public void onSilentStatusBarIconsVisibilityChanged(boolean);
method public final void requestInterruptionFilter(int);
method public final void requestListenerHints(int);
method public static void requestRebind(android.content.ComponentName);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 9c1a8e8..0ab2588 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -652,6 +652,7 @@
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.webkit.WebViewProviderResponse> CREATOR;
field public static final int STATUS_FAILED_LISTING_WEBVIEW_PACKAGES = 4; // 0x4
+ field public static final int STATUS_FAILED_OTHER = 11; // 0xb
field public static final int STATUS_FAILED_WAITING_FOR_RELRO = 3; // 0x3
field public static final int STATUS_SUCCESS = 0; // 0x0
field @Nullable public final android.content.pm.PackageInfo packageInfo;
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index afb796b..adc7ef1 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -4386,7 +4386,7 @@
field public static final int PROTECTION_FLAG_MODULE = 4194304; // 0x400000
field public static final int PROTECTION_FLAG_OEM = 16384; // 0x4000
field public static final int PROTECTION_FLAG_RECENTS = 33554432; // 0x2000000
- field public static final int PROTECTION_FLAG_RETAIL_DEMO = 16777216; // 0x1000000
+ field @Deprecated public static final int PROTECTION_FLAG_RETAIL_DEMO = 16777216; // 0x1000000
field public static final int PROTECTION_FLAG_ROLE = 67108864; // 0x4000000
field public static final int PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER = 65536; // 0x10000
field public static final int PROTECTION_FLAG_VENDOR_PRIVILEGED = 32768; // 0x8000
@@ -4821,7 +4821,7 @@
method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(@NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener);
method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener);
method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean areAnySensorPrivacyTogglesEnabled(int);
- method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @NonNull @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public java.util.Map<java.lang.String,java.lang.Boolean> getCameraPrivacyAllowlist();
+ method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @NonNull @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public java.util.List<java.lang.String> getCameraPrivacyAllowlist();
method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public int getSensorPrivacyState(int, int);
method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isCameraPrivacyEnabled(@NonNull String);
method @Deprecated @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isSensorPrivacyEnabled(int);
@@ -4844,6 +4844,12 @@
method public boolean isEnabled();
}
+ @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") public static class SensorPrivacyManager.StateTypes {
+ field public static final int DISABLED = 2; // 0x2
+ field public static final int ENABLED = 1; // 0x1
+ field public static final int ENABLED_EXCEPT_ALLOWLISTED_APPS = 3; // 0x3
+ }
+
}
package android.hardware.biometrics {
@@ -12885,7 +12891,7 @@
}
public abstract class NotificationListenerService extends android.app.Service {
- method public void onNotificationRemoved(@NonNull android.service.notification.StatusBarNotification, @NonNull android.service.notification.NotificationListenerService.RankingMap, @NonNull android.service.notification.NotificationStats, int);
+ method @UiThread public void onNotificationRemoved(@NonNull android.service.notification.StatusBarNotification, @NonNull android.service.notification.NotificationListenerService.RankingMap, @NonNull android.service.notification.NotificationStats, int);
}
public static class NotificationListenerService.Ranking {
@@ -12967,7 +12973,7 @@
field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceIntelligenceService";
}
- public static class OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException extends java.lang.Exception {
+ public abstract static class OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException extends java.lang.Exception {
ctor public OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException(int);
ctor public OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException(int, @NonNull String);
method public int getErrorCode();
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 6fb6b0d..53d0c03 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1519,6 +1519,7 @@
package android.hardware {
public final class SensorPrivacyManager {
+ method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setCameraPrivacyAllowlist(@NonNull java.util.List<java.lang.String>);
method @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacy(int, int, boolean);
method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacyState(int, int, int);
}
diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index f6ec370..5e2397d 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -173,8 +173,8 @@
* interrupt the user (e.g. via sound & vibration) while this rule
* is active.
* @param enabled Whether the rule is enabled.
- * @deprecated use {@link #AutomaticZenRule(String, ComponentName, ComponentName, Uri,
- * ZenPolicy, int, boolean)}.
+ *
+ * @deprecated Use {@link AutomaticZenRule.Builder} to construct an {@link AutomaticZenRule}.
*/
@Deprecated
public AutomaticZenRule(String name, ComponentName owner, Uri conditionId,
@@ -206,8 +206,10 @@
* while this rule is active. This overrides the global policy while this rule is
* action ({@link Condition#STATE_TRUE}).
* @param enabled Whether the rule is enabled.
+ *
+ * @deprecated Use {@link AutomaticZenRule.Builder} to construct an {@link AutomaticZenRule}.
*/
- // TODO (b/309088420): deprecate this constructor in favor of the builder
+ @Deprecated
public AutomaticZenRule(@NonNull String name, @Nullable ComponentName owner,
@Nullable ComponentName configurationActivity, @NonNull Uri conditionId,
@Nullable ZenPolicy policy, int interruptionFilter, boolean enabled) {
@@ -368,6 +370,9 @@
/**
* Sets the zen policy.
+ *
+ * <p>When updating an existing rule via {@link NotificationManager#updateAutomaticZenRule},
+ * a {@code null} value here means the previous policy is retained.
*/
public void setZenPolicy(@Nullable ZenPolicy zenPolicy) {
this.mZenPolicy = (zenPolicy == null ? null : zenPolicy.copy());
@@ -390,7 +395,12 @@
* Sets the configuration activity - an activity that handles
* {@link NotificationManager#ACTION_AUTOMATIC_ZEN_RULE} that shows the user more information
* about this rule and/or allows them to configure it. This is required to be non-null for rules
- * that are not backed by {@link android.service.notification.ConditionProviderService}.
+ * that are not backed by a {@link android.service.notification.ConditionProviderService}.
+ *
+ * <p>This is exclusive with the {@code owner} supplied in the constructor; rules where a
+ * configuration activity is set will not use the
+ * {@link android.service.notification.ConditionProviderService} supplied there to determine
+ * whether the rule should be active.
*/
public void setConfigurationActivity(@Nullable ComponentName componentName) {
this.configurationActivity = getTrimmedComponentName(componentName);
diff --git a/core/java/android/app/GrammaticalInflectionManager.java b/core/java/android/app/GrammaticalInflectionManager.java
index 483a6e1..4ce983f 100644
--- a/core/java/android/app/GrammaticalInflectionManager.java
+++ b/core/java/android/app/GrammaticalInflectionManager.java
@@ -16,8 +16,10 @@
package android.app;
+import android.Manifest;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.content.Context;
import android.content.res.Configuration;
@@ -127,6 +129,7 @@
*
* @see Configuration#getGrammaticalGender
*/
+ @RequiresPermission(Manifest.permission.READ_SYSTEM_GRAMMATICAL_GENDER)
@FlaggedApi(Flags.FLAG_SYSTEM_TERMS_OF_ADDRESS_ENABLED)
@Configuration.GrammaticalGender
public int getSystemGrammaticalGender() {
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index d49a254..b82a1e3 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -270,13 +270,16 @@
* Integer extra for {@link #ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED} containing the state of
* the {@link AutomaticZenRule}.
*
- * <p>
- * The value will be one of {@link #AUTOMATIC_RULE_STATUS_ENABLED},
- * {@link #AUTOMATIC_RULE_STATUS_DISABLED}, {@link #AUTOMATIC_RULE_STATUS_REMOVED},
- * {@link #AUTOMATIC_RULE_STATUS_UNKNOWN}.
- * </p>
+ * <p>The value will be one of {@link #AUTOMATIC_RULE_STATUS_ENABLED},
+ * {@link #AUTOMATIC_RULE_STATUS_DISABLED}, {@link #AUTOMATIC_RULE_STATUS_REMOVED},
+ * {@link #AUTOMATIC_RULE_STATUS_ACTIVATED}, {@link #AUTOMATIC_RULE_STATUS_DEACTIVATED}, or
+ * {@link #AUTOMATIC_RULE_STATUS_UNKNOWN}.
+ *
+ * <p>Note that the {@link #AUTOMATIC_RULE_STATUS_ACTIVATED} and
+ * {@link #AUTOMATIC_RULE_STATUS_DEACTIVATED} statuses are only sent to packages targeting
+ * {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above; apps targeting a lower SDK version
+ * will be sent {@link #AUTOMATIC_RULE_STATUS_UNKNOWN} in their place instead.
*/
- // TODO (b/309101513): Add new status types to javadoc
public static final String EXTRA_AUTOMATIC_ZEN_RULE_STATUS =
"android.app.extra.AUTOMATIC_ZEN_RULE_STATUS";
@@ -370,11 +373,15 @@
= "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED";
/**
- * Intent that is broadcast when the state of getNotificationPolicy() changes.
+ * Intent that is broadcast when the state of {@link #getNotificationPolicy()} changes.
*
* <p>This broadcast is only sent to registered receivers and (starting from
* {@link Build.VERSION_CODES#Q}) receivers in packages that have been granted Do Not
* Disturb access (see {@link #isNotificationPolicyAccessGranted()}).
+ *
+ * <p>Starting with {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, most calls to
+ * {@link #setNotificationPolicy(Policy)} will update the app's implicit rule policy instead of
+ * the global policy, so this broadcast will be sent much less frequently.
*/
@SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_NOTIFICATION_POLICY_CHANGED
@@ -1378,12 +1385,16 @@
/**
* Updates the given zen rule.
*
- * <p>
- * Throws a SecurityException if policy access is not granted to this package.
+ * <p>Before {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, updating a rule that is not backed
+ * up by a {@link android.service.notification.ConditionProviderService} will deactivate it if
+ * it was previously active. Starting with {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, this
+ * will only happen if the rule's definition is actually changing.
+ *
+ * <p>Throws a SecurityException if policy access is not granted to this package.
* See {@link #isNotificationPolicyAccessGranted}.
*
- * <p>
- * Callers can only update rules that they own. See {@link AutomaticZenRule#getOwner}.
+ * <p>Callers can only update rules that they own. See {@link AutomaticZenRule#getOwner}.
+ *
* @param id The id of the rule to update
* @param automaticZenRule the rule to update.
* @return Whether the rule was successfully updated.
@@ -1744,9 +1755,11 @@
/**
* Gets the current user-specified default notification policy.
*
- * <p>
+ * <p>For apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above (with some
+ * exceptions, such as companion device managers) this method will return the policy associated
+ * to their implicit {@link AutomaticZenRule} instead, if it exists. See
+ * {@link #setNotificationPolicy(Policy)}.
*/
- // TODO(b/309457271): Update documentation with VANILLA_ICE_CREAM behavior.
public Policy getNotificationPolicy() {
INotificationManager service = getService();
try {
@@ -1757,15 +1770,20 @@
}
/**
- * Sets the current notification policy.
+ * Sets the current notification policy (which applies when {@link #setInterruptionFilter} is
+ * called with the {@link #INTERRUPTION_FILTER_PRIORITY} value).
*
- * <p>
- * Only available if policy access is granted to this package.
- * See {@link #isNotificationPolicyAccessGranted}.
+ * <p>Apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above (with some
+ * exceptions, such as companion device managers) cannot modify the global notification policy.
+ * Calling this method will instead create or update an {@link AutomaticZenRule} associated to
+ * the app, using a {@link ZenPolicy} corresponding to the {@link Policy} supplied here, and
+ * which will be activated/deactivated by calls to {@link #setInterruptionFilter(int)}.
+ *
+ * <p>Only available if policy access is granted to this package. See
+ * {@link #isNotificationPolicyAccessGranted}.
*
* @param policy The new desired policy.
*/
- // TODO(b/309457271): Update documentation with VANILLA_ICE_CREAM behavior.
public void setNotificationPolicy(@NonNull Policy policy) {
setNotificationPolicy(policy, /* fromUser= */ false);
}
@@ -2052,10 +2070,12 @@
/** Notification senders to prioritize for calls. One of:
* PRIORITY_SENDERS_ANY, PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED */
+ @PrioritySenders
public final int priorityCallSenders;
/** Notification senders to prioritize for messages. One of:
* PRIORITY_SENDERS_ANY, PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED */
+ @PrioritySenders
public final int priorityMessageSenders;
/**
@@ -2063,6 +2083,7 @@
* {@link #CONVERSATION_SENDERS_NONE}, {@link #CONVERSATION_SENDERS_IMPORTANT},
* {@link #CONVERSATION_SENDERS_ANYONE}.
*/
+ @ConversationSenders
public final int priorityConversationSenders;
/**
@@ -2630,16 +2651,19 @@
}
/** @hide **/
+ @PrioritySenders
public int allowCallsFrom() {
return priorityCallSenders;
}
/** @hide **/
+ @PrioritySenders
public int allowMessagesFrom() {
return priorityMessageSenders;
}
/** @hide **/
+ @ConversationSenders
public int allowConversationsFrom() {
return priorityConversationSenders;
}
@@ -2780,11 +2804,17 @@
* The interruption filter defines which notifications are allowed to
* interrupt the user (e.g. via sound & vibration) and is applied
* globally.
- * <p>
- * Only available if policy access is granted to this package. See
+ *
+ * <p>Apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above (with some
+ * exceptions, such as companion device managers) cannot modify the global interruption filter.
+ * Calling this method will instead activate or deactivate an {@link AutomaticZenRule}
+ * associated to the app, using a {@link ZenPolicy} that corresponds to the {@link Policy}
+ * supplied to {@link #setNotificationPolicy(Policy)} (or the global policy when one wasn't
+ * provided).
+ *
+ * <p> Only available if policy access is granted to this package. See
* {@link #isNotificationPolicyAccessGranted}.
*/
- // TODO(b/309457271): Update documentation with VANILLA_ICE_CREAM behavior.
public final void setInterruptionFilter(@InterruptionFilter int interruptionFilter) {
setInterruptionFilter(interruptionFilter, /* fromUser= */ false);
}
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 6255260..8b84f06 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -124,6 +124,32 @@
*/
private LocaleConfig mLocaleConfig = new LocaleConfig(LocaleList.getEmptyLocaleList());
+ private final ArrayMap<String, SharedLibraryAssets> mSharedLibAssetsMap =
+ new ArrayMap<>();
+
+ /**
+ * The internal function to register the resources paths of a package (e.g. a shared library).
+ * This will collect the package resources' paths from its ApplicationInfo and add them to all
+ * existing and future contexts while the application is running.
+ */
+ public void registerResourcePaths(@NonNull String uniqueId, @NonNull ApplicationInfo appInfo) {
+ SharedLibraryAssets sharedLibAssets = new SharedLibraryAssets(appInfo.sourceDir,
+ appInfo.splitSourceDirs, appInfo.sharedLibraryFiles,
+ appInfo.resourceDirs, appInfo.overlayPaths);
+
+ synchronized (mLock) {
+ if (mSharedLibAssetsMap.containsKey(uniqueId)) {
+ Slog.v(TAG, "Package resources' paths for uniqueId: " + uniqueId
+ + " has already been registered, this is a no-op.");
+ return;
+ }
+ mSharedLibAssetsMap.put(uniqueId, sharedLibAssets);
+ appendLibAssetsLocked(sharedLibAssets.getAllAssetPaths());
+ Slog.v(TAG, "The following resources' paths have been added: "
+ + Arrays.toString(sharedLibAssets.getAllAssetPaths()));
+ }
+ }
+
private static class ApkKey {
public final String path;
public final boolean sharedLib;
@@ -278,6 +304,21 @@
public ResourcesManager() {
}
+ /**
+ * Inject a customized ResourcesManager instance for testing, return the old ResourcesManager
+ * instance.
+ */
+ @UnsupportedAppUsage
+ @VisibleForTesting
+ public static ResourcesManager setInstance(ResourcesManager resourcesManager) {
+ synchronized (ResourcesManager.class) {
+ ResourcesManager oldResourceManager = sResourcesManager;
+ sResourcesManager = resourcesManager;
+ return oldResourceManager;
+ }
+
+ }
+
@UnsupportedAppUsage
public static ResourcesManager getInstance() {
synchronized (ResourcesManager.class) {
@@ -1480,6 +1521,56 @@
}
}
+ private void appendLibAssetsLocked(String[] libAssets) {
+ synchronized (mLock) {
+ // Record which ResourcesImpl need updating
+ // (and what ResourcesKey they should update to).
+ final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>();
+
+ final int implCount = mResourceImpls.size();
+ for (int i = 0; i < implCount; i++) {
+ final ResourcesKey key = mResourceImpls.keyAt(i);
+ final WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
+ final ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
+ if (impl == null) {
+ Slog.w(TAG, "Found a ResourcesImpl which is null, skip it and continue to "
+ + "append shared library assets for next ResourcesImpl.");
+ continue;
+ }
+
+ var newDirs = new ArrayList<String>();
+ var dirsSet = new ArraySet<String>();
+ if (key.mLibDirs != null) {
+ final int dirsLength = key.mLibDirs.length;
+ for (int k = 0; k < dirsLength; k++) {
+ newDirs.add(key.mLibDirs[k]);
+ dirsSet.add(key.mLibDirs[k]);
+ }
+ }
+ final int assetsLength = libAssets.length;
+ for (int j = 0; j < assetsLength; j++) {
+ if (dirsSet.add(libAssets[j])) {
+ newDirs.add(libAssets[j]);
+ }
+ }
+ String[] newLibAssets = newDirs.toArray(new String[0]);
+ if (!Arrays.equals(newLibAssets, key.mLibDirs)) {
+ updatedResourceKeys.put(impl, new ResourcesKey(
+ key.mResDir,
+ key.mSplitResDirs,
+ key.mOverlayPaths,
+ newLibAssets,
+ key.mDisplayId,
+ key.mOverrideConfiguration,
+ key.mCompatInfo,
+ key.mLoaders));
+ }
+ }
+
+ redirectResourcesToNewImplLocked(updatedResourceKeys);
+ }
+ }
+
private void applyNewResourceDirsLocked(@Nullable final String[] oldSourceDirs,
@NonNull final ApplicationInfo appInfo) {
try {
@@ -1689,4 +1780,50 @@
}
}
}
+
+ public static class SharedLibraryAssets{
+ private final String[] mAssetPaths;
+
+ SharedLibraryAssets(String sourceDir, String[] splitSourceDirs, String[] sharedLibraryFiles,
+ String[] resourceDirs, String[] overlayPaths) {
+ mAssetPaths = collectAssetPaths(sourceDir, splitSourceDirs, sharedLibraryFiles,
+ resourceDirs, overlayPaths);
+ }
+
+ private @NonNull String[] collectAssetPaths(String sourceDir, String[] splitSourceDirs,
+ String[] sharedLibraryFiles, String[] resourceDirs, String[] overlayPaths) {
+ final String[][] inputLists = {
+ splitSourceDirs, sharedLibraryFiles, resourceDirs, overlayPaths
+ };
+
+ final ArraySet<String> assetPathSet = new ArraySet<>();
+ final List<String> assetPathList = new ArrayList<>();
+ if (sourceDir != null) {
+ assetPathSet.add(sourceDir);
+ assetPathList.add(sourceDir);
+ }
+
+ for (int i = 0; i < inputLists.length; i++) {
+ if (inputLists[i] != null) {
+ for (int j = 0; j < inputLists[i].length; j++) {
+ if (assetPathSet.add(inputLists[i][j])) {
+ assetPathList.add(inputLists[i][j]);
+ }
+ }
+ }
+ }
+ return assetPathList.toArray(new String[0]);
+ }
+
+ /**
+ * @return all the asset paths of this collected in this class.
+ */
+ public @NonNull String[] getAllAssetPaths() {
+ return mAssetPaths;
+ }
+ }
+
+ public @NonNull ArrayMap<String, SharedLibraryAssets> getSharedLibAssetsMap() {
+ return new ArrayMap<>(mSharedLibAssetsMap);
+ }
}
diff --git a/core/java/android/app/admin/BundlePolicyValue.java b/core/java/android/app/admin/BundlePolicyValue.java
index 4f70604..cb5e986 100644
--- a/core/java/android/app/admin/BundlePolicyValue.java
+++ b/core/java/android/app/admin/BundlePolicyValue.java
@@ -32,7 +32,7 @@
public BundlePolicyValue(Bundle value) {
super(value);
if (Flags.devicePolicySizeTrackingInternalEnabled()) {
- PolicySizeVerifier.enforceMaxParcelableFieldsLength(value);
+ PolicySizeVerifier.enforceMaxBundleFieldsLength(value);
}
}
diff --git a/core/java/android/app/admin/IntentFilterPolicyKey.java b/core/java/android/app/admin/IntentFilterPolicyKey.java
index 63c3a4cb..7526a7b 100644
--- a/core/java/android/app/admin/IntentFilterPolicyKey.java
+++ b/core/java/android/app/admin/IntentFilterPolicyKey.java
@@ -24,7 +24,6 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.app.admin.flags.Flags;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Parcel;
@@ -60,9 +59,6 @@
@TestApi
public IntentFilterPolicyKey(@NonNull String identifier, @NonNull IntentFilter filter) {
super(identifier);
- if (Flags.devicePolicySizeTrackingInternalEnabled()) {
- PolicySizeVerifier.enforceMaxParcelableFieldsLength(filter);
- }
mFilter = Objects.requireNonNull(filter);
}
diff --git a/core/java/android/app/admin/PolicySizeVerifier.java b/core/java/android/app/admin/PolicySizeVerifier.java
index 792ebc6..7f8e50e 100644
--- a/core/java/android/app/admin/PolicySizeVerifier.java
+++ b/core/java/android/app/admin/PolicySizeVerifier.java
@@ -17,12 +17,12 @@
package android.app.admin;
import android.content.ComponentName;
+import android.os.Bundle;
import android.os.Parcelable;
import android.os.PersistableBundle;
import com.android.internal.util.Preconditions;
-import java.lang.reflect.Field;
import java.util.ArrayDeque;
import java.util.Queue;
@@ -71,44 +71,51 @@
for (String key : current.keySet()) {
enforceMaxStringLength(key, "key in " + argName);
Object value = current.get(key);
- if (value instanceof String) {
- enforceMaxStringLength((String) value, "string value in " + argName);
- } else if (value instanceof String[]) {
- for (String str : (String[]) value) {
+ if (value instanceof String str) {
+ enforceMaxStringLength(str, "string value in " + argName);
+ } else if (value instanceof String[] strArray) {
+ for (String str : strArray) {
enforceMaxStringLength(str, "string value in " + argName);
}
- } else if (value instanceof PersistableBundle) {
- queue.add((PersistableBundle) value);
+ } else if (value instanceof PersistableBundle persistableBundle) {
+ queue.add(persistableBundle);
}
}
}
}
/**
- * Throw if Parcelable contains any string that's too long to be serialized.
+ * Throw if bundle contains any string that's too long to be serialized. This follows the
+ * serialization logic in BundlePolicySerializer#writeBundle.
*/
- public static void enforceMaxParcelableFieldsLength(Parcelable parcelable) {
- // TODO(b/326662716) rework to protect against infinite recursion.
- if (true) {
- return;
- }
- Class<?> clazz = parcelable.getClass();
-
- Field[] fields = clazz.getDeclaredFields();
- for (Field field : fields) {
- field.setAccessible(true);
- try {
- Object value = field.get(parcelable);
- if (value instanceof String) {
- String stringValue = (String) value;
- enforceMaxStringLength(stringValue, field.getName());
+ public static void enforceMaxBundleFieldsLength(Bundle bundle) {
+ Queue<Bundle> queue = new ArrayDeque<>();
+ queue.add(bundle);
+ while (!queue.isEmpty()) {
+ Bundle current = queue.remove();
+ for (String key : current.keySet()) {
+ enforceMaxStringLength(key, "key in Bundle");
+ Object value = current.get(key);
+ if (value instanceof String str) {
+ enforceMaxStringLength(str, "string value in Bundle with "
+ + "key" + key);
+ } else if (value instanceof String[] strArray) {
+ for (String str : strArray) {
+ enforceMaxStringLength(str, "string value in Bundle with"
+ + " key" + key);
+ }
+ } else if (value instanceof Bundle b) {
+ queue.add(b);
}
-
- if (value instanceof Parcelable) {
- enforceMaxParcelableFieldsLength((Parcelable) value);
+ else if (value instanceof Parcelable[] parcelableArray) {
+ for (Parcelable parcelable : parcelableArray) {
+ if (!(parcelable instanceof Bundle)) {
+ throw new IllegalArgumentException("bundle-array can only hold "
+ + "Bundles");
+ }
+ queue.add((Bundle) parcelable);
+ }
}
- } catch (IllegalAccessException e) {
- e.printStackTrace();
}
}
}
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index cdda12e..3f941da 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -273,6 +273,10 @@
* to the <code>retailDemo</code> value of
* {@link android.R.attr#protectionLevel}.
*
+ * @deprecated This flag has been replaced by the
+ * {@link android.R.string#config_defaultRetailDemo retail demo role} and is a
+ * no-op since {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}.
+ *
* @hide
*/
@SystemApi
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index d259e97..273e40a 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -471,6 +471,16 @@
return addAssetPathInternal(path, true /*overlay*/, false /*appAsLib*/);
}
+ /**
+ * @hide
+ */
+ public void addSharedLibraryPaths(@NonNull String[] paths) {
+ final int length = paths.length;
+ for (int i = 0; i < length; i++) {
+ addAssetPathInternal(paths[i], false, true);
+ }
+ }
+
private int addAssetPathInternal(String path, boolean overlay, boolean appAsLib) {
Objects.requireNonNull(path, "path");
synchronized (this) {
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 7fba3e8..1f5f88f 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -43,6 +43,7 @@
import android.annotation.StyleableRes;
import android.annotation.XmlRes;
import android.app.Application;
+import android.app.ResourcesManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.ActivityInfo;
@@ -2854,6 +2855,11 @@
@FlaggedApi(android.content.res.Flags.FLAG_REGISTER_RESOURCE_PATHS)
public static void registerResourcePaths(@NonNull String uniqueId,
@NonNull ApplicationInfo appInfo) {
- throw new UnsupportedOperationException("The implementation has not been done yet.");
+ if (Flags.registerResourcePaths()) {
+ ResourcesManager.getInstance().registerResourcePaths(uniqueId, appInfo);
+ } else {
+ throw new UnsupportedOperationException("Flag " + Flags.FLAG_REGISTER_RESOURCE_PATHS
+ + " is disabled.");
+ }
}
}
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 079c2c1..8d045aa 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -29,6 +29,7 @@
import android.annotation.StyleableRes;
import android.app.LocaleConfig;
import android.app.ResourcesManager;
+import android.app.ResourcesManager.SharedLibraryAssets;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo.Config;
@@ -47,6 +48,7 @@
import android.os.LocaleList;
import android.os.ParcelFileDescriptor;
import android.os.Trace;
+import android.util.ArrayMap;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -197,6 +199,14 @@
public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics,
@Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments) {
mAssets = assets;
+ if (Flags.registerResourcePaths()) {
+ ArrayMap<String, SharedLibraryAssets> sharedLibMap =
+ ResourcesManager.getInstance().getSharedLibAssetsMap();
+ final int size = sharedLibMap.size();
+ for (int i = 0; i < size; i++) {
+ assets.addSharedLibraryPaths(sharedLibMap.valueAt(i).getAllAssetPaths());
+ }
+ }
mMetrics.setToDefaults();
mDisplayAdjustments = displayAdjustments;
mConfiguration.setToDefaults();
diff --git a/core/java/android/hardware/ISensorPrivacyManager.aidl b/core/java/android/hardware/ISensorPrivacyManager.aidl
index 851ce2a..19d1029 100644
--- a/core/java/android/hardware/ISensorPrivacyManager.aidl
+++ b/core/java/android/hardware/ISensorPrivacyManager.aidl
@@ -16,7 +16,6 @@
package android.hardware;
-import android.hardware.CameraPrivacyAllowlistEntry;
import android.hardware.ISensorPrivacyListener;
/** @hide */
@@ -48,7 +47,7 @@
void setToggleSensorPrivacyForProfileGroup(int userId, int source, int sensor, boolean enable);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY)")
- List<CameraPrivacyAllowlistEntry> getCameraPrivacyAllowlist();
+ List<String> getCameraPrivacyAllowlist();
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY)")
int getToggleSensorPrivacyState(int toggleType, int sensor);
@@ -62,6 +61,10 @@
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY)")
boolean isCameraPrivacyEnabled(String packageName);
+ /** @hide */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY)")
+ void setCameraPrivacyAllowlist(in List<String> allowlist);
+
// =============== End of transactions used on native side as well ============================
void suppressToggleSensorPrivacyReminders(int userId, int sensor, IBinder token,
diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java
index 6294a8d..08b9064 100644
--- a/core/java/android/hardware/SensorPrivacyManager.java
+++ b/core/java/android/hardware/SensorPrivacyManager.java
@@ -43,7 +43,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.Map;
+import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -204,6 +204,8 @@
* Types of state which can exist for the sensor privacy toggle
* @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
public static class StateTypes {
private StateTypes() {}
@@ -217,30 +219,12 @@
*/
public static final int DISABLED = SensorPrivacyIndividualEnabledSensorProto.DISABLED;
- /**
- * Constant indicating privacy is enabled except for the automotive driver assistance apps
- * which are helpful for driving.
- */
- @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
- public static final int AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS =
- SensorPrivacyIndividualEnabledSensorProto.AUTO_DRIVER_ASSISTANCE_HELPFUL_APPS;
-
/**
* Constant indicating privacy is enabled except for the automotive driver assistance apps
* which are required by car manufacturer for driving.
*/
- @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
- public static final int AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS =
- SensorPrivacyIndividualEnabledSensorProto.AUTO_DRIVER_ASSISTANCE_REQUIRED_APPS;
-
- /**
- * Constant indicating privacy is enabled except for the automotive driver assistance apps
- * which are both helpful for driving and also apps required by car manufacturer for
- * driving.
- */
- @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
- public static final int AUTOMOTIVE_DRIVER_ASSISTANCE_APPS =
- SensorPrivacyIndividualEnabledSensorProto.AUTO_DRIVER_ASSISTANCE_APPS;
+ public static final int ENABLED_EXCEPT_ALLOWLISTED_APPS =
+ SensorPrivacyIndividualEnabledSensorProto.ENABLED_EXCEPT_ALLOWLISTED_APPS;
/**
* Types of state which can exist for a sensor privacy toggle
@@ -250,9 +234,7 @@
@IntDef(value = {
ENABLED,
DISABLED,
- AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS,
- AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS,
- AUTOMOTIVE_DRIVER_ASSISTANCE_APPS
+ ENABLED_EXCEPT_ALLOWLISTED_APPS
})
@Retention(RetentionPolicy.SOURCE)
public @interface StateType {}
@@ -369,9 +351,6 @@
private final ArrayMap<Pair<Integer, OnSensorPrivacyChangedListener>,
OnSensorPrivacyChangedListener> mLegacyToggleListeners = new ArrayMap<>();
- @GuardedBy("mLock")
- private ArrayMap<String, Boolean> mCameraPrivacyAllowlist = null;
-
/** The singleton ISensorPrivacyListener for IPC which will be used to dispatch to local
* listeners */
@NonNull
@@ -397,7 +376,8 @@
@Override
@FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
- public void onSensorPrivacyStateChanged(int toggleType, int sensor, int state) {
+ public void onSensorPrivacyStateChanged(@ToggleType int toggleType,
+ @Sensors.Sensor int sensor, @StateTypes.StateType int state) {
synchronized (mLock) {
for (int i = 0; i < mToggleListeners.size(); i++) {
OnSensorPrivacyChangedListener listener = mToggleListeners.keyAt(i);
@@ -725,6 +705,8 @@
/**
* Returns sensor privacy state for a specific sensor.
*
+ * @param toggleType The type of toggle to use
+ * @param sensor The sensor to check
* @return int sensor privacy state.
*
* @hide
@@ -741,9 +723,10 @@
}
}
- /**
+ /**
* Returns if camera privacy is enabled for a specific package.
*
+ * @param packageName The package to check
* @return boolean sensor privacy state.
*
* @hide
@@ -763,29 +746,41 @@
* Returns camera privacy allowlist.
*
* @return List of automotive driver assistance packages for
- * privacy allowlisting. The returned map includes the package
- * name as key and the value is a Boolean which tells if that package
- * is required by the car manufacturer as mandatory package for driving.
+ * privacy allowlisting.
*
* @hide
*/
@SystemApi
@RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
@FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
- public @NonNull Map<String, Boolean> getCameraPrivacyAllowlist() {
+ public @NonNull List<String> getCameraPrivacyAllowlist() {
synchronized (mLock) {
- if (mCameraPrivacyAllowlist == null) {
- mCameraPrivacyAllowlist = new ArrayMap<>();
- try {
- for (CameraPrivacyAllowlistEntry entry :
- mService.getCameraPrivacyAllowlist()) {
- mCameraPrivacyAllowlist.put(entry.packageName, entry.isMandatory);
- }
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ try {
+ return mService.getCameraPrivacyAllowlist();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
- return mCameraPrivacyAllowlist;
+ }
+ }
+
+ /**
+ * Sets camera privacy allowlist.
+ *
+ * @param allowlist List of automotive driver assistance packages for
+ * privacy allowlisting.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY)
+ @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
+ public void setCameraPrivacyAllowlist(@NonNull List<String> allowlist) {
+ synchronized (mLock) {
+ try {
+ mService.setCameraPrivacyAllowlist(allowlist);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
}
@@ -867,6 +862,7 @@
/**
* Sets sensor privacy to the specified state for an individual sensor.
*
+ * @param source the source using which the sensor is toggled
* @param sensor the sensor which to change the state for
* @param state the state to which sensor privacy should be set.
*
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index de7008b..dc782d4 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -138,3 +138,11 @@
bug: "325356776"
}
+flag {
+ name: "runtime_permission_appops_mapping_enabled"
+ is_fixed_read_only: true
+ namespace: "permissions"
+ description: "Use runtime permission state to determine appop state"
+ bug: "266164193"
+}
+
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index c03dc71..120846c 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -667,7 +667,8 @@
@FlaggedApi(Flags.FLAG_BUSINESS_CALL_COMPOSER)
public @NonNull AddCallParametersBuilder setAssertedDisplayName(
String assertedDisplayName) {
- if (assertedDisplayName.length() > MAX_NUMBER_OF_CHARACTERS) {
+ if (assertedDisplayName != null
+ && assertedDisplayName.length() > MAX_NUMBER_OF_CHARACTERS) {
throw new IllegalArgumentException("assertedDisplayName exceeds the character"
+ " limit of " + MAX_NUMBER_OF_CHARACTERS + ".");
}
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 7658af5..bd9ab86 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -22,6 +22,7 @@
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
+import android.annotation.UiThread;
import android.app.ActivityManager;
import android.app.INotificationManager;
import android.app.Notification;
@@ -468,6 +469,7 @@
* object as well as its identifying information (tag and id) and source
* (package name).
*/
+ @UiThread
public void onNotificationPosted(StatusBarNotification sbn) {
// optional
}
@@ -481,6 +483,7 @@
* @param rankingMap The current ranking map that can be used to retrieve ranking information
* for active notifications, including the newly posted one.
*/
+ @UiThread
public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
onNotificationPosted(sbn);
}
@@ -499,6 +502,7 @@
* and source (package name) used to post the {@link android.app.Notification} that
* was just removed.
*/
+ @UiThread
public void onNotificationRemoved(StatusBarNotification sbn) {
// optional
}
@@ -520,6 +524,7 @@
* for active notifications.
*
*/
+ @UiThread
public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
onNotificationRemoved(sbn);
}
@@ -541,6 +546,7 @@
* @param rankingMap The current ranking map that can be used to retrieve ranking information
* for active notifications.
*/
+ @UiThread
public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
@NotificationCancelReason int reason) {
onNotificationRemoved(sbn, rankingMap);
@@ -552,6 +558,7 @@
*
* @hide
*/
+ @UiThread
@SystemApi
public void onNotificationRemoved(@NonNull StatusBarNotification sbn,
@NonNull RankingMap rankingMap, @NonNull NotificationStats stats, int reason) {
@@ -563,6 +570,7 @@
* the notification manager. You are safe to call {@link #getActiveNotifications()}
* at this time.
*/
+ @UiThread
public void onListenerConnected() {
// optional
}
@@ -572,6 +580,7 @@
* notification manager.You will not receive any events after this call, and may only
* call {@link #requestRebind(ComponentName)} at this time.
*/
+ @UiThread
public void onListenerDisconnected() {
// optional
}
@@ -582,6 +591,7 @@
* @param rankingMap The current ranking map that can be used to retrieve ranking information
* for active notifications.
*/
+ @UiThread
public void onNotificationRankingUpdate(RankingMap rankingMap) {
// optional
}
@@ -592,6 +602,7 @@
*
* @param hints The current {@link #getCurrentListenerHints() listener hints}.
*/
+ @UiThread
public void onListenerHintsChanged(int hints) {
// optional
}
@@ -603,6 +614,7 @@
* @param hideSilentStatusIcons whether or not status bar icons should be hidden for silent
* notifications
*/
+ @UiThread
public void onSilentStatusBarIconsVisibilityChanged(boolean hideSilentStatusIcons) {
// optional
}
@@ -620,6 +632,7 @@
* {@link #NOTIFICATION_CHANNEL_OR_GROUP_UPDATED},
* {@link #NOTIFICATION_CHANNEL_OR_GROUP_DELETED}.
*/
+ @UiThread
public void onNotificationChannelModified(String pkg, UserHandle user,
NotificationChannel channel, @ChannelOrGroupModificationTypes int modificationType) {
// optional
@@ -638,6 +651,7 @@
* {@link #NOTIFICATION_CHANNEL_OR_GROUP_UPDATED},
* {@link #NOTIFICATION_CHANNEL_OR_GROUP_DELETED}.
*/
+ @UiThread
public void onNotificationChannelGroupModified(String pkg, UserHandle user,
NotificationChannelGroup group, @ChannelOrGroupModificationTypes int modificationType) {
// optional
@@ -650,6 +664,7 @@
* @param interruptionFilter The current
* {@link #getCurrentInterruptionFilter() interruption filter}.
*/
+ @UiThread
public void onInterruptionFilterChanged(int interruptionFilter) {
// optional
}
@@ -1197,6 +1212,11 @@
* <p>
* Listen for updates using {@link #onInterruptionFilterChanged(int)}.
*
+ * <p>Apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above (with some
+ * exceptions, such as companion device managers) cannot modify the global interruption filter.
+ * Calling this method will instead activate or deactivate an
+ * {@link android.app.AutomaticZenRule} associated to the app.
+ *
* <p>The service should wait for the {@link #onListenerConnected()} event
* before performing this operation.
*
diff --git a/core/java/android/service/notification/ZenAdapters.java b/core/java/android/service/notification/ZenAdapters.java
new file mode 100644
index 0000000..b249815
--- /dev/null
+++ b/core/java/android/service/notification/ZenAdapters.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.notification;
+
+import android.annotation.NonNull;
+import android.app.Flags;
+import android.app.NotificationManager.Policy;
+
+/**
+ * Converters between different Zen representations.
+ * @hide
+ */
+public class ZenAdapters {
+
+ /** Maps {@link Policy} to {@link ZenPolicy}. */
+ @NonNull
+ public static ZenPolicy notificationPolicyToZenPolicy(@NonNull Policy policy) {
+ ZenPolicy.Builder zenPolicyBuilder = new ZenPolicy.Builder()
+ .allowAlarms(policy.allowAlarms())
+ .allowCalls(
+ policy.allowCalls()
+ ? notificationPolicySendersToZenPolicyPeopleType(
+ policy.allowCallsFrom())
+ : ZenPolicy.PEOPLE_TYPE_NONE)
+ .allowConversations(
+ policy.allowConversations()
+ ? notificationPolicyConversationSendersToZenPolicy(
+ policy.allowConversationsFrom())
+ : ZenPolicy.CONVERSATION_SENDERS_NONE)
+ .allowEvents(policy.allowEvents())
+ .allowMedia(policy.allowMedia())
+ .allowMessages(
+ policy.allowMessages()
+ ? notificationPolicySendersToZenPolicyPeopleType(
+ policy.allowMessagesFrom())
+ : ZenPolicy.PEOPLE_TYPE_NONE)
+ .allowReminders(policy.allowReminders())
+ .allowRepeatCallers(policy.allowRepeatCallers())
+ .allowSystem(policy.allowSystem());
+
+ if (policy.suppressedVisualEffects != Policy.SUPPRESSED_EFFECTS_UNSET) {
+ zenPolicyBuilder.showBadges(policy.showBadges())
+ .showFullScreenIntent(policy.showFullScreenIntents())
+ .showInAmbientDisplay(policy.showAmbient())
+ .showInNotificationList(policy.showInNotificationList())
+ .showLights(policy.showLights())
+ .showPeeking(policy.showPeeking())
+ .showStatusBarIcons(policy.showStatusBarIcons());
+ }
+
+ if (Flags.modesApi()) {
+ zenPolicyBuilder.allowPriorityChannels(policy.allowPriorityChannels());
+ }
+
+ return zenPolicyBuilder.build();
+ }
+
+ /** Maps {@link ZenPolicy.PeopleType} enum to {@link Policy.PrioritySenders}. */
+ @Policy.PrioritySenders
+ public static int zenPolicyPeopleTypeToNotificationPolicySenders(
+ @ZenPolicy.PeopleType int zpPeopleType, @Policy.PrioritySenders int defaultResult) {
+ switch (zpPeopleType) {
+ case ZenPolicy.PEOPLE_TYPE_ANYONE:
+ return Policy.PRIORITY_SENDERS_ANY;
+ case ZenPolicy.PEOPLE_TYPE_CONTACTS:
+ return Policy.PRIORITY_SENDERS_CONTACTS;
+ case ZenPolicy.PEOPLE_TYPE_STARRED:
+ return Policy.PRIORITY_SENDERS_STARRED;
+ default:
+ return defaultResult;
+ }
+ }
+
+ /** Maps {@link Policy.PrioritySenders} enum to {@link ZenPolicy.PeopleType}. */
+ @ZenPolicy.PeopleType
+ public static int notificationPolicySendersToZenPolicyPeopleType(
+ @Policy.PrioritySenders int npPrioritySenders) {
+ switch (npPrioritySenders) {
+ case Policy.PRIORITY_SENDERS_ANY:
+ return ZenPolicy.PEOPLE_TYPE_ANYONE;
+ case Policy.PRIORITY_SENDERS_CONTACTS:
+ return ZenPolicy.PEOPLE_TYPE_CONTACTS;
+ case Policy.PRIORITY_SENDERS_STARRED:
+ default:
+ return ZenPolicy.PEOPLE_TYPE_STARRED;
+ }
+ }
+
+ /** Maps {@link ZenPolicy.ConversationSenders} enum to {@link Policy.ConversationSenders}. */
+ @Policy.ConversationSenders
+ public static int zenPolicyConversationSendersToNotificationPolicy(
+ @ZenPolicy.ConversationSenders int zpConversationSenders,
+ @Policy.ConversationSenders int defaultResult) {
+ switch (zpConversationSenders) {
+ case ZenPolicy.CONVERSATION_SENDERS_ANYONE:
+ return Policy.CONVERSATION_SENDERS_ANYONE;
+ case ZenPolicy.CONVERSATION_SENDERS_IMPORTANT:
+ return Policy.CONVERSATION_SENDERS_IMPORTANT;
+ case ZenPolicy.CONVERSATION_SENDERS_NONE:
+ return Policy.CONVERSATION_SENDERS_NONE;
+ default:
+ return defaultResult;
+ }
+ }
+
+ /** Maps {@link Policy.ConversationSenders} enum to {@link ZenPolicy.ConversationSenders}. */
+ @ZenPolicy.ConversationSenders
+ private static int notificationPolicyConversationSendersToZenPolicy(
+ @Policy.ConversationSenders int npPriorityConversationSenders) {
+ switch (npPriorityConversationSenders) {
+ case Policy.CONVERSATION_SENDERS_ANYONE:
+ return ZenPolicy.CONVERSATION_SENDERS_ANYONE;
+ case Policy.CONVERSATION_SENDERS_IMPORTANT:
+ return ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
+ case Policy.CONVERSATION_SENDERS_NONE:
+ return ZenPolicy.CONVERSATION_SENDERS_NONE;
+ default: // including Policy.CONVERSATION_SENDERS_UNSET
+ return ZenPolicy.CONVERSATION_SENDERS_UNSET;
+ }
+ }
+}
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index d9ca935..1d6dd1e 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -24,6 +24,9 @@
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
+import static android.service.notification.ZenAdapters.notificationPolicySendersToZenPolicyPeopleType;
+import static android.service.notification.ZenAdapters.zenPolicyConversationSendersToNotificationPolicy;
+import static android.service.notification.ZenAdapters.zenPolicyPeopleTypeToNotificationPolicySenders;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
@@ -1269,11 +1272,11 @@
public ZenPolicy toZenPolicy() {
ZenPolicy.Builder builder = new ZenPolicy.Builder()
.allowCalls(allowCalls
- ? ZenModeConfig.getZenPolicySenders(allowCallsFrom)
+ ? notificationPolicySendersToZenPolicyPeopleType(allowCallsFrom)
: ZenPolicy.PEOPLE_TYPE_NONE)
.allowRepeatCallers(allowRepeatCallers)
.allowMessages(allowMessages
- ? ZenModeConfig.getZenPolicySenders(allowMessagesFrom)
+ ? notificationPolicySendersToZenPolicyPeopleType(allowMessagesFrom)
: ZenPolicy.PEOPLE_TYPE_NONE)
.allowReminders(allowReminders)
.allowEvents(allowEvents)
@@ -1333,14 +1336,14 @@
if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_MESSAGES,
isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_MESSAGES, defaultPolicy))) {
priorityCategories |= Policy.PRIORITY_CATEGORY_MESSAGES;
- messageSenders = getNotificationPolicySenders(zenPolicy.getPriorityMessageSenders(),
- messageSenders);
+ messageSenders = zenPolicyPeopleTypeToNotificationPolicySenders(
+ zenPolicy.getPriorityMessageSenders(), messageSenders);
}
if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_CONVERSATIONS,
isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_CONVERSATIONS, defaultPolicy))) {
priorityCategories |= Policy.PRIORITY_CATEGORY_CONVERSATIONS;
- conversationSenders = getConversationSendersWithDefault(
+ conversationSenders = zenPolicyConversationSendersToNotificationPolicy(
zenPolicy.getPriorityConversationSenders(), conversationSenders);
} else {
conversationSenders = CONVERSATION_SENDERS_NONE;
@@ -1349,8 +1352,8 @@
if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_CALLS,
isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_CALLS, defaultPolicy))) {
priorityCategories |= Policy.PRIORITY_CATEGORY_CALLS;
- callSenders = getNotificationPolicySenders(zenPolicy.getPriorityCallSenders(),
- callSenders);
+ callSenders = zenPolicyPeopleTypeToNotificationPolicySenders(
+ zenPolicy.getPriorityCallSenders(), callSenders);
}
if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_REPEAT_CALLERS,
@@ -1449,47 +1452,6 @@
return (policy.suppressedVisualEffects & visualEffect) == 0;
}
- private static int getNotificationPolicySenders(@ZenPolicy.PeopleType int senders,
- int defaultPolicySender) {
- switch (senders) {
- case ZenPolicy.PEOPLE_TYPE_ANYONE:
- return Policy.PRIORITY_SENDERS_ANY;
- case ZenPolicy.PEOPLE_TYPE_CONTACTS:
- return Policy.PRIORITY_SENDERS_CONTACTS;
- case ZenPolicy.PEOPLE_TYPE_STARRED:
- return Policy.PRIORITY_SENDERS_STARRED;
- default:
- return defaultPolicySender;
- }
- }
-
- private static int getConversationSendersWithDefault(@ZenPolicy.ConversationSenders int senders,
- int defaultPolicySender) {
- switch (senders) {
- case ZenPolicy.CONVERSATION_SENDERS_ANYONE:
- case ZenPolicy.CONVERSATION_SENDERS_IMPORTANT:
- case ZenPolicy.CONVERSATION_SENDERS_NONE:
- return senders;
- default:
- return defaultPolicySender;
- }
- }
-
- /**
- * Maps NotificationManager.Policy senders type to ZenPolicy.PeopleType
- */
- public static @ZenPolicy.PeopleType int getZenPolicySenders(int senders) {
- switch (senders) {
- case Policy.PRIORITY_SENDERS_ANY:
- return ZenPolicy.PEOPLE_TYPE_ANYONE;
- case Policy.PRIORITY_SENDERS_CONTACTS:
- return ZenPolicy.PEOPLE_TYPE_CONTACTS;
- case Policy.PRIORITY_SENDERS_STARRED:
- default:
- return ZenPolicy.PEOPLE_TYPE_STARRED;
- }
- }
-
public Policy toNotificationPolicy() {
int priorityCategories = 0;
int priorityCallSenders = Policy.PRIORITY_SENDERS_CONTACTS;
@@ -1524,7 +1486,7 @@
}
priorityCallSenders = sourceToPrioritySenders(allowCallsFrom, priorityCallSenders);
priorityMessageSenders = sourceToPrioritySenders(allowMessagesFrom, priorityMessageSenders);
- priorityConversationSenders = getConversationSendersWithDefault(
+ priorityConversationSenders = zenPolicyConversationSendersToNotificationPolicy(
allowConversationsFrom, priorityConversationSenders);
int state = areChannelsBypassingDnd ? Policy.STATE_CHANNELS_BYPASSING_DND : 0;
@@ -1559,15 +1521,6 @@
}
}
- private static int prioritySendersToSource(int prioritySenders, int def) {
- switch (prioritySenders) {
- case Policy.PRIORITY_SENDERS_CONTACTS: return SOURCE_CONTACT;
- case Policy.PRIORITY_SENDERS_STARRED: return SOURCE_STAR;
- case Policy.PRIORITY_SENDERS_ANY: return SOURCE_ANYONE;
- default: return def;
- }
- }
-
private static int normalizePrioritySenders(int prioritySenders, int def) {
if (!(prioritySenders == Policy.PRIORITY_SENDERS_CONTACTS
|| prioritySenders == Policy.PRIORITY_SENDERS_STARRED
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 68e8c72..67a3978 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -1733,6 +1733,8 @@
private static native long nativeCopy(long destNativePtr, long sourceNativePtr,
boolean keepHistory);
@CriticalNative
+ private static native long nativeSplit(long destNativePtr, long sourceNativePtr, int idBits);
+ @CriticalNative
private static native int nativeGetId(long nativePtr);
@CriticalNative
private static native int nativeGetDeviceId(long nativePtr);
@@ -3767,86 +3769,23 @@
}
/**
- * Splits a motion event such that it includes only a subset of pointer ids.
+ * Splits a motion event such that it includes only a subset of pointer IDs.
+ * @param idBits the bitset indicating all of the pointer IDs from this motion event that should
+ * be in the new split event. idBits must be a non-empty subset of the pointer IDs
+ * contained in this event.
* @hide
*/
+ // TODO(b/327503168): Pass downTime as a parameter to split.
@UnsupportedAppUsage
+ @NonNull
public final MotionEvent split(int idBits) {
- MotionEvent ev = obtain();
- synchronized (gSharedTempLock) {
- final int oldPointerCount = nativeGetPointerCount(mNativePtr);
- ensureSharedTempPointerCapacity(oldPointerCount);
- final PointerProperties[] pp = gSharedTempPointerProperties;
- final PointerCoords[] pc = gSharedTempPointerCoords;
- final int[] map = gSharedTempPointerIndexMap;
-
- final int oldAction = nativeGetAction(mNativePtr);
- final int oldActionMasked = oldAction & ACTION_MASK;
- final int oldActionPointerIndex = (oldAction & ACTION_POINTER_INDEX_MASK)
- >> ACTION_POINTER_INDEX_SHIFT;
- int newActionPointerIndex = -1;
- int newPointerCount = 0;
- for (int i = 0; i < oldPointerCount; i++) {
- nativeGetPointerProperties(mNativePtr, i, pp[newPointerCount]);
- final int idBit = 1 << pp[newPointerCount].id;
- if ((idBit & idBits) != 0) {
- if (i == oldActionPointerIndex) {
- newActionPointerIndex = newPointerCount;
- }
- map[newPointerCount] = i;
- newPointerCount += 1;
- }
- }
-
- if (newPointerCount == 0) {
- throw new IllegalArgumentException("idBits did not match any ids in the event");
- }
-
- final int newAction;
- if (oldActionMasked == ACTION_POINTER_DOWN || oldActionMasked == ACTION_POINTER_UP) {
- if (newActionPointerIndex < 0) {
- // An unrelated pointer changed.
- newAction = ACTION_MOVE;
- } else if (newPointerCount == 1) {
- // The first/last pointer went down/up.
- newAction = oldActionMasked == ACTION_POINTER_DOWN
- ? ACTION_DOWN
- : (getFlags() & FLAG_CANCELED) == 0 ? ACTION_UP : ACTION_CANCEL;
- } else {
- // A secondary pointer went down/up.
- newAction = oldActionMasked
- | (newActionPointerIndex << ACTION_POINTER_INDEX_SHIFT);
- }
- } else {
- // Simple up/down/cancel/move or other motion action.
- newAction = oldAction;
- }
-
- final int historySize = nativeGetHistorySize(mNativePtr);
- for (int h = 0; h <= historySize; h++) {
- final int historyPos = h == historySize ? HISTORY_CURRENT : h;
-
- for (int i = 0; i < newPointerCount; i++) {
- nativeGetPointerCoords(mNativePtr, map[i], historyPos, pc[i]);
- }
-
- final long eventTimeNanos = nativeGetEventTimeNanos(mNativePtr, historyPos);
- if (h == 0) {
- ev.initialize(nativeGetDeviceId(mNativePtr), nativeGetSource(mNativePtr),
- nativeGetDisplayId(mNativePtr),
- newAction, nativeGetFlags(mNativePtr),
- nativeGetEdgeFlags(mNativePtr), nativeGetMetaState(mNativePtr),
- nativeGetButtonState(mNativePtr), nativeGetClassification(mNativePtr),
- nativeGetXOffset(mNativePtr), nativeGetYOffset(mNativePtr),
- nativeGetXPrecision(mNativePtr), nativeGetYPrecision(mNativePtr),
- nativeGetDownTimeNanos(mNativePtr), eventTimeNanos,
- newPointerCount, pp, pc);
- } else {
- nativeAddBatch(ev.mNativePtr, eventTimeNanos, pc, 0);
- }
- }
- return ev;
+ if (idBits == 0) {
+ throw new IllegalArgumentException(
+ "idBits must contain at least one pointer from this motion event");
}
+ MotionEvent event = obtain();
+ event.mNativePtr = nativeSplit(event.mNativePtr, this.mNativePtr, idBits);
+ return event;
}
/**
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index a7cb169..02f8e6e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -3447,7 +3447,10 @@
// other windows to resize/move based on the raw frame of the window, waiting until we
// can finish laying out this window and get back to the window manager with the
// ultimately computed insets.
- insetsPending = computesInternalInsets;
+ insetsPending = computesInternalInsets
+ // If this window provides insets via params, its insets source frame can be
+ // updated directly without waiting for WindowSession#setInsets.
+ && mWindowAttributes.providedInsets == null;
if (mSurfaceHolder != null) {
mSurfaceHolder.mSurfaceLock.lock();
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 64e5a5b..cf6b9e5 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -1160,12 +1160,10 @@
// denylist only applies to not important views
if (!view.isImportantForAutofill() && isActivityDeniedForAutofill()) {
- Log.d(TAG, "view is not autofillable - activity denied for autofill");
return false;
}
if (isActivityAllowedForAutofill()) {
- Log.d(TAG, "view is autofillable - activity allowed for autofill");
return true;
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 3be76cc..985f542 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1153,7 +1153,6 @@
}
final boolean startInput;
synchronized (mH) {
- mImeDispatcher.clear();
if (getBindSequenceLocked() != sequence) {
return;
}
@@ -2909,8 +2908,6 @@
* @param flags {@link #HANDWRITING_DELEGATE_FLAG_HOME_DELEGATOR_ALLOWED} or {@code 0}
* @param executor The executor to run the callback on.
* @param callback {@code true>} would be received if delegation was accepted.
- * @return {@code true} if view belongs to allowed delegate package declared in {@link
- * #prepareStylusHandwritingDelegation(View, String)} and delegation is accepted
* @see #prepareStylusHandwritingDelegation(View, String)
* @see #acceptStylusHandwritingDelegation(View)
*/
diff --git a/core/java/android/webkit/WebViewDelegate.java b/core/java/android/webkit/WebViewDelegate.java
index 3fc0a30..8501474 100644
--- a/core/java/android/webkit/WebViewDelegate.java
+++ b/core/java/android/webkit/WebViewDelegate.java
@@ -175,8 +175,16 @@
/**
* Adds the WebView asset path to {@link android.content.res.AssetManager}.
+ * If {@link android.content.res.Flags#FLAG_REGISTER_RESOURCE_PATHS} is enabled, this function
+ * will be a no-op because the asset paths appending work will only be handled by
+ * {@link android.content.res.Resources#registerResourcePaths(String, ApplicationInfo)},
+ * otherwise it behaves the old way.
*/
public void addWebViewAssetPath(Context context) {
+ if (android.content.res.Flags.registerResourcePaths()) {
+ return;
+ }
+
final String[] newAssetPaths =
WebViewFactory.getLoadedPackageInfo().applicationInfo.getAllApkPaths();
final ApplicationInfo appInfo = context.getApplicationInfo();
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index c748a57..8f1b72e 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -30,6 +30,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
+import android.content.res.Resources;
import android.os.Build;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -93,6 +94,9 @@
// error for namespace lookup
public static final int LIBLOAD_FAILED_TO_FIND_NAMESPACE = 10;
+ // generic error for future use
+ static final int LIBLOAD_FAILED_OTHER = 11;
+
/**
* Stores the timestamps at which various WebView startup events occurred in this process.
*/
@@ -544,8 +548,14 @@
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getChromiumProviderClass()");
try {
sTimestamps.mAddAssetsStart = SystemClock.uptimeMillis();
- for (String newAssetPath : webViewContext.getApplicationInfo().getAllApkPaths()) {
- initialApplication.getAssets().addAssetPathAsSharedLibrary(newAssetPath);
+ if (android.content.res.Flags.registerResourcePaths()) {
+ Resources.registerResourcePaths(webViewContext.getPackageName(),
+ webViewContext.getApplicationInfo());
+ } else {
+ for (String newAssetPath : webViewContext.getApplicationInfo()
+ .getAllApkPaths()) {
+ initialApplication.getAssets().addAssetPathAsSharedLibrary(newAssetPath);
+ }
}
sTimestamps.mAddAssetsEnd = sTimestamps.mGetClassLoaderStart =
SystemClock.uptimeMillis();
diff --git a/core/java/android/webkit/WebViewProviderResponse.java b/core/java/android/webkit/WebViewProviderResponse.java
index 84e34a3..926aaff 100644
--- a/core/java/android/webkit/WebViewProviderResponse.java
+++ b/core/java/android/webkit/WebViewProviderResponse.java
@@ -40,6 +40,7 @@
STATUS_SUCCESS,
STATUS_FAILED_WAITING_FOR_RELRO,
STATUS_FAILED_LISTING_WEBVIEW_PACKAGES,
+ STATUS_FAILED_OTHER,
})
@Retention(RetentionPolicy.SOURCE)
private @interface WebViewProviderStatus {}
@@ -49,6 +50,7 @@
WebViewFactory.LIBLOAD_FAILED_WAITING_FOR_RELRO;
public static final int STATUS_FAILED_LISTING_WEBVIEW_PACKAGES =
WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES;
+ public static final int STATUS_FAILED_OTHER = WebViewFactory.LIBLOAD_FAILED_OTHER;
public WebViewProviderResponse(
@Nullable PackageInfo packageInfo, @WebViewProviderStatus int status) {
diff --git a/core/java/android/webkit/WebViewUpdateManager.java b/core/java/android/webkit/WebViewUpdateManager.java
index 8ada598..07576a2 100644
--- a/core/java/android/webkit/WebViewUpdateManager.java
+++ b/core/java/android/webkit/WebViewUpdateManager.java
@@ -127,7 +127,7 @@
*
* This choice will be stored persistently.
*
- * @param newProvider the package name to use, or null to reset to default.
+ * @param newProvider the package name to use.
* @return the package name which is now in use, which may not be the
* requested one if it was not usable.
*/
@@ -155,7 +155,7 @@
/**
* Get the WebView provider which will be used if no explicit choice has been made.
*
- * The default provider is not guaranteed to be currently valid/usable.
+ * The default provider is not guaranteed to be a valid/usable WebView implementation.
*
* @return the default WebView provider.
*/
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index 55b2251..0b99df3 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -16,6 +16,8 @@
package android.widget;
+import static android.view.flags.Flags.viewVelocityApi;
+
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.compat.annotation.UnsupportedAppUsage;
@@ -1488,6 +1490,11 @@
if (!awakenScrollBars()) {
postInvalidateOnAnimation();
}
+
+ // For variable refresh rate project to track the current velocity of this View
+ if (viewVelocityApi()) {
+ setFrameContentVelocity(Math.abs(mScroller.getCurrVelocity()));
+ }
}
}
@@ -1810,6 +1817,11 @@
mScroller.fling(mScrollX, mScrollY, velocityX, 0, 0,
maxScroll, 0, 0, width / 2, 0);
+ // For variable refresh rate project to track the current velocity of this View
+ if (viewVelocityApi()) {
+ setFrameContentVelocity(Math.abs(mScroller.getCurrVelocity()));
+ }
+
final boolean movingRight = velocityX > 0;
View currentFocused = findFocus();
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index fe4ac4e..a2d8d80 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -4299,7 +4299,7 @@
}
if (mode == MODE_NORMAL) {
- mApplication = ApplicationInfo.CREATOR.createFromParcel(parcel);
+ mApplication = parcel.readTypedObject(ApplicationInfo.CREATOR);
mIdealSize = parcel.readInt() == 0 ? null : SizeF.CREATOR.createFromParcel(parcel);
mLayoutId = parcel.readInt();
mViewId = parcel.readInt();
@@ -6805,7 +6805,7 @@
mBitmapCache.writeBitmapsToParcel(dest, flags);
mCollectionCache.writeToParcel(dest, flags, intentsToIgnore);
}
- mApplication.writeToParcel(dest, flags);
+ dest.writeTypedObject(mApplication, flags);
if (mIsRoot || mIdealSize == null) {
dest.writeInt(0);
} else {
@@ -6893,7 +6893,8 @@
* @hide
*/
public boolean hasSameAppInfo(ApplicationInfo info) {
- return mApplication.packageName.equals(info.packageName) && mApplication.uid == info.uid;
+ return mApplication == null || mApplication.packageName.equals(info.packageName)
+ && mApplication.uid == info.uid;
}
/**
@@ -7672,8 +7673,7 @@
byte[] instruction;
final List<byte[]> instructions = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
- instruction = new byte[in.readInt()];
- in.readByteArray(instruction);
+ instruction = in.readBlob();
instructions.add(instruction);
}
return new DrawInstructions(instructions);
@@ -7688,8 +7688,7 @@
final List<byte[]> instructions = drawInstructions.mInstructions;
dest.writeInt(instructions.size());
for (byte[] instruction : instructions) {
- dest.writeInt(instruction.length);
- dest.writeByteArray(instruction);
+ dest.writeBlob(instruction);
}
}
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index a1ebde7..42c2d80 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -16,6 +16,8 @@
package android.widget;
+import static android.view.flags.Flags.viewVelocityApi;
+
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.compat.annotation.UnsupportedAppUsage;
@@ -726,6 +728,12 @@
* isFinished() is correct.
*/
mScroller.computeScrollOffset();
+
+ // For variable refresh rate project to track the current velocity of this View
+ if (viewVelocityApi()) {
+ setFrameContentVelocity(Math.abs(mScroller.getCurrVelocity()));
+ }
+
mIsBeingDragged = !mScroller.isFinished() || !mEdgeGlowBottom.isFinished()
|| !mEdgeGlowTop.isFinished();
// Catch the edge effect if it is active.
@@ -1573,6 +1581,11 @@
// Keep on drawing until the animation has finished.
postInvalidateOnAnimation();
}
+
+ // For variable refresh rate project to track the current velocity of this View
+ if (viewVelocityApi()) {
+ setFrameContentVelocity(Math.abs(mScroller.getCurrVelocity()));
+ }
} else {
if (mFlingStrictSpan != null) {
mFlingStrictSpan.finish();
@@ -1884,6 +1897,10 @@
mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0,
Math.max(0, bottom - height), 0, height/2);
+ // For variable refresh rate project to track the current velocity of this View
+ if (viewVelocityApi()) {
+ setFrameContentVelocity(Math.abs(mScroller.getCurrVelocity()));
+ }
if (mFlingStrictSpan == null) {
mFlingStrictSpan = StrictMode.enterCriticalSpan("ScrollView-fling");
}
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index 65b5979..c769518 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -172,6 +172,20 @@
newIntent.prepareToLeaveUser(callingUserId);
final CompletableFuture<ResolveInfo> targetResolveInfoFuture =
mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, targetUserId);
+
+ if (isPrivateProfile(callingUserId)) {
+ buildAndExecuteForPrivateProfile(intentReceived, className, newIntent, callingUserId,
+ targetUserId);
+ } else {
+ buildAndExecute(targetResolveInfoFuture, intentReceived, className, newIntent,
+ callingUserId,
+ targetUserId, userMessage, managedProfile);
+ }
+ }
+
+ private void buildAndExecute(CompletableFuture<ResolveInfo> targetResolveInfoFuture,
+ Intent intentReceived, String className, Intent newIntent, int callingUserId,
+ int targetUserId, String userMessage, UserInfo managedProfile) {
targetResolveInfoFuture
.thenApplyAsync(targetResolveInfo -> {
if (isResolverActivityResolveInfo(targetResolveInfo)) {
@@ -195,6 +209,23 @@
}, getApplicationContext().getMainExecutor());
}
+ private void buildAndExecuteForPrivateProfile(
+ Intent intentReceived, String className, Intent newIntent, int callingUserId,
+ int targetUserId) {
+ final CompletableFuture<ResolveInfo> targetResolveInfoFuture =
+ mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, targetUserId);
+ targetResolveInfoFuture
+ .thenAcceptAsync(targetResolveInfo -> {
+ if (isResolverActivityResolveInfo(targetResolveInfo)) {
+ launchResolverActivityWithCorrectTab(intentReceived, className, newIntent,
+ callingUserId, targetUserId);
+ } else {
+ maybeShowUserConsentMiniResolverPrivate(targetResolveInfo, newIntent,
+ targetUserId);
+ }
+ }, getApplicationContext().getMainExecutor());
+ }
+
private void maybeShowUserConsentMiniResolver(
ResolveInfo target, Intent launchIntent, UserInfo managedProfile) {
if (target == null || isIntentForwarderResolveInfo(target) || !isDeviceProvisioned()) {
@@ -233,24 +264,70 @@
"Showing user consent for redirection into the managed profile for intent [%s] and "
+ " calling package [%s]",
launchIntent, callingPackage));
+ PackageManager packageManagerForTargetUser =
+ createContextAsUser(UserHandle.of(targetUserId), /* flags= */ 0)
+ .getPackageManager();
+ buildMiniResolver(target, launchIntent, targetUserId,
+ getOpenInWorkMessage(launchIntent, target.loadLabel(packageManagerForTargetUser)),
+ packageManagerForTargetUser);
+
+
+ View telephonyInfo = findViewById(R.id.miniresolver_info_section);
+
+ // Additional information section is work telephony specific. Therefore, it is only shown
+ // for telephony related intents, when all sim subscriptions are in the work profile.
+ if ((isDialerIntent(launchIntent) || isTextMessageIntent(launchIntent))
+ && devicePolicyManager.getManagedSubscriptionsPolicy().getPolicyType()
+ == ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS) {
+ telephonyInfo.setVisibility(View.VISIBLE);
+ ((TextView) findViewById(R.id.miniresolver_info_section_text))
+ .setText(getWorkTelephonyInfoSectionMessage(launchIntent));
+ } else {
+ telephonyInfo.setVisibility(View.GONE);
+ }
+ }
+
+ private void maybeShowUserConsentMiniResolverPrivate(
+ ResolveInfo target, Intent launchIntent, int targetUserId) {
+ if (target == null || isIntentForwarderResolveInfo(target)) {
+ finish();
+ return;
+ }
+
+ String callingPackage = getCallingPackage();
+
+ Log.i("IntentForwarderActivity", String.format(
+ "Showing user consent for redirection into the main profile for intent [%s] and "
+ + " calling package [%s]",
+ launchIntent, callingPackage));
+ PackageManager packageManagerForTargetUser =
+ createContextAsUser(UserHandle.of(targetUserId), /* flags= */ 0)
+ .getPackageManager();
+ buildMiniResolver(target, launchIntent, targetUserId,
+ getString(R.string.miniresolver_open_in_personal,
+ target.loadLabel(packageManagerForTargetUser)),
+ packageManagerForTargetUser);
+
+ View telephonyInfo = findViewById(R.id.miniresolver_info_section);
+ telephonyInfo.setVisibility(View.GONE);
+ }
+
+ private void buildMiniResolver(ResolveInfo target, Intent launchIntent, int targetUserId,
+ String resolverTitle, PackageManager pmForTargetUser) {
int layoutId = R.layout.miniresolver;
setContentView(layoutId);
findViewById(R.id.title_container).setElevation(0);
- PackageManager packageManagerForTargetUser =
- createContextAsUser(UserHandle.of(targetUserId), /* flags= */ 0)
- .getPackageManager();
-
ImageView icon = findViewById(R.id.icon);
icon.setImageDrawable(
- getAppIcon(target, launchIntent, targetUserId, packageManagerForTargetUser));
+ getAppIcon(target, launchIntent, targetUserId, pmForTargetUser));
View buttonContainer = findViewById(R.id.button_bar_container);
buttonContainer.setPadding(0, 0, 0, buttonContainer.getPaddingBottom());
((TextView) findViewById(R.id.open_cross_profile)).setText(
- getOpenInWorkMessage(launchIntent, target.loadLabel(packageManagerForTargetUser)));
+ resolverTitle);
// The mini-resolver's negative button is reused in this flow to cancel the intent
((Button) findViewById(R.id.use_same_profile_browser)).setText(R.string.cancel);
@@ -269,21 +346,6 @@
targetUserId);
finish();
});
-
-
- View telephonyInfo = findViewById(R.id.miniresolver_info_section);
-
- // Additional information section is work telephony specific. Therefore, it is only shown
- // for telephony related intents, when all sim subscriptions are in the work profile.
- if ((isDialerIntent(launchIntent) || isTextMessageIntent(launchIntent))
- && devicePolicyManager.getManagedSubscriptionsPolicy().getPolicyType()
- == ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS) {
- telephonyInfo.setVisibility(View.VISIBLE);
- ((TextView) findViewById(R.id.miniresolver_info_section_text))
- .setText(getWorkTelephonyInfoSectionMessage(launchIntent));
- } else {
- telephonyInfo.setVisibility(View.GONE);
- }
}
private Drawable getAppIcon(
@@ -548,6 +610,18 @@
}
/**
+ * Returns the private profile for this device or null if there is no private profile.
+ */
+ @Nullable
+ private UserInfo getPrivateProfile() {
+ List<UserInfo> relatedUsers = mInjector.getUserManager().getProfiles(UserHandle.myUserId());
+ for (UserInfo userInfo : relatedUsers) {
+ if (userInfo.isPrivateProfile()) return userInfo;
+ }
+ return null;
+ }
+
+ /**
* Returns the userId of the profile parent or UserHandle.USER_NULL if there is
* no parent.
*/
@@ -577,6 +651,17 @@
return mMetricsLogger;
}
+ private boolean isPrivateProfile(int userId) {
+ UserInfo privateProfile = getPrivateProfile();
+ return privateSpaceFlagsEnabled() && privateProfile != null
+ && privateProfile.id == userId;
+ }
+
+ private boolean privateSpaceFlagsEnabled() {
+ return android.os.Flags.allowPrivateProfile()
+ && android.multiuser.Flags.enablePrivateSpaceIntentRedirection();
+ }
+
@VisibleForTesting
protected Injector createInjector() {
return new InjectorImpl();
diff --git a/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java b/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
index 30de546..2096ba4 100644
--- a/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
@@ -55,37 +55,38 @@
* A service for the ProtoLog logging system.
*/
public class LegacyProtoLogImpl implements IProtoLog {
- private final TreeMap<String, IProtoLogGroup> mLogGroups = new TreeMap<>();
-
private static final int BUFFER_CAPACITY = 1024 * 1024;
private static final int PER_CHUNK_SIZE = 1024;
private static final String TAG = "ProtoLog";
private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
static final String PROTOLOG_VERSION = "2.0.0";
- private static final int DEFAULT_PER_CHUNK_SIZE = 0;
private final File mLogFile;
private final String mLegacyViewerConfigFilename;
private final TraceBuffer mBuffer;
private final LegacyProtoLogViewerConfigReader mViewerConfig;
+ private final TreeMap<String, IProtoLogGroup> mLogGroups;
private final int mPerChunkSize;
private boolean mProtoLogEnabled;
private boolean mProtoLogEnabledLockFree;
private final Object mProtoLogEnabledLock = new Object();
- public LegacyProtoLogImpl(String outputFile, String viewerConfigFilename) {
+ public LegacyProtoLogImpl(String outputFile, String viewerConfigFilename,
+ TreeMap<String, IProtoLogGroup> logGroups) {
this(new File(outputFile), viewerConfigFilename, BUFFER_CAPACITY,
- new LegacyProtoLogViewerConfigReader(), PER_CHUNK_SIZE);
+ new LegacyProtoLogViewerConfigReader(), PER_CHUNK_SIZE, logGroups);
}
public LegacyProtoLogImpl(File file, String viewerConfigFilename, int bufferCapacity,
- LegacyProtoLogViewerConfigReader viewerConfig, int perChunkSize) {
+ LegacyProtoLogViewerConfigReader viewerConfig, int perChunkSize,
+ TreeMap<String, IProtoLogGroup> logGroups) {
mLogFile = file;
mBuffer = new TraceBuffer(bufferCapacity);
mLegacyViewerConfigFilename = viewerConfigFilename;
mViewerConfig = viewerConfig;
mPerChunkSize = perChunkSize;
+ this.mLogGroups = logGroups;
}
/**
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 53062d8..bdd9a91 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -22,9 +22,9 @@
import static perfetto.protos.PerfettoTrace.InternedString.STR;
import static perfetto.protos.PerfettoTrace.ProtoLogMessage.BOOLEAN_PARAMS;
import static perfetto.protos.PerfettoTrace.ProtoLogMessage.DOUBLE_PARAMS;
-import static perfetto.protos.PerfettoTrace.ProtoLogMessage.STACKTRACE_IID;
import static perfetto.protos.PerfettoTrace.ProtoLogMessage.MESSAGE_ID;
import static perfetto.protos.PerfettoTrace.ProtoLogMessage.SINT64_PARAMS;
+import static perfetto.protos.PerfettoTrace.ProtoLogMessage.STACKTRACE_IID;
import static perfetto.protos.PerfettoTrace.ProtoLogMessage.STR_PARAM_IIDS;
import static perfetto.protos.PerfettoTrace.ProtoLogViewerConfig.GROUPS;
import static perfetto.protos.PerfettoTrace.ProtoLogViewerConfig.Group.ID;
@@ -78,7 +78,6 @@
* A service for the ProtoLog logging system.
*/
public class PerfettoProtoLogImpl implements IProtoLog {
- private final TreeMap<String, IProtoLogGroup> mLogGroups = new TreeMap<>();
private static final String LOG_TAG = "ProtoLog";
private final AtomicInteger mTracingInstances = new AtomicInteger();
@@ -89,8 +88,10 @@
);
private final ProtoLogViewerConfigReader mViewerConfigReader;
private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider;
+ private final TreeMap<String, IProtoLogGroup> mLogGroups;
- public PerfettoProtoLogImpl(String viewerConfigFilePath) {
+ public PerfettoProtoLogImpl(String viewerConfigFilePath,
+ TreeMap<String, IProtoLogGroup> logGroups) {
this(() -> {
try {
return new ProtoInputStream(new FileInputStream(viewerConfigFilePath));
@@ -98,23 +99,28 @@
Slog.w(LOG_TAG, "Failed to load viewer config file " + viewerConfigFilePath, e);
return null;
}
- });
+ }, logGroups);
}
- public PerfettoProtoLogImpl(ViewerConfigInputStreamProvider viewerConfigInputStreamProvider) {
+ public PerfettoProtoLogImpl(
+ ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
+ TreeMap<String, IProtoLogGroup> logGroups
+ ) {
this(viewerConfigInputStreamProvider,
- new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider));
+ new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider), logGroups);
}
@VisibleForTesting
public PerfettoProtoLogImpl(
ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
- ProtoLogViewerConfigReader viewerConfigReader
+ ProtoLogViewerConfigReader viewerConfigReader,
+ TreeMap<String, IProtoLogGroup> logGroups
) {
Producer.init(InitArguments.DEFAULTS);
mDataSource.register(DataSourceParams.DEFAULTS);
this.mViewerConfigInputStreamProvider = viewerConfigInputStreamProvider;
this.mViewerConfigReader = viewerConfigReader;
+ this.mLogGroups = logGroups;
}
/**
diff --git a/core/java/com/android/internal/protolog/ProtoLogImpl.java b/core/java/com/android/internal/protolog/ProtoLogImpl.java
index 8965385..487ae814 100644
--- a/core/java/com/android/internal/protolog/ProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/ProtoLogImpl.java
@@ -18,6 +18,7 @@
import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.LEGACY_OUTPUT_FILE_PATH;
import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.LEGACY_VIEWER_CONFIG_PATH;
+import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.LOG_GROUPS;
import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.VIEWER_CONFIG_PATH;
import android.annotation.Nullable;
@@ -28,6 +29,8 @@
import com.android.internal.protolog.common.LogLevel;
import com.android.internal.protolog.common.ProtoLogToolInjected;
+import java.util.TreeMap;
+
/**
* A service for the ProtoLog logging system.
*/
@@ -43,6 +46,9 @@
@ProtoLogToolInjected(LEGACY_OUTPUT_FILE_PATH)
private static String sLegacyOutputFilePath;
+ @ProtoLogToolInjected(LOG_GROUPS)
+ private static TreeMap<String, IProtoLogGroup> sLogGroups;
+
/** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
public static void d(IProtoLogGroup group, long messageHash, int paramsMask,
@Nullable String messageString,
@@ -99,11 +105,10 @@
public static synchronized IProtoLog getSingleInstance() {
if (sServiceInstance == null) {
if (android.tracing.Flags.perfettoProtologTracing()) {
- sServiceInstance =
- new PerfettoProtoLogImpl(sViewerConfigPath);
+ sServiceInstance = new PerfettoProtoLogImpl(sViewerConfigPath, sLogGroups);
} else {
- sServiceInstance =
- new LegacyProtoLogImpl(sLegacyOutputFilePath, sLegacyViewerConfigPath);
+ sServiceInstance = new LegacyProtoLogImpl(
+ sLegacyOutputFilePath, sLegacyViewerConfigPath, sLogGroups);
}
}
return sServiceInstance;
diff --git a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
index 3c206ac..ae3d448 100644
--- a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
+++ b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
@@ -4,6 +4,7 @@
import static perfetto.protos.PerfettoTrace.ProtoLogViewerConfig.MessageData.MESSAGE;
import static perfetto.protos.PerfettoTrace.ProtoLogViewerConfig.MessageData.MESSAGE_ID;
+import android.util.ArrayMap;
import android.util.proto.ProtoInputStream;
import com.android.internal.protolog.common.ILogger;
@@ -57,6 +58,7 @@
}
private void doLoadViewerConfig() throws IOException {
+ mLogMessageMap = new ArrayMap<>();
final ProtoInputStream pis = mViewerConfigInputStreamProvider.getInputStream();
while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
diff --git a/core/java/com/android/internal/protolog/common/ProtoLogToolInjected.java b/core/java/com/android/internal/protolog/common/ProtoLogToolInjected.java
index ffd0d76..17c82d7 100644
--- a/core/java/com/android/internal/protolog/common/ProtoLogToolInjected.java
+++ b/core/java/com/android/internal/protolog/common/ProtoLogToolInjected.java
@@ -22,7 +22,9 @@
@Target({ElementType.FIELD, ElementType.PARAMETER})
public @interface ProtoLogToolInjected {
- enum Value { VIEWER_CONFIG_PATH, LEGACY_OUTPUT_FILE_PATH, LEGACY_VIEWER_CONFIG_PATH }
+ enum Value {
+ VIEWER_CONFIG_PATH, LEGACY_OUTPUT_FILE_PATH, LEGACY_VIEWER_CONFIG_PATH, LOG_GROUPS
+ }
Value value();
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
index b7cb392..7c9fda5 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
@@ -92,7 +92,7 @@
mIndex = 0;
mStartingIndex = 0;
mSize = 0;
- if (expectedSize > mMaxSize) {
+ if (expectedSize >= mMaxSize) {
resize(expectedSize);
}
}
diff --git a/core/jni/LayoutlibLoader.cpp b/core/jni/LayoutlibLoader.cpp
index d5f17da..83b6afa 100644
--- a/core/jni/LayoutlibLoader.cpp
+++ b/core/jni/LayoutlibLoader.cpp
@@ -196,14 +196,6 @@
return result;
}
-static vector<string> parseCsv(JNIEnv* env, jstring csvJString) {
- const char* charArray = env->GetStringUTFChars(csvJString, 0);
- string csvString(charArray);
- vector<string> result = parseCsv(csvString);
- env->ReleaseStringUTFChars(csvJString, charArray);
- return result;
-}
-
void LayoutlibLogger(base::LogId, base::LogSeverity severity, const char* tag, const char* file,
unsigned int line, const char* message) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
@@ -395,20 +387,28 @@
jmethodID getPropertyMethod = GetStaticMethodIDOrDie(env, system, "getProperty",
"(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
- // Get the names of classes that need to register their native methods
- auto nativesClassesJString =
- (jstring)env->CallStaticObjectMethod(system, getPropertyMethod,
- env->NewStringUTF("core_native_classes"),
- env->NewStringUTF(""));
- vector<string> classesToRegister = parseCsv(env, nativesClassesJString);
+ // Java system properties that contain LayoutLib config. The initial values in the map
+ // are the default values if the property is not specified.
+ std::unordered_map<std::string, std::string> systemProperties =
+ {{"core_native_classes", ""},
+ {"register_properties_during_load", ""},
+ {"icu.data.path", ""},
+ {"use_bridge_for_logging", ""},
+ {"keyboard_paths", ""}};
- jstring registerProperty =
- (jstring)env->CallStaticObjectMethod(system, getPropertyMethod,
- env->NewStringUTF(
- "register_properties_during_load"),
- env->NewStringUTF(""));
- const char* registerPropertyString = env->GetStringUTFChars(registerProperty, 0);
- if (strcmp(registerPropertyString, "true") == 0) {
+ for (auto& [name, defaultValue] : systemProperties) {
+ jstring propertyString =
+ (jstring)env->CallStaticObjectMethod(system, getPropertyMethod,
+ env->NewStringUTF(name.c_str()),
+ env->NewStringUTF(defaultValue.c_str()));
+ const char* propertyChars = env->GetStringUTFChars(propertyString, 0);
+ systemProperties[name] = string(propertyChars);
+ env->ReleaseStringUTFChars(propertyString, propertyChars);
+ }
+ // Get the names of classes that need to register their native methods
+ vector<string> classesToRegister = parseCsv(systemProperties["core_native_classes"]);
+
+ if (systemProperties["register_properties_during_load"] == "true") {
// Set the system properties first as they could be used in the static initialization of
// other classes
if (register_android_os_SystemProperties(env) < 0) {
@@ -423,35 +423,20 @@
env->CallStaticVoidMethod(bridge, setSystemPropertiesMethod);
property_initialize_ro_cpu_abilist();
}
- env->ReleaseStringUTFChars(registerProperty, registerPropertyString);
if (register_jni_procs(gRegJNIMap, classesToRegister, env) < 0) {
return JNI_ERR;
}
- // Set the location of ICU data
- auto stringPath = (jstring)env->CallStaticObjectMethod(system, getPropertyMethod,
- env->NewStringUTF("icu.data.path"),
- env->NewStringUTF(""));
- const char* path = env->GetStringUTFChars(stringPath, 0);
-
- if (strcmp(path, "**n/a**") != 0) {
- bool icuInitialized = init_icu(path);
+ if (!systemProperties["icu.data.path"].empty()) {
+ // Set the location of ICU data
+ bool icuInitialized = init_icu(systemProperties["icu.data.path"].c_str());
if (!icuInitialized) {
- fprintf(stderr, "Failed to initialize ICU\n");
return JNI_ERR;
}
- } else {
- fprintf(stderr, "Skip initializing ICU\n");
}
- env->ReleaseStringUTFChars(stringPath, path);
- jstring useJniProperty =
- (jstring)env->CallStaticObjectMethod(system, getPropertyMethod,
- env->NewStringUTF("use_bridge_for_logging"),
- env->NewStringUTF(""));
- const char* useJniString = env->GetStringUTFChars(useJniProperty, 0);
- if (strcmp(useJniString, "true") == 0) {
+ if (systemProperties["use_bridge_for_logging"] == "true") {
layoutLog = FindClassOrDie(env, "com/android/ide/common/rendering/api/ILayoutLog");
layoutLog = MakeGlobalRefOrDie(env, layoutLog);
logMethodId = GetMethodIDOrDie(env, layoutLog, "logAndroidFramework",
@@ -468,23 +453,16 @@
// initialize logging, so ANDROD_LOG_TAGS env variable is respected
android::base::InitLogging(nullptr, android::base::StderrLogger);
}
- env->ReleaseStringUTFChars(useJniProperty, useJniString);
// Use English locale for number format to ensure correct parsing of floats when using strtof
setlocale(LC_NUMERIC, "en_US.UTF-8");
- auto keyboardPathsJString =
- (jstring)env->CallStaticObjectMethod(system, getPropertyMethod,
- env->NewStringUTF("keyboard_paths"),
- env->NewStringUTF(""));
- const char* keyboardPathsString = env->GetStringUTFChars(keyboardPathsJString, 0);
- if (strcmp(keyboardPathsString, "**n/a**") != 0) {
- vector<string> keyboardPaths = parseCsv(env, keyboardPathsJString);
+ if (!systemProperties["keyboard_paths"].empty()) {
+ vector<string> keyboardPaths = parseCsv(systemProperties["keyboard_paths"]);
init_keyboard(env, keyboardPaths);
} else {
fprintf(stderr, "Skip initializing keyboard\n");
}
- env->ReleaseStringUTFChars(keyboardPathsJString, keyboardPathsString);
return JNI_VERSION_1_6;
}
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index b39d5cf..23adb8f7 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -622,6 +622,18 @@
return reinterpret_cast<jlong>(destEvent);
}
+static jlong android_view_MotionEvent_nativeSplit(jlong destNativePtr, jlong sourceNativePtr,
+ jint idBits) {
+ MotionEvent* destEvent = reinterpret_cast<MotionEvent*>(destNativePtr);
+ if (!destEvent) {
+ destEvent = new MotionEvent();
+ }
+ MotionEvent* sourceEvent = reinterpret_cast<MotionEvent*>(sourceNativePtr);
+ destEvent->splitFrom(*sourceEvent, static_cast<std::bitset<MAX_POINTER_ID + 1>>(idBits),
+ InputEvent::nextId());
+ return reinterpret_cast<jlong>(destEvent);
+}
+
static jint android_view_MotionEvent_nativeGetId(jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getId();
@@ -837,6 +849,7 @@
// --------------- @CriticalNative ------------------
{"nativeCopy", "(JJZ)J", (void*)android_view_MotionEvent_nativeCopy},
+ {"nativeSplit", "(JJI)J", (void*)android_view_MotionEvent_nativeSplit},
{"nativeGetId", "(J)I", (void*)android_view_MotionEvent_nativeGetId},
{"nativeGetDeviceId", "(J)I", (void*)android_view_MotionEvent_nativeGetDeviceId},
{"nativeGetSource", "(J)I", (void*)android_view_MotionEvent_nativeGetSource},
diff --git a/core/proto/android/hardware/sensorprivacy.proto b/core/proto/android/hardware/sensorprivacy.proto
index e368c6a..53aa710 100644
--- a/core/proto/android/hardware/sensorprivacy.proto
+++ b/core/proto/android/hardware/sensorprivacy.proto
@@ -91,9 +91,7 @@
enum StateType {
ENABLED = 1;
DISABLED = 2;
- AUTO_DRIVER_ASSISTANCE_HELPFUL_APPS = 3;
- AUTO_DRIVER_ASSISTANCE_REQUIRED_APPS = 4;
- AUTO_DRIVER_ASSISTANCE_APPS = 5;
+ ENABLED_EXCEPT_ALLOWLISTED_APPS = 3;
}
// DEPRECATED
diff --git a/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto b/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto
index 5a18d9e..b75d545 100644
--- a/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto
+++ b/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto
@@ -39,7 +39,7 @@
optional string cur_token = 14;
optional int32 cur_token_display_id = 15;
optional bool system_ready = 16;
- optional int32 last_switch_user_id = 17;
+ reserved 17; // deprecated last_switch_user_id
optional bool have_connection = 18;
optional bool bound_to_method = 19;
optional bool is_interactive = 20;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ba9751f..52bad21 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8679,7 +8679,7 @@
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
- <service android:name="com.android.server.companion.InactiveAssociationsRemovalService"
+ <service android:name="com.android.server.companion.association.InactiveAssociationsRemovalService"
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index c882938..d89f236 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -301,7 +301,9 @@
granted to the system companion device manager service -->
<flag name="companion" value="0x800000" />
<!-- Additional flag from base permission type: this permission will be granted to the
- retail demo app, as defined by the OEM. -->
+ retail demo app, as defined by the OEM.
+ This flag has been replaced by the retail demo role and is a no-op since Android V.
+ -->
<flag name="retailDemo" value="0x1000000" />
<!-- Additional flag from base permission type: this permission will be granted to the
recents app. -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 90f2731..4f20fce 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3551,10 +3551,6 @@
<string name="config_keyguardComponent" translatable="false"
>com.android.systemui/com.android.systemui.keyguard.KeyguardService</string>
- <!-- Screen record dialog component -->
- <string name="config_screenRecorderComponent" translatable="false"
- >com.android.systemui/com.android.systemui.screenrecord.ScreenRecordDialog</string>
-
<!-- The component name of a special dock app that merely launches a dream.
We don't want to launch this app when docked because it causes an unnecessary
activity transition. We just want to start the dream. -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 59066eb..238772f 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -6434,4 +6434,6 @@
<string name="satellite_notification_open_message">Open Messages</string>
<!-- Invoke Satellite setting activity of Settings -->
<string name="satellite_notification_how_it_works">How it works</string>
+ <!-- Initial/System provided label shown for an app which gets unarchived. [CHAR LIMIT=64]. -->
+ <string name="unarchival_session_app_label">Pending...</string>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a025c8d..06bcd26 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -375,7 +375,6 @@
<java-symbol type="string" name="config_recentsComponentName" />
<java-symbol type="string" name="config_systemUIServiceComponent" />
<java-symbol type="string" name="config_controlsPackage" />
- <java-symbol type="string" name="config_screenRecorderComponent" />
<java-symbol type="string" name="config_somnambulatorComponent" />
<java-symbol type="string" name="config_screenshotAppClipsServiceComponent" />
<java-symbol type="string" name="config_screenshotServiceComponent" />
@@ -5374,4 +5373,6 @@
<java-symbol type="string" name="config_defaultContextualSearchKey" />
<java-symbol type="string" name="config_defaultContextualSearchEnabled" />
<java-symbol type="string" name="config_defaultContextualSearchLegacyEnabled" />
+
+ <java-symbol type="string" name="unarchival_session_app_label" />
</resources>
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index 61e6a36..7d740ef 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -88,6 +88,9 @@
<!-- Colombia: 1-6 digits (not confirmed) -->
<shortcode country="co" pattern="\\d{1,6}" free="890350|908160|892255|898002|898880|899960|899948|87739|85517|491289" />
+ <!-- Costa Rica -->
+ <shortcode country="cr" pattern="\\d{1,6}" free="466453" />
+
<!-- Cyprus: 4-6 digits (not confirmed), known premium codes listed, plus EU -->
<shortcode country="cy" pattern="\\d{4,6}" premium="7510" free="116\\d{3}" />
@@ -143,6 +146,9 @@
<!-- Greece: 5 digits (54xxx, 19yxx, x=0-9, y=0-5): http://www.cmtelecom.com/premium-sms/greece -->
<shortcode country="gr" pattern="\\d{5}" premium="54\\d{3}|19[0-5]\\d{2}" free="116\\d{3}|12115" />
+ <!-- Guatemala -->
+ <shortcode country="gt" pattern="\\d{1,6}" free="466453" />
+
<!-- Croatia -->
<shortcode country="hr" pattern="\\d{1,5}" free="13062" />
@@ -241,10 +247,10 @@
<shortcode country="ph" pattern="\\d{1,5}" free="2147|5495|5496" />
<!-- Pakistan -->
- <shortcode country="pk" pattern="\\d{1,5}" free="2057" />
+ <shortcode country="pk" pattern="\\d{1,5}" free="2057|9092" />
<!-- Palestine: 5 digits, known premium codes listed -->
- <shortcode country="ps" pattern="\\d{1,5}" free="37477" />
+ <shortcode country="ps" pattern="\\d{1,5}" free="37477|6681" />
<!-- Poland: 4-5 digits (not confirmed), known premium codes listed, plus EU -->
<shortcode country="pl" pattern="\\d{4,5}" premium="74240|79(?:10|866)|92525" free="116\\d{3}|8012|80921" />
@@ -324,4 +330,7 @@
<!-- South Africa -->
<shortcode country="za" pattern="\\d{1,5}" free="44136|30791|36056" />
+ <!-- Zimbabwe -->
+ <shortcode country="zw" pattern="\\d{1,5}" free="33679" />
+
</shortcodes>
diff --git a/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
index 37a499a..6cc5485 100644
--- a/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
+++ b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
@@ -110,8 +110,6 @@
Paths.get("/data/misc/wmtrace/ime_trace_service.winscope"),
Paths.get("/data/misc/wmtrace/wm_trace.winscope"),
Paths.get("/data/misc/wmtrace/wm_log.winscope"),
- Paths.get("/data/misc/wmtrace/wm_transition_trace.winscope"),
- Paths.get("/data/misc/wmtrace/shell_transition_trace.winscope"),
};
private Handler mHandler;
@@ -257,6 +255,38 @@
assertThatAllFileContentsAreDifferent(preDumpedTraceFiles, actualTraceFiles);
}
+ @LargeTest
+ @Test
+ public void noPreDumpData_then_fullWithUsePreDumpFlag_ignoresFlag() throws Exception {
+ startPreDumpedUiTraces();
+
+ mBrm.preDumpUiData();
+ waitTillDumpstateExitedOrTimeout();
+
+ // Simulate lost of pre-dumped data.
+ // For example it can happen in this scenario:
+ // 1. Pre-dump data
+ // 2. Start bugreport + "use pre-dump" flag (USE AND REMOVE THE PRE-DUMP FROM DISK)
+ // 3. Start bugreport + "use pre-dump" flag (NO PRE-DUMP AVAILABLE ON DISK)
+ removeFilesIfNeeded(UI_TRACES_PREDUMPED);
+
+ // Start bugreport with "use predump" flag. Because the pre-dumped data is not available
+ // the flag will be ignored and data will be dumped as in normal flow.
+ BugreportCallbackImpl callback = new BugreportCallbackImpl();
+ mBrm.startBugreport(mBugreportFd, null, fullWithUsePreDumpFlag(), mExecutor,
+ callback);
+ shareConsentDialog(ConsentReply.ALLOW);
+ waitTillDoneOrTimeout(callback);
+
+ stopPreDumpedUiTraces();
+
+ assertThat(callback.isDone()).isTrue();
+ assertThat(mBugreportFile.length()).isGreaterThan(0L);
+ assertFdsAreClosed(mBugreportFd);
+
+ assertThatBugreportContainsFiles(UI_TRACES_PREDUMPED);
+ }
+
@Test
public void simultaneousBugreportsNotAllowed() throws Exception {
// Start bugreport #1
@@ -506,9 +536,6 @@
InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
"cmd window tracing start"
);
- InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
- "service call SurfaceFlinger 1025 i32 1"
- );
}
private static void stopPreDumpedUiTraces() {
@@ -611,6 +638,14 @@
return files;
}
+ private static void removeFilesIfNeeded(Path[] paths) throws Exception {
+ for (Path path : paths) {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+ "rm -f " + path.toString()
+ );
+ }
+ }
+
private static ParcelFileDescriptor parcelFd(File file) throws Exception {
return ParcelFileDescriptor.open(file,
ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_APPEND);
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index e72beee..24031cad 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -101,6 +101,7 @@
"flickerlib-trace_processor_shell",
"mockito-target-extended-minus-junit4",
"TestParameterInjector",
+ "android.content.res.flags-aconfig-java",
],
libs: [
diff --git a/core/tests/coretests/res/layout/activity_horizontal_scroll_view.xml b/core/tests/coretests/res/layout/activity_horizontal_scroll_view.xml
index 866e1a9..5029212 100644
--- a/core/tests/coretests/res/layout/activity_horizontal_scroll_view.xml
+++ b/core/tests/coretests/res/layout/activity_horizontal_scroll_view.xml
@@ -14,105 +14,150 @@
~ limitations under the License
-->
-<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:id="@+id/horizontal_scroll_view">
+ android:orientation="vertical">
- <LinearLayout
+ <HorizontalScrollView
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal">
+ android:layout_height="match_parent"
+ android:id="@+id/horizontal_scroll_view">
- <View
- android:background="#F00"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
- <View
- android:background="#880"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#F00"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#0F0"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#880"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#088"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#0F0"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#00F"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#088"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#808"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#00F"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#F00"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#808"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#880"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#F00"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#0F0"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#880"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#088"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#0F0"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#00F"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#088"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#808"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#00F"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#F00"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#808"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#880"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#F00"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#0F0"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#880"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#088"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#0F0"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#00F"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#088"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#808"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#00F"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- </LinearLayout>
-</HorizontalScrollView>
+ <View
+ android:background="#808"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ </LinearLayout>
+ </HorizontalScrollView>
+
+ <view
+ class="android.widget.HorizontalScrollViewFunctionalTest$MyHorizontalScrollView"
+ android:id="@+id/my_horizontal_scroll_view"
+ android:layout_width="90dp"
+ android:layout_height="90dp"
+ android:background="#FFF"
+ android:defaultFocusHighlightEnabled="false">
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <View
+ android:background="#00F"
+ android:layout_width="90dp"
+ android:layout_height="50dp"/>
+ <View
+ android:background="#0FF"
+ android:layout_width="90dp"
+ android:layout_height="50dp"/>
+ <View
+ android:background="#0F0"
+ android:layout_width="90dp"
+ android:layout_height="50dp"/>
+ <View
+ android:background="#FF0"
+ android:layout_width="90dp"
+ android:layout_height="50dp"/>
+ <View
+ android:background="#F00"
+ android:layout_width="90dp"
+ android:layout_height="50dp"/>
+ <View
+ android:background="#F0F"
+ android:layout_width="90dp"
+ android:layout_height="50dp"/>
+ </LinearLayout>
+ </view>
+</LinearLayout>
\ No newline at end of file
diff --git a/core/tests/coretests/res/layout/activity_scroll_view.xml b/core/tests/coretests/res/layout/activity_scroll_view.xml
index 61fabf8..db8cd02 100644
--- a/core/tests/coretests/res/layout/activity_scroll_view.xml
+++ b/core/tests/coretests/res/layout/activity_scroll_view.xml
@@ -14,105 +14,150 @@
~ limitations under the License
-->
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:id="@+id/scroll_view">
+ android:orientation="vertical">
- <LinearLayout
+ <ScrollView
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
+ android:layout_height="match_parent"
+ android:id="@+id/scroll_view">
- <View
- android:background="#F00"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
- <View
- android:background="#880"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#F00"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#0F0"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#880"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#088"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#0F0"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#00F"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#088"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#808"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#00F"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#F00"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#808"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#880"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#F00"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#0F0"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#880"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#088"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#0F0"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#00F"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#088"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#808"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#00F"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#F00"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#808"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#880"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#F00"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#0F0"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#880"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#088"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#0F0"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#00F"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#088"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#808"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#00F"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- </LinearLayout>
-</ScrollView>
+ <View
+ android:background="#808"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ </LinearLayout>
+ </ScrollView>
+
+ <view
+ class="android.widget.ScrollViewFunctionalTest$MyScrollView"
+ android:id="@+id/my_scroll_view"
+ android:layout_width="90dp"
+ android:layout_height="90dp"
+ android:background="#FFF"
+ android:defaultFocusHighlightEnabled="false">
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <View
+ android:background="#00F"
+ android:layout_width="90dp"
+ android:layout_height="50dp"/>
+ <View
+ android:background="#0FF"
+ android:layout_width="90dp"
+ android:layout_height="50dp"/>
+ <View
+ android:background="#0F0"
+ android:layout_width="90dp"
+ android:layout_height="50dp"/>
+ <View
+ android:background="#FF0"
+ android:layout_width="90dp"
+ android:layout_height="50dp"/>
+ <View
+ android:background="#F00"
+ android:layout_width="90dp"
+ android:layout_height="50dp"/>
+ <View
+ android:background="#F0F"
+ android:layout_width="90dp"
+ android:layout_height="50dp"/>
+ </LinearLayout>
+ </view>
+</LinearLayout>
diff --git a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
index 4a9cb71..0c1e879 100644
--- a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
+++ b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
@@ -18,34 +18,52 @@
import android.annotation.NonNull;
import android.app.ResourcesManager;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.LocaleList;
import android.platform.test.annotations.Postsubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.Display;
import android.view.DisplayAdjustments;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import junit.framework.TestCase;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@Postsubmit
+@RunWith(AndroidJUnit4.class)
public class ResourcesManagerTest extends TestCase {
private static final int SECONDARY_DISPLAY_ID = 1;
private static final String APP_ONE_RES_DIR = "app_one.apk";
private static final String APP_ONE_RES_SPLIT_DIR = "app_one_split.apk";
private static final String APP_TWO_RES_DIR = "app_two.apk";
private static final String LIB_RES_DIR = "lib.apk";
+ private static final String TEST_LIB = "com.android.frameworks.coretests.bdr_helper_app1";
private ResourcesManager mResourcesManager;
private Map<Integer, DisplayMetrics> mDisplayMetricsMap;
+ private PackageManager mPackageManager;
- @Override
- protected void setUp() throws Exception {
+ @Before
+ public void setUp() throws Exception {
super.setUp();
mDisplayMetricsMap = new HashMap<>();
@@ -93,8 +111,14 @@
return mDisplayMetricsMap.get(displayId);
}
};
+
+ mPackageManager = InstrumentationRegistry.getContext().getPackageManager();
}
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ @Test
@SmallTest
public void testMultipleCallsWithIdenticalParametersCacheReference() {
Resources resources = mResourcesManager.getResources(
@@ -109,6 +133,7 @@
assertSame(resources.getImpl(), newResources.getImpl());
}
+ @Test
@SmallTest
public void testMultipleCallsWithDifferentParametersReturnDifferentReferences() {
Resources resources = mResourcesManager.getResources(
@@ -125,6 +150,7 @@
assertNotSame(resources, newResources);
}
+ @Test
@SmallTest
public void testAddingASplitCreatesANewImpl() {
Resources resources1 = mResourcesManager.getResources(
@@ -142,6 +168,7 @@
assertNotSame(resources1.getImpl(), resources2.getImpl());
}
+ @Test
@SmallTest
public void testUpdateConfigurationUpdatesAllAssetManagers() {
Resources resources1 = mResourcesManager.getResources(
@@ -187,6 +214,7 @@
assertEquals(expectedConfig, resources3.getConfiguration());
}
+ @Test
@SmallTest
public void testTwoActivitiesWithIdenticalParametersShareImpl() {
Binder activity1 = new Binder();
@@ -208,6 +236,7 @@
assertSame(resources1.getImpl(), resources2.getImpl());
}
+ @Test
@SmallTest
public void testThemesGetUpdatedWithNewImpl() {
Binder activity1 = new Binder();
@@ -237,6 +266,7 @@
assertTrue(value.data != 0);
}
+ @Test
@SmallTest
public void testMultipleResourcesForOneActivityGetUpdatedWhenActivityBaseUpdates() {
Binder activity1 = new Binder();
@@ -286,6 +316,7 @@
assertEquals(expectedConfig2, resources2.getConfiguration());
}
+ @Test
@SmallTest
public void testChangingActivityDisplayDoesntOverrideDisplayRequestedByResources() {
Binder activity = new Binder();
@@ -322,4 +353,101 @@
assertEquals(mDisplayMetricsMap.get(Display.DEFAULT_DISPLAY).widthPixels,
defaultDisplayResources.getDisplayMetrics().widthPixels);
}
+
+ @Test
+ @SmallTest
+ @RequiresFlagsEnabled(Flags.FLAG_REGISTER_RESOURCE_PATHS)
+ public void testExistingResourcesAfterResourcePathsRegistration()
+ throws PackageManager.NameNotFoundException {
+ // Inject ResourcesManager instance from this test to the ResourcesManager class so that all
+ // the static method can interact with this test smoothly.
+ ResourcesManager oriResourcesManager = ResourcesManager.getInstance();
+ ResourcesManager.setInstance(mResourcesManager);
+
+ // Create a Resources before register resources' paths for a package.
+ Resources resources = mResourcesManager.getResources(
+ null, APP_ONE_RES_DIR, null, null, null, null, null, null,
+ CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
+ assertNotNull(resources);
+ ResourcesImpl oriResImpl = resources.getImpl();
+
+ ApplicationInfo appInfo = mPackageManager.getApplicationInfo(TEST_LIB, 0);
+ Resources.registerResourcePaths(TEST_LIB, appInfo);
+
+ assertNotSame(oriResImpl, resources.getImpl());
+
+ String[] resourcePaths = appInfo.getAllApkPaths();
+ resourcePaths = removeDuplicates(resourcePaths);
+ ApkAssets[] loadedAssets = resources.getAssets().getApkAssets();
+ assertTrue(allResourcePathsLoaded(resourcePaths, loadedAssets));
+
+ // Package resources' paths should be cached in ResourcesManager.
+ assertEquals(Arrays.toString(resourcePaths), Arrays.toString(ResourcesManager.getInstance()
+ .getSharedLibAssetsMap().get(TEST_LIB).getAllAssetPaths()));
+
+ // Revert the ResourcesManager instance back.
+ ResourcesManager.setInstance(oriResourcesManager);
+ }
+
+ @Test
+ @SmallTest
+ @RequiresFlagsEnabled(Flags.FLAG_REGISTER_RESOURCE_PATHS)
+ public void testNewResourcesAfterResourcePathsRegistration()
+ throws PackageManager.NameNotFoundException {
+ // Inject ResourcesManager instance from this test to the ResourcesManager class so that all
+ // the static method can interact with this test smoothly.
+ ResourcesManager oriResourcesManager = ResourcesManager.getInstance();
+ ResourcesManager.setInstance(mResourcesManager);
+
+ ApplicationInfo appInfo = mPackageManager.getApplicationInfo(TEST_LIB, 0);
+ Resources.registerResourcePaths(TEST_LIB, appInfo);
+
+ // Create a Resources after register resources' paths for a package.
+ Resources resources = mResourcesManager.getResources(
+ null, APP_ONE_RES_DIR, null, null, null, null, null, null,
+ CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
+ assertNotNull(resources);
+
+ String[] resourcePaths = appInfo.getAllApkPaths();
+ resourcePaths = removeDuplicates(resourcePaths);
+ ApkAssets[] loadedAssets = resources.getAssets().getApkAssets();
+ assertTrue(allResourcePathsLoaded(resourcePaths, loadedAssets));
+
+ // Package resources' paths should be cached in ResourcesManager.
+ assertEquals(Arrays.toString(resourcePaths), Arrays.toString(ResourcesManager.getInstance()
+ .getSharedLibAssetsMap().get(TEST_LIB).getAllAssetPaths()));
+
+ // Revert the ResourcesManager instance back.
+ ResourcesManager.setInstance(oriResourcesManager);
+ }
+
+ private static boolean allResourcePathsLoaded(String[] resourcePaths, ApkAssets[] loadedAsset) {
+ for (int i = 0; i < resourcePaths.length; i++) {
+ if (!resourcePaths[i].endsWith(".apk")) {
+ continue;
+ }
+ boolean found = false;
+ for (int j = 0; j < loadedAsset.length; j++) {
+ if (loadedAsset[j].getAssetPath().equals(resourcePaths[i])) {
+ found = true;
+ }
+ }
+ if (!found) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static String[] removeDuplicates(String[] paths) {
+ var pathList = new ArrayList<String>();
+ var pathSet = new ArraySet<String>();
+ final int pathsLen = paths.length;
+ for (int i = 0; i < pathsLen; i++) {
+ if (pathSet.add(paths[i])) {
+ pathList.add(paths[i]);
+ }
+ }
+ return pathList.toArray(new String[0]);
+ }
}
diff --git a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
index df212eb..cd38bd6 100644
--- a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
+++ b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
@@ -16,11 +16,17 @@
package android.widget;
+import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import android.content.Context;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.util.AttributeSet;
import android.util.PollingCheck;
import androidx.test.filters.MediumTest;
@@ -43,14 +49,21 @@
public class HorizontalScrollViewFunctionalTest {
private HorizontalScrollViewActivity mActivity;
private HorizontalScrollView mHorizontalScrollView;
+ private MyHorizontalScrollView mMyHorizontalScrollView;
@Rule
public ActivityTestRule<HorizontalScrollViewActivity> mActivityRule = new ActivityTestRule<>(
HorizontalScrollViewActivity.class);
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() throws Exception {
mActivity = mActivityRule.getActivity();
mHorizontalScrollView = mActivity.findViewById(R.id.horizontal_scroll_view);
+ mMyHorizontalScrollView =
+ (MyHorizontalScrollView) mActivity.findViewById(R.id.my_horizontal_scroll_view);
}
@Test
@@ -79,6 +92,22 @@
assertEquals(maxScroll, mHorizontalScrollView.getScrollX());
}
+ @Test
+ @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
+ public void testSetVelocity() throws Throwable {
+ mActivityRule.runOnUiThread(() -> {
+ mMyHorizontalScrollView.setFrameContentVelocity(0);
+ });
+ // set setFrameContentVelocity shouldn't do anything.
+ assertEquals(mMyHorizontalScrollView.isSetVelocityCalled, false);
+
+ mActivityRule.runOnUiThread(() -> {
+ mMyHorizontalScrollView.fling(100);
+ });
+ // set setFrameContentVelocity should be called when fling.
+ assertEquals(mMyHorizontalScrollView.isSetVelocityCalled, true);
+ }
+
static class WatchedEdgeEffect extends EdgeEffect {
public CountDownLatch onAbsorbLatch = new CountDownLatch(1);
@@ -92,5 +121,29 @@
onAbsorbLatch.countDown();
}
}
+
+ public static class MyHorizontalScrollView extends ScrollView {
+
+ public boolean isSetVelocityCalled;
+
+ public MyHorizontalScrollView(Context context) {
+ super(context);
+ }
+
+ public MyHorizontalScrollView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public MyHorizontalScrollView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ public void setFrameContentVelocity(float pixelsPerSecond) {
+ if (pixelsPerSecond != 0) {
+ isSetVelocityCalled = true;
+ }
+ }
+ }
}
diff --git a/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java b/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java
index 109c808..a60b2a13 100644
--- a/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java
+++ b/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java
@@ -16,11 +16,17 @@
package android.widget;
+import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import android.content.Context;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.util.AttributeSet;
import android.util.PollingCheck;
import androidx.test.filters.MediumTest;
@@ -43,14 +49,20 @@
public class ScrollViewFunctionalTest {
private ScrollViewActivity mActivity;
private ScrollView mScrollView;
+ private MyScrollView mMyScrollView;
@Rule
public ActivityTestRule<ScrollViewActivity> mActivityRule = new ActivityTestRule<>(
ScrollViewActivity.class);
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() throws Exception {
mActivity = mActivityRule.getActivity();
mScrollView = mActivity.findViewById(R.id.scroll_view);
+ mMyScrollView = (MyScrollView) mActivity.findViewById(R.id.my_scroll_view);
}
@Test
@@ -79,6 +91,22 @@
assertEquals(maxScroll, mScrollView.getScrollY());
}
+ @Test
+ @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
+ public void testSetVelocity() throws Throwable {
+ mActivityRule.runOnUiThread(() -> {
+ mMyScrollView.setFrameContentVelocity(0);
+ });
+ // set setFrameContentVelocity shouldn't do anything.
+ assertEquals(mMyScrollView.isSetVelocityCalled, false);
+
+ mActivityRule.runOnUiThread(() -> {
+ mMyScrollView.fling(100);
+ });
+ // set setFrameContentVelocity should be called when fling.
+ assertEquals(mMyScrollView.isSetVelocityCalled, true);
+ }
+
static class WatchedEdgeEffect extends EdgeEffect {
public CountDownLatch onAbsorbLatch = new CountDownLatch(1);
@@ -92,5 +120,29 @@
onAbsorbLatch.countDown();
}
}
+
+ public static class MyScrollView extends ScrollView {
+
+ public boolean isSetVelocityCalled;
+
+ public MyScrollView(Context context) {
+ super(context);
+ }
+
+ public MyScrollView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public MyScrollView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ public void setFrameContentVelocity(float pixelsPerSecond) {
+ if (pixelsPerSecond != 0) {
+ isSetVelocityCalled = true;
+ }
+ }
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
index 58cfc66..43e6227 100644
--- a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
@@ -53,6 +53,7 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import androidx.test.InstrumentationRegistry;
@@ -93,6 +94,9 @@
private static final String TYPE_PLAIN_TEXT = "text/plain";
private static UserInfo MANAGED_PROFILE_INFO = new UserInfo();
+ private static UserInfo PRIVATE_PROFILE_INFO = new UserInfo(12, "Private", null,
+ UserInfo.FLAG_PROFILE, UserManager.USER_TYPE_PROFILE_PRIVATE);
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
static {
MANAGED_PROFILE_INFO.id = 10;
@@ -131,6 +135,7 @@
@Before
public void setup() {
+
MockitoAnnotations.initMocks(this);
mContext = InstrumentationRegistry.getTargetContext();
sInjector = spy(new TestInjector());
@@ -632,6 +637,54 @@
logMakerCaptor.getValue().getSubtype());
}
+ @Test
+ public void shouldForwardToParent_telephony_privateProfile() throws Exception {
+ mSetFlagsRule.enableFlags(
+ android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_INTENT_REDIRECTION);
+
+ sComponentName = FORWARD_TO_PARENT_COMPONENT_NAME;
+ when(mIPm.canForwardTo(
+ any(Intent.class), nullable(String.class), anyInt(), anyInt())).thenReturn(true);
+
+ List<UserInfo> profiles = new ArrayList<>();
+ profiles.add(CURRENT_USER_INFO);
+ profiles.add(PRIVATE_PROFILE_INFO);
+ when(mUserManager.getProfiles(anyInt())).thenReturn(profiles);
+ when(mUserManager.getProfileParent(anyInt())).thenReturn(CURRENT_USER_INFO);
+ Intent intent = new Intent(mContext, IntentForwarderWrapperActivity.class);
+ intent.setAction(Intent.ACTION_DIAL);
+ IntentForwarderWrapperActivity activity = mActivityRule.launchActivity(intent);
+ verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt());
+ assertEquals(activity.getStartActivityIntent().getAction(), intent.getAction());
+ assertEquals(activity.getUserIdActivityLaunchedIn(), CURRENT_USER_INFO.id);
+ }
+
+ @Test
+ public void shouldForwardToParent_mms_privateProfile() throws Exception {
+ mSetFlagsRule.enableFlags(
+ android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_INTENT_REDIRECTION);
+
+ sComponentName = FORWARD_TO_PARENT_COMPONENT_NAME;
+ when(mIPm.canForwardTo(
+ any(Intent.class), nullable(String.class), anyInt(), anyInt())).thenReturn(true);
+
+ List<UserInfo> profiles = new ArrayList<>();
+ profiles.add(CURRENT_USER_INFO);
+ profiles.add(PRIVATE_PROFILE_INFO);
+ when(mUserManager.getProfiles(anyInt())).thenReturn(profiles);
+ when(mUserManager.getProfileParent(anyInt())).thenReturn(CURRENT_USER_INFO);
+ Intent intent = new Intent(mContext, IntentForwarderWrapperActivity.class);
+ intent.setAction(Intent.ACTION_SEND);
+ intent.setType(TYPE_PLAIN_TEXT);
+ IntentForwarderWrapperActivity activity = mActivityRule.launchActivity(intent);
+ verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt());
+ assertEquals(activity.getStartActivityIntent().getAction(), intent.getAction());
+ assertEquals(activity.getStartActivityIntent().getType(), intent.getType());
+ assertEquals(activity.getUserIdActivityLaunchedIn(), CURRENT_USER_INFO.id);
+ }
+
private void setupShouldSkipDisclosureTest() throws RemoteException {
sComponentName = FORWARD_TO_PARENT_COMPONENT_NAME;
sActivityName = "MyTestActivity";
@@ -688,6 +741,14 @@
protected MetricsLogger getMetricsLogger() {
return mMetricsLogger;
}
+
+ Intent getStartActivityIntent() {
+ return mStartActivityIntent;
+ }
+
+ int getUserIdActivityLaunchedIn() {
+ return mUserIdActivityLaunchedIn;
+ }
}
public class TestInjector implements IntentForwarderActivity.Injector {
diff --git a/graphics/java/android/graphics/Matrix44.java b/graphics/java/android/graphics/Matrix44.java
index 7cc0eb7..a99e201 100644
--- a/graphics/java/android/graphics/Matrix44.java
+++ b/graphics/java/android/graphics/Matrix44.java
@@ -17,6 +17,7 @@
package android.graphics;
import android.annotation.FlaggedApi;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import com.android.graphics.hwui.flags.Flags;
@@ -98,11 +99,11 @@
/**
* Gets the value at the matrix's row and column.
*
- * @param row An integer from 0 to 4 indicating the row of the value to get
- * @param col An integer from 0 to 4 indicating the column of the value to get
+ * @param row An integer from 0 to 3 indicating the row of the value to get
+ * @param col An integer from 0 to 3 indicating the column of the value to get
*/
@FlaggedApi(Flags.FLAG_MATRIX_44)
- public float get(int row, int col) {
+ public float get(@IntRange(from = 0, to = 3) int row, @IntRange(from = 0, to = 3) int col) {
if (row >= 0 && row < 4 && col >= 0 && col < 4) {
return mBackingArray[row * 4 + col];
}
@@ -112,12 +113,13 @@
/**
* Sets the value at the matrix's row and column to the provided value.
*
- * @param row An integer from 0 to 4 indicating the row of the value to change
- * @param col An integer from 0 to 4 indicating the column of the value to change
+ * @param row An integer from 0 to 3 indicating the row of the value to change
+ * @param col An integer from 0 to 3 indicating the column of the value to change
* @param val The value the element at the specified index will be set to
*/
@FlaggedApi(Flags.FLAG_MATRIX_44)
- public void set(int row, int col, float val) {
+ public void set(@IntRange(from = 0, to = 3) int row, @IntRange(from = 0, to = 3) int col,
+ float val) {
if (row >= 0 && row < 4 && col >= 0 && col < 4) {
mBackingArray[row * 4 + col] = val;
} else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 8fd6ffe..474430e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -717,11 +717,6 @@
// Hide the stack after a delay, if needed.
updateTemporarilyInvisibleAnimation(false /* hideImmediately */);
-
- if (mShouldReorderBubblesAfterGestureCompletes) {
- mShouldReorderBubblesAfterGestureCompletes = false;
- updateBubbleOrderInternal(mBubbleData.getBubbles(), true);
- }
}
};
@@ -2732,6 +2727,12 @@
ev.getAction() != MotionEvent.ACTION_UP
&& ev.getAction() != MotionEvent.ACTION_CANCEL;
+ // If there is a deferred reorder action, and the gesture is over, run it now.
+ if (mShouldReorderBubblesAfterGestureCompletes && !mIsGestureInProgress) {
+ mShouldReorderBubblesAfterGestureCompletes = false;
+ updateBubbleOrderInternal(mBubbleData.getBubbles(), false);
+ }
+
return dispatched;
}
diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp
index 2ea4e3f..af169f4 100644
--- a/libs/hwui/VectorDrawable.cpp
+++ b/libs/hwui/VectorDrawable.cpp
@@ -540,7 +540,7 @@
}
bool Tree::canReuseBitmap(Bitmap* bitmap, int width, int height) {
- return bitmap && width <= bitmap->width() && height <= bitmap->height();
+ return bitmap && width == bitmap->width() && height == bitmap->height();
}
void Tree::onPropertyChanged(TreeProperties* prop) {
diff --git a/media/java/android/media/metrics/PlaybackSession.java b/media/java/android/media/metrics/PlaybackSession.java
index f8dd756..6223acf 100644
--- a/media/java/android/media/metrics/PlaybackSession.java
+++ b/media/java/android/media/metrics/PlaybackSession.java
@@ -24,7 +24,10 @@
import java.util.Objects;
/**
- * An instances of this class represents a session of media playback.
+ * An instance of this class represents a session of media playback used to report playback
+ * metrics and events.
+ *
+ * Create a new instance using {@link MediaMetricsManager#createPlaybackSession}.
*/
public final class PlaybackSession implements AutoCloseable {
private final @NonNull String mId;
@@ -80,6 +83,21 @@
mManager.reportTrackChangeEvent(mId, event);
}
+ /**
+ * A session ID is used to identify a unique playback and to tie together lower-level
+ * playback components.
+ *
+ * Associate this session with a {@link MediaCodec} by passing the ID into
+ * {@link MediaFormat} through {@link MediaFormat#LOG_SESSION_ID} when
+ * creating the {@link MediaCodec}.
+ *
+ * Associate this session with an {@link AudioTrack} by calling
+ * {@link AudioTrack#setLogSessionId}.
+ *
+ * Associate this session with {@link MediaDrm} and {@link MediaCrypto} by calling
+ * {@link MediaDrm#getPlaybackComponent} and then calling
+ * {@link PlaybackComponent#setLogSessionId}.
+ */
public @NonNull LogSessionId getSessionId() {
return mLogSessionId;
}
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 8396005..0fc80dd 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -2895,6 +2895,10 @@
jint offset,
jint size,
std::shared_ptr<C2Buffer> *buffer) {
+ if ((offset + size) > context->capacity()) {
+ ALOGW("extractBufferFromContext: offset + size provided exceed capacity");
+ return;
+ }
*buffer = context->toC2Buffer(offset, size);
if (*buffer == nullptr) {
if (!context->mMemory) {
@@ -2995,18 +2999,15 @@
"MediaCodec.LinearBlock#obtain method to obtain a compatible buffer.");
return;
}
- sp<CryptoInfosWrapper> cryptoInfos = new CryptoInfosWrapper{decltype(cryptoInfos->value)()};
- jint sampleSize = 0;
+ sp<CryptoInfosWrapper> cryptoInfos = nullptr;
+ jint sampleSize = totalSize;
if (cryptoInfoArray != nullptr) {
+ cryptoInfos = new CryptoInfosWrapper{decltype(cryptoInfos->value)()};
extractCryptoInfosFromObjectArray(env,
&sampleSize,
&cryptoInfos->value,
cryptoInfoArray,
&errorDetailMsg);
- } else {
- sampleSize = totalSize;
- std::unique_ptr<CodecCryptoInfo> cryptoInfo{new MediaCodecCryptoInfo(totalSize)};
- cryptoInfos->value.push_back(std::move(cryptoInfo));
}
if (env->ExceptionCheck()) {
// Creation of cryptoInfo failed. Let the exception bubble up.
diff --git a/native/android/Android.bp b/native/android/Android.bp
index 7f3792d..752ebdf 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -98,6 +98,7 @@
"libpowermanager",
"android.hardware.configstore@1.0",
"android.hardware.configstore-utils",
+ "android.os.flags-aconfig-cc",
"libnativedisplay",
],
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index 9d0221a..c0db089 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -268,9 +268,9 @@
ctor public PollingFrame(int, @Nullable byte[], int, int);
method public int describeContents();
method @NonNull public byte[] getData();
- method public int getGain();
method public int getTimestamp();
method public int getType();
+ method public int getVendorSpecificGain();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.nfc.cardemulation.PollingFrame> CREATOR;
field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final int POLLING_LOOP_TYPE_A = 65; // 0x41
diff --git a/nfc/java/android/nfc/cardemulation/PollingFrame.java b/nfc/java/android/nfc/cardemulation/PollingFrame.java
index 994f4ae..29d7bdf 100644
--- a/nfc/java/android/nfc/cardemulation/PollingFrame.java
+++ b/nfc/java/android/nfc/cardemulation/PollingFrame.java
@@ -157,7 +157,7 @@
mType = frame.getInt(KEY_POLLING_LOOP_TYPE);
byte[] data = frame.getByteArray(KEY_POLLING_LOOP_DATA);
mData = (data == null) ? new byte[0] : data;
- mGain = frame.getByte(KEY_POLLING_LOOP_GAIN);
+ mGain = frame.getInt(KEY_POLLING_LOOP_GAIN, -1);
mTimestamp = frame.getInt(KEY_POLLING_LOOP_TIMESTAMP);
}
@@ -194,8 +194,9 @@
/**
* Returns the gain representing the field strength of the NFC field when this polling loop
* frame was observed.
+ * @return the gain or -1 if there is no gain measurement associated with this frame.
*/
- public int getGain() {
+ public int getVendorSpecificGain() {
return mGain;
}
@@ -227,7 +228,9 @@
public Bundle toBundle() {
Bundle frame = new Bundle();
frame.putInt(KEY_POLLING_LOOP_TYPE, getType());
- frame.putByte(KEY_POLLING_LOOP_GAIN, (byte) getGain());
+ if (getVendorSpecificGain() != -1) {
+ frame.putInt(KEY_POLLING_LOOP_GAIN, (byte) getVendorSpecificGain());
+ }
frame.putByteArray(KEY_POLLING_LOOP_DATA, getData());
frame.putInt(KEY_POLLING_LOOP_TIMESTAMP, getTimestamp());
return frame;
@@ -236,7 +239,7 @@
@Override
public String toString() {
return "PollingFrame { Type: " + (char) getType()
- + ", gain: " + getGain()
+ + ", gain: " + getVendorSpecificGain()
+ ", timestamp: " + Integer.toUnsignedString(getTimestamp())
+ ", data: [" + HexFormat.ofDelimiter(" ").formatHex(getData()) + "] }";
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 4e1f4ee..3363ac0 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -29,6 +29,7 @@
import android.credentials.selection.Entry
import android.credentials.selection.GetCredentialProviderData
import android.credentials.selection.ProviderData
+import android.graphics.BlendMode
import android.graphics.drawable.Icon
import android.os.Bundle
import android.os.CancellationSignal
@@ -353,6 +354,7 @@
val sliceBuilder = InlineSuggestionUi
.newContentBuilder(pendingIntent)
.setTitle(displayName)
+ icon.setTintBlendMode(BlendMode.DST)
sliceBuilder.setStartIcon(icon)
if (primaryEntry.credentialType ==
CredentialType.PASSKEY && duplicateDisplayNameForPasskeys[displayName] == true) {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
index 56bd066..965ee86 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
@@ -152,14 +152,14 @@
},
)
}
- } else if (entrySecondLineText != null) {
+ } else if (!entrySecondLineText.isNullOrBlank()) {
BodySmallText(
text = entrySecondLineText,
enforceOneLine = enforceOneLine,
onTextLayout = onTextLayout,
)
}
- if (entryThirdLineText != null) {
+ if (!entryThirdLineText.isNullOrBlank()) {
BodySmallText(
text = entryThirdLineText,
enforceOneLine = enforceOneLine,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/InlinePresentationFactory.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/InlinePresentationFactory.kt
index 3ebdd20..ff421bc 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/InlinePresentationFactory.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/InlinePresentationFactory.kt
@@ -21,63 +21,20 @@
import android.content.Context
import android.util.Size
import android.widget.inline.InlinePresentationSpec
-import androidx.autofill.inline.common.TextViewStyle
-import androidx.autofill.inline.common.ViewStyle
-import androidx.autofill.inline.UiVersions
-import androidx.autofill.inline.UiVersions.Style
-import androidx.autofill.inline.v1.InlineSuggestionUi
-import androidx.core.content.ContextCompat
-import android.util.TypedValue
-import android.graphics.Typeface
-
class InlinePresentationsFactory {
companion object {
- private const val googleSansMediumFontFamily = "google-sans-medium"
- private const val googleSansTextFontFamily = "google-sans-text"
- // There is no min width required for now but this is needed for the spec builder
- private const val minInlineWidth = 5000
+ // There is no max width required for now but this is needed for the spec builder
+ private const val maxInlineWidth = 5000
fun modifyInlinePresentationSpec(context: Context,
originalSpec: InlinePresentationSpec): InlinePresentationSpec {
return InlinePresentationSpec.Builder(Size(originalSpec.minSize.width, originalSpec
.minSize.height),
- Size(minInlineWidth, originalSpec
+ Size(maxInlineWidth, originalSpec
.maxSize.height))
- .setStyle(UiVersions.newStylesBuilder().addStyle(getStyle(context)).build())
- .build()
- }
-
-
- fun getStyle(context: Context): Style {
- val textColorPrimary = ContextCompat.getColor(context,
- com.android.credentialmanager.R.color.text_primary)
- val textColorSecondary = ContextCompat.getColor(context,
- com.android.credentialmanager.R.color.text_secondary)
- val textColorBackground = ContextCompat.getColor(context,
- com.android.credentialmanager.R.color.inline_background)
- val chipHorizontalPadding = context.resources.getDimensionPixelSize(com.android
- .credentialmanager.R.dimen.horizontal_chip_padding)
- val chipVerticalPadding = context.resources.getDimensionPixelSize(com.android
- .credentialmanager.R.dimen.vertical_chip_padding)
- return InlineSuggestionUi.newStyleBuilder()
- .setChipStyle(
- ViewStyle.Builder().setPadding(chipHorizontalPadding,
- chipVerticalPadding,
- chipHorizontalPadding, chipVerticalPadding).build()
- )
- .setTitleStyle(
- TextViewStyle.Builder().setTextColor(textColorPrimary).setTextSize
- (TypedValue.COMPLEX_UNIT_DIP, 14F)
- .setTypeface(googleSansMediumFontFamily,
- Typeface.NORMAL).setBackgroundColor(textColorBackground)
- .build()
- )
- .setSubtitleStyle(TextViewStyle.Builder().setTextColor(textColorSecondary)
- .setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12F).setTypeface
- (googleSansTextFontFamily, Typeface.NORMAL).setBackgroundColor
- (textColorBackground).build())
+ .setStyle(originalSpec.getStyle())
.build()
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 4ef7760..af78573 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -336,7 +336,7 @@
if (!footerDescription.isNullOrBlank()) {
item {
Row(modifier = Modifier.fillMaxWidth().wrapContentHeight()) {
- BodySmallText(text = footerDescription)
+ BodyMediumText(text = footerDescription)
}
}
item { Divider(thickness = 24.dp, color = Color.Transparent) }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 748c798..bc0ea02 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -786,16 +786,16 @@
else if (credentialEntryInfo.icon == null) painterResource(R.drawable.ic_other_sign_in_24)
else null,
entryHeadlineText = username,
- entrySecondLineText =
+ entrySecondLineText = displayName,
+ entryThirdLineText =
(if (hasSingleEntry != null && hasSingleEntry)
if (credentialEntryInfo.credentialType == CredentialType.PASSKEY ||
credentialEntryInfo.credentialType == CredentialType.PASSWORD)
- listOf(displayName)
+ emptyList()
// Still show the type display name for all non-password/passkey types since it won't be
// mentioned in the bottom sheet heading.
- else listOf(displayName, credentialEntryInfo.credentialTypeDisplayName)
+ else listOf(credentialEntryInfo.credentialTypeDisplayName)
else listOf(
- displayName,
credentialEntryInfo.credentialTypeDisplayName,
credentialEntryInfo.providerDisplayName
)).filterNot(TextUtils::isEmpty).let { itemsToDisplay ->
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index ef40188..e35acae 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -25,6 +25,7 @@
import com.android.credentialmanager.model.get.CredentialEntryInfo
import com.android.credentialmanager.model.get.RemoteEntryInfo
import com.android.internal.util.Preconditions
+import java.time.Instant
data class GetCredentialUiState(
val isRequestForAllOptions: Boolean,
@@ -156,11 +157,17 @@
userNameToCredentialEntryMap.values.forEach {
it.sortWith(comparator)
}
- // Transform to list of PerUserNameCredentialEntryLists and then sort across usernames
+ // Transform to list of PerUserNameCredentialEntryLists and then sort the outer list (of
+ // entries grouped by username / entryGroupId) based on the latest timestamp within that
+ // PerUserNameCredentialEntryList
val sortedUserNameToCredentialEntryList = userNameToCredentialEntryMap.map {
PerUserNameCredentialEntryList(it.key, it.value)
}.sortedWith(
- compareByDescending { it.sortedCredentialEntryList.first().lastUsedTimeMillis }
+ compareByDescending {
+ it.sortedCredentialEntryList.maxByOrNull{ entry ->
+ entry.lastUsedTimeMillis ?: Instant.MIN
+ }?.lastUsedTimeMillis ?: Instant.MIN
+ }
)
return ProviderDisplayInfo(
@@ -211,7 +218,7 @@
val typePriorityMap: Map<String, Int>,
) : Comparator<CredentialEntryInfo> {
override fun compare(p0: CredentialEntryInfo, p1: CredentialEntryInfo): Int {
- // First prefer passkey type for its security benefits
+ // First rank by priorities of each credential type.
if (p0.rawCredentialType != p1.rawCredentialType) {
val p0Priority = typePriorityMap.getOrDefault(
p0.rawCredentialType, PriorityHints.PRIORITY_DEFAULT
@@ -225,6 +232,7 @@
return 1
}
}
+ // Then rank by last used timestamps.
val p0LastUsedTimeMillis = p0.lastUsedTimeMillis
val p1LastUsedTimeMillis = p1.lastUsedTimeMillis
// Then order by last used timestamp
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
index 634e067..cf2f85e 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
@@ -20,7 +20,6 @@
import static com.android.packageinstaller.PackageInstallerActivity.EXTRA_STAGED_SESSION_ID;
-import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
@@ -28,10 +27,10 @@
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
-import android.content.pm.Flags;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.res.AssetFileDescriptor;
+import android.Manifest;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
@@ -201,7 +200,7 @@
params.setPermissionState(Manifest.permission.USE_FULL_SCREEN_INTENT,
PackageInstaller.SessionParams.PERMISSION_STATE_DENIED);
- if (pfd != null && Flags.readInstallInfo()) {
+ if (pfd != null) {
try {
final PackageInstaller.InstallInfo result = installer.readInstallInfo(pfd,
debugPathName, 0);
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index e95a8e6..45bfe54 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -31,7 +31,6 @@
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
-import android.content.pm.Flags;
import android.content.pm.InstallSourceInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
@@ -400,10 +399,7 @@
final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID,
-1 /* defaultValue */);
final SessionInfo info = mInstaller.getSessionInfo(sessionId);
- String resolvedPath = null;
- if (info != null && Flags.getResolvedApkPath()) {
- resolvedPath = info.getResolvedBaseApkPath();
- }
+ String resolvedPath = info != null ? info.getResolvedBaseApkPath() : null;
if (info == null || !info.isSealed() || resolvedPath == null) {
Log.w(TAG, "Session " + sessionId + " in funky state; ignoring");
finish();
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
index 22caabd..aeabbd5 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
@@ -25,7 +25,6 @@
import android.content.Context
import android.content.Intent
import android.content.pm.ApplicationInfo
-import android.content.pm.Flags
import android.content.pm.PackageInfo
import android.content.pm.PackageInstaller
import android.content.pm.PackageInstaller.SessionInfo
@@ -363,7 +362,7 @@
params.setPermissionState(
Manifest.permission.USE_FULL_SCREEN_INTENT, SessionParams.PERMISSION_STATE_DENIED
)
- if (pfd != null && Flags.readInstallInfo()) {
+ if (pfd != null) {
try {
val installInfo = packageInstaller.readInstallInfo(pfd, debugPathName, 0)
params.setAppPackageName(installInfo.packageName)
@@ -426,8 +425,7 @@
if (PackageInstaller.ACTION_CONFIRM_INSTALL == intent.action) {
val info = packageInstaller.getSessionInfo(sessionId)
- val resolvedPath =
- if (Flags.getResolvedApkPath()) info?.resolvedBaseApkPath else null
+ val resolvedPath = info?.resolvedBaseApkPath
if (info == null || !info.isSealed || resolvedPath == null) {
Log.w(LOG_TAG, "Session $sessionId in funky state; ignoring")
return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt
index 0117ece..d5444cf 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt
@@ -53,7 +53,7 @@
* @param noInternet True if a connected Wi-Fi network cannot access the Internet
* @param level The number of bars to show (0-4)
*/
- fun getIcon(noInternet: Boolean, level: Int): Drawable? {
+ open fun getIcon(noInternet: Boolean, level: Int): Drawable? {
return context.getDrawable(getInternetIconResource(level, noInternet))
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
index 827d8fa..3b18aa3 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
@@ -72,6 +72,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -499,6 +500,7 @@
verify(mApplicationsState, never()).clearEntries();
}
+ @Ignore("b/328332487")
@Test
public void removeProfileApp_workprofileExists_doResumeIfNeededLocked_shouldClearEntries()
throws RemoteException {
@@ -573,6 +575,7 @@
verify(mApplicationsState).clearEntries();
}
+ @Ignore("b/328332487")
@Test
public void removeOwnerApp_workprofileExists_doResumeIfNeededLocked_shouldClearEntries()
throws RemoteException {
@@ -654,6 +657,7 @@
verify(mApplicationsState).clearEntries();
}
+ @Ignore("b/328332487")
@Test
public void noAppRemoved_workprofileExists_doResumeIfNeededLocked_shouldNotClearEntries()
throws RemoteException {
@@ -773,6 +777,7 @@
assertThat(primaryUserApp.shouldShowInPersonalTab(um, appInfo.uid)).isTrue();
}
+ @Ignore("b/328332487")
@Test
public void shouldShowInPersonalTab_userProfilePreU_returnsFalse() {
UserManager um = RuntimeEnvironment.application.getSystemService(UserManager.class);
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index add3134..3043d54 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -116,5 +116,8 @@
Settings.Global.Wearable.CHARGING_SOUNDS_ENABLED,
Settings.Global.Wearable.WRIST_DETECTION_AUTO_LOCKING_ENABLED,
Settings.Global.FORCE_ENABLE_PSS_PROFILING,
+ Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_ENABLED,
+ Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_TYPE,
+ Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_SPEED,
};
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index ce0257f..2a8eb9b 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -72,6 +72,7 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -157,6 +158,9 @@
"/product/etc/aconfig_flags.pb",
"/vendor/etc/aconfig_flags.pb");
+ private static final String APEX_DIR = "/apex";
+ private static final String APEX_ACONFIG_PATH_SUFFIX = "/etc/aconfig_flags.pb";
+
/**
* This tag is applied to all aconfig default value-loaded flags.
*/
@@ -238,7 +242,7 @@
private int mNextHistoricalOpIdx;
@GuardedBy("mLock")
- @Nullable
+ @NonNull
private Map<String, Map<String, String>> mNamespaceDefaults;
public static final int SETTINGS_TYPE_GLOBAL = 0;
@@ -332,23 +336,29 @@
mHistoricalOperations = Build.IS_DEBUGGABLE
? new ArrayList<>(HISTORICAL_OPERATION_COUNT) : null;
+ mNamespaceDefaults = new HashMap<>();
+
synchronized (mLock) {
readStateSyncLocked();
if (Flags.loadAconfigDefaults()) {
if (isConfigSettingsKey(mKey)) {
- loadAconfigDefaultValuesLocked();
+ loadAconfigDefaultValuesLocked(sAconfigTextProtoFilesOnDevice);
}
}
+ if (Flags.loadApexAconfigProtobufs()) {
+ if (isConfigSettingsKey(mKey)) {
+ List<String> apexProtoPaths = listApexProtoPaths();
+ loadAconfigDefaultValuesLocked(apexProtoPaths);
+ }
+ }
}
}
@GuardedBy("mLock")
- private void loadAconfigDefaultValuesLocked() {
- mNamespaceDefaults = new HashMap<>();
-
- for (String fileName : sAconfigTextProtoFilesOnDevice) {
+ private void loadAconfigDefaultValuesLocked(List<String> filePaths) {
+ for (String fileName : filePaths) {
try (FileInputStream inputStream = new FileInputStream(fileName)) {
loadAconfigDefaultValues(inputStream.readAllBytes(), mNamespaceDefaults);
} catch (IOException e) {
@@ -357,13 +367,41 @@
}
}
+ private List<String> listApexProtoPaths() {
+ LinkedList<String> paths = new LinkedList();
+
+ File apexDirectory = new File(APEX_DIR);
+ if (!apexDirectory.isDirectory()) {
+ return paths;
+ }
+
+ File[] subdirs = apexDirectory.listFiles();
+ if (subdirs == null) {
+ return paths;
+ }
+
+ for (File prefix : subdirs) {
+ // For each mainline modules, there are two directories, one <modulepackage>/,
+ // and one <modulepackage>@<versioncode>/. Just read the former.
+ if (prefix.getAbsolutePath().contains("@")) {
+ continue;
+ }
+
+ File protoPath = new File(prefix + APEX_ACONFIG_PATH_SUFFIX);
+ if (!protoPath.exists()) {
+ continue;
+ }
+
+ paths.add(protoPath.getAbsolutePath());
+ }
+ return paths;
+ }
+
@VisibleForTesting
@GuardedBy("mLock")
public void addAconfigDefaultValuesFromMap(
@NonNull Map<String, Map<String, String>> defaultMap) {
- if (mNamespaceDefaults != null) {
- mNamespaceDefaults.putAll(defaultMap);
- }
+ mNamespaceDefaults.putAll(defaultMap);
}
@VisibleForTesting
@@ -447,7 +485,7 @@
return names;
}
- @Nullable
+ @NonNull
public Map<String, Map<String, String>> getAconfigDefaultValues() {
synchronized (mLock) {
return mNamespaceDefaults;
@@ -519,9 +557,9 @@
return false;
}
- // Aconfig flags are always boot stable, so we anytime we write one, we staged it to be
+ // Aconfig flags are always boot stable, so we anytime we write one, we stage it to be
// applied on reboot.
- if (Flags.stageAllAconfigFlags() && mNamespaceDefaults != null) {
+ if (Flags.stageAllAconfigFlags()) {
int slashIndex = name.indexOf("/");
boolean stageFlag = isConfigSettingsKey(mKey)
&& slashIndex != -1
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
index e5086e8..2e14e9b 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
+++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
@@ -25,3 +25,11 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "load_apex_aconfig_protobufs"
+ namespace: "core_experiments_team_internal"
+ description: "When enabled, loads aconfig default values in apex flag protobufs into DeviceConfig on boot."
+ bug: "327383546"
+ is_fixed_read_only: true
+}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 28cdc6d..09d076e 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -626,9 +626,6 @@
Settings.Global.Wearable.BEDTIME_MODE,
Settings.Global.Wearable.BEDTIME_HARD_MODE,
Settings.Global.Wearable.LOCK_SCREEN_STATE,
- Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_ENABLED,
- Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_TYPE,
- Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_SPEED,
Settings.Global.Wearable.DISABLE_AOD_WHILE_PLUGGED,
Settings.Global.Wearable.NETWORK_LOCATION_OPT_IN,
Settings.Global.Wearable.CUSTOM_COLOR_FOREGROUND,
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index ea30c69..33362a2 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -25,7 +25,6 @@
import android.aconfig.Aconfig.parsed_flag;
import android.aconfig.Aconfig.parsed_flags;
import android.os.Looper;
-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;
@@ -231,38 +230,6 @@
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_LOAD_ACONFIG_DEFAULTS)
- public void testAddingAconfigMapOnNullIsNoOp() {
- int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
- Object lock = new Object();
- SettingsState settingsState = new SettingsState(
- InstrumentationRegistry.getContext(), lock, mSettingsFile, configKey,
- SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
-
- parsed_flags flags = parsed_flags
- .newBuilder()
- .addParsedFlag(parsed_flag
- .newBuilder()
- .setPackage("com.android.flags")
- .setName("flag5")
- .setNamespace("test_namespace")
- .setDescription("test flag")
- .addBug("12345678")
- .setState(Aconfig.flag_state.DISABLED)
- .setPermission(Aconfig.flag_permission.READ_WRITE))
- .build();
-
- synchronized (lock) {
- Map<String, Map<String, String>> defaults = new HashMap<>();
- settingsState.loadAconfigDefaultValues(flags.toByteArray(), defaults);
- settingsState.addAconfigDefaultValuesFromMap(defaults);
-
- assertEquals(null, settingsState.getAconfigDefaultValues());
- }
-
- }
-
- @Test
public void testInvalidAconfigProtoDoesNotCrash() {
Map<String, Map<String, String>> defaults = new HashMap<>();
SettingsState settingsState = getSettingStateObject();
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index cc2e84c..04cb88d 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -308,6 +308,7 @@
name: "SystemUI-tests",
use_resource_processor: true,
manifest: "tests/AndroidManifest-base.xml",
+ resource_dirs: [],
additional_manifests: ["tests/AndroidManifest.xml"],
srcs: [
"tests/src/**/*.kt",
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 8c8975f..d05d40d 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -25,6 +25,16 @@
}
flag {
+ name: "notification_view_flipper_pausing"
+ namespace: "systemui"
+ description: "Pause ViewFlippers inside Notification custom layouts when the shade is closed."
+ bug: "309146176"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "notification_async_group_header_inflation"
namespace: "systemui"
description: "Inflates the notification group summary header views from the background thread."
@@ -194,14 +204,6 @@
}
flag {
- name: "keyguard_shade_migration_nssl"
- namespace: "systemui"
- description: "Moves NSSL into a shared element between the notification_panel and "
- "keyguard_root_view."
- bug: "278054201"
-}
-
-flag {
name: "unfold_animation_background_progress"
namespace: "systemui"
description: "Moves unfold animation progress calculation to a background thread"
@@ -419,6 +421,13 @@
}
flag {
+ name: "smartspace_remoteviews_rendering"
+ namespace: "systemui"
+ description: "Indicate Smartspace RemoteViews rendering"
+ bug: "326292691"
+}
+
+flag {
name: "pin_input_field_styled_focus_state"
namespace: "systemui"
description: "Enables styled focus states on pin input field if keyboard is connected"
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
index 8dc7495..b124025 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
@@ -27,6 +27,7 @@
import android.text.Layout
import android.util.LruCache
import kotlin.math.roundToInt
+import android.util.Log
private const val DEFAULT_ANIMATION_DURATION: Long = 300
private const val TYPEFACE_CACHE_MAX_ENTRIES = 5
@@ -140,7 +141,6 @@
}
sealed class PositionedGlyph {
-
/** Mutable X coordinate of the glyph position relative from drawing offset. */
var x: Float = 0f
@@ -269,41 +269,53 @@
duration: Long = -1L,
interpolator: TimeInterpolator? = null,
delay: Long = 0,
- onAnimationEnd: Runnable? = null
+ onAnimationEnd: Runnable? = null,
+ ) = setTextStyleInternal(fvar, textSize, color, strokeWidth, animate, duration,
+ interpolator, delay, onAnimationEnd, updateLayoutOnFailure = true)
+
+ private fun setTextStyleInternal(
+ fvar: String?,
+ textSize: Float,
+ color: Int?,
+ strokeWidth: Float,
+ animate: Boolean,
+ duration: Long,
+ interpolator: TimeInterpolator?,
+ delay: Long,
+ onAnimationEnd: Runnable?,
+ updateLayoutOnFailure: Boolean,
) {
- if (animate) {
- animator.cancel()
- textInterpolator.rebase()
- }
+ try {
+ if (animate) {
+ animator.cancel()
+ textInterpolator.rebase()
+ }
- if (textSize >= 0) {
- textInterpolator.targetPaint.textSize = textSize
- }
+ if (textSize >= 0) {
+ textInterpolator.targetPaint.textSize = textSize
+ }
+ if (!fvar.isNullOrBlank()) {
+ textInterpolator.targetPaint.typeface = typefaceCache.getTypefaceForVariant(fvar)
+ }
+ if (color != null) {
+ textInterpolator.targetPaint.color = color
+ }
+ if (strokeWidth >= 0F) {
+ textInterpolator.targetPaint.strokeWidth = strokeWidth
+ }
+ textInterpolator.onTargetPaintModified()
- if (!fvar.isNullOrBlank()) {
- textInterpolator.targetPaint.typeface = typefaceCache.getTypefaceForVariant(fvar)
- }
-
- if (color != null) {
- textInterpolator.targetPaint.color = color
- }
- if (strokeWidth >= 0F) {
- textInterpolator.targetPaint.strokeWidth = strokeWidth
- }
- textInterpolator.onTargetPaintModified()
-
- if (animate) {
- animator.startDelay = delay
- animator.duration =
- if (duration == -1L) {
- DEFAULT_ANIMATION_DURATION
- } else {
- duration
- }
- interpolator?.let { animator.interpolator = it }
- if (onAnimationEnd != null) {
- val listener =
- object : AnimatorListenerAdapter() {
+ if (animate) {
+ animator.startDelay = delay
+ animator.duration =
+ if (duration == -1L) {
+ DEFAULT_ANIMATION_DURATION
+ } else {
+ duration
+ }
+ interpolator?.let { animator.interpolator = it }
+ if (onAnimationEnd != null) {
+ val listener = object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
onAnimationEnd.run()
animator.removeListener(this)
@@ -312,14 +324,25 @@
animator.removeListener(this)
}
}
- animator.addListener(listener)
+ animator.addListener(listener)
+ }
+ animator.start()
+ } else {
+ // No animation is requested, thus set base and target state to the same state.
+ textInterpolator.progress = 1f
+ textInterpolator.rebase()
+ invalidateCallback()
}
- animator.start()
- } else {
- // No animation is requested, thus set base and target state to the same state.
- textInterpolator.progress = 1f
- textInterpolator.rebase()
- invalidateCallback()
+ } catch (ex: IllegalArgumentException) {
+ if (updateLayoutOnFailure) {
+ Log.e(TAG, "setTextStyleInternal: Exception caught but retrying. This is usually" +
+ " due to the layout having changed unexpectedly without being notified.", ex)
+ updateLayout(textInterpolator.layout)
+ setTextStyleInternal(fvar, textSize, color, strokeWidth, animate, duration,
+ interpolator, delay, onAnimationEnd, updateLayoutOnFailure = false)
+ } else {
+ throw ex
+ }
}
}
@@ -355,15 +378,13 @@
interpolator: TimeInterpolator? = null,
delay: Long = 0,
onAnimationEnd: Runnable? = null
- ) {
- val fvar = fontVariationUtils.updateFontVariation(
- weight = weight,
- width = width,
- opticalSize = opticalSize,
- roundness = roundness,
- )
- setTextStyle(
- fvar = fvar,
+ ) = setTextStyleInternal(
+ fvar = fontVariationUtils.updateFontVariation(
+ weight = weight,
+ width = width,
+ opticalSize = opticalSize,
+ roundness = roundness,
+ ),
textSize = textSize,
color = color,
strokeWidth = strokeWidth,
@@ -372,6 +393,10 @@
interpolator = interpolator,
delay = delay,
onAnimationEnd = onAnimationEnd,
+ updateLayoutOnFailure = true,
)
+
+ companion object {
+ private val TAG = TextAnimator::class.simpleName!!
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
index bd539a7..2a13d49 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
@@ -51,6 +51,7 @@
import androidx.compose.ui.unit.dp
import com.android.compose.PlatformIconButton
import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
+import com.android.systemui.common.ui.compose.SelectedUserAwareInputConnection
import com.android.systemui.res.R
/** UI for the input part of a password-requiring version of the bouncer. */
@@ -71,6 +72,7 @@
val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState()
val animateFailure: Boolean by viewModel.animateFailure.collectAsState()
val isImeSwitcherButtonVisible by viewModel.isImeSwitcherButtonVisible.collectAsState()
+ val selectedUserId by viewModel.selectedUserId.collectAsState()
DisposableEffect(Unit) {
viewModel.onShown()
@@ -87,47 +89,51 @@
val color = MaterialTheme.colorScheme.onSurfaceVariant
val lineWidthPx = with(LocalDensity.current) { 2.dp.toPx() }
- TextField(
- value = password,
- onValueChange = viewModel::onPasswordInputChanged,
- enabled = isInputEnabled,
- visualTransformation = PasswordVisualTransformation(),
- singleLine = true,
- textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
- keyboardOptions =
- KeyboardOptions(
- keyboardType = KeyboardType.Password,
- imeAction = ImeAction.Done,
- ),
- keyboardActions =
- KeyboardActions(
- onDone = { viewModel.onAuthenticateKeyPressed() },
- ),
- modifier =
- modifier
- .focusRequester(focusRequester)
- .onFocusChanged { viewModel.onTextFieldFocusChanged(it.isFocused) }
- .drawBehind {
- drawLine(
- color = color,
- start = Offset(x = 0f, y = size.height - lineWidthPx),
- end = Offset(size.width, y = size.height - lineWidthPx),
- strokeWidth = lineWidthPx,
- )
- }
- .onInterceptKeyBeforeSoftKeyboard { keyEvent ->
- if (keyEvent.key == Key.Back) {
- viewModel.onImeDismissed()
- true
- } else {
- false
+ SelectedUserAwareInputConnection(selectedUserId) {
+ TextField(
+ value = password,
+ onValueChange = viewModel::onPasswordInputChanged,
+ enabled = isInputEnabled,
+ visualTransformation = PasswordVisualTransformation(),
+ singleLine = true,
+ textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
+ keyboardOptions =
+ KeyboardOptions(
+ keyboardType = KeyboardType.Password,
+ imeAction = ImeAction.Done,
+ ),
+ keyboardActions =
+ KeyboardActions(
+ onDone = { viewModel.onAuthenticateKeyPressed() },
+ ),
+ modifier =
+ modifier
+ .focusRequester(focusRequester)
+ .onFocusChanged { viewModel.onTextFieldFocusChanged(it.isFocused) }
+ .drawBehind {
+ drawLine(
+ color = color,
+ start = Offset(x = 0f, y = size.height - lineWidthPx),
+ end = Offset(size.width, y = size.height - lineWidthPx),
+ strokeWidth = lineWidthPx,
+ )
}
- },
- trailingIcon =
- if (isImeSwitcherButtonVisible) {
- { ImeSwitcherButton(viewModel, color) }
- } else null
- )
+ .onInterceptKeyBeforeSoftKeyboard { keyEvent ->
+ if (keyEvent.key == Key.Back) {
+ viewModel.onImeDismissed()
+ true
+ } else {
+ false
+ }
+ },
+ trailingIcon =
+ if (isImeSwitcherButtonVisible) {
+ { ImeSwitcherButton(viewModel, color) }
+ } else {
+ null
+ }
+ )
+ }
}
/** Button for changing the password input method (IME). */
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/SelectedUserAwareInputConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/SelectedUserAwareInputConnection.kt
new file mode 100644
index 0000000..c8e1450
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/SelectedUserAwareInputConnection.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalComposeUiApi::class)
+
+package com.android.systemui.common.ui.compose
+
+import android.annotation.UserIdInt
+import android.os.UserHandle
+import android.view.inputmethod.EditorInfo
+import android.view.inputmethod.InputConnection
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.platform.InterceptPlatformTextInput
+import androidx.compose.ui.platform.PlatformTextInputMethodRequest
+
+/**
+ * Wrapper for input connection composables that need to be aware of the selected user to connect to
+ * the correct instance of on-device services like autofill, autocorrect, etc.
+ *
+ * Usage:
+ * ```
+ * @Composable
+ * fun YourFunction(viewModel: YourViewModel) {
+ * val selectedUserId by viewModel.selectedUserId.collectAsState()
+ *
+ * SelectedUserAwareInputConnection(selectedUserId) {
+ * TextField(...)
+ * }
+ * }
+ * ```
+ */
+@Composable
+fun SelectedUserAwareInputConnection(
+ @UserIdInt selectedUserId: Int,
+ content: @Composable () -> Unit,
+) {
+ InterceptPlatformTextInput(
+ interceptor = { request, nextHandler ->
+ // Create a new request to wrap the incoming one with some custom logic.
+ val modifiedRequest =
+ object : PlatformTextInputMethodRequest {
+ override fun createInputConnection(outAttributes: EditorInfo): InputConnection {
+ val inputConnection = request.createInputConnection(outAttributes)
+ // After the original request finishes initializing the EditorInfo we can
+ // customize it. If we needed to we could also wrap the InputConnection
+ // before
+ // returning it.
+ updateEditorInfo(outAttributes)
+ return inputConnection
+ }
+
+ fun updateEditorInfo(outAttributes: EditorInfo) {
+ outAttributes.targetInputMethodUser = UserHandle.of(selectedUserId)
+ }
+ }
+
+ // Send our wrapping request to the next handler, which could be the system or another
+ // interceptor up the tree.
+ nextHandler.startInputMethod(modifiedRequest)
+ }
+ ) {
+ content()
+ }
+}
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 515c816..078da1c86 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
@@ -31,8 +31,8 @@
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@@ -93,7 +93,6 @@
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.dimensionResource
-import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTagsAsResourceId
@@ -119,7 +118,6 @@
import com.android.systemui.communal.ui.compose.extensions.observeTapsWithoutConsuming
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
-import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.res.R
import kotlinx.coroutines.launch
@@ -199,37 +197,26 @@
}
},
) {
- Column(Modifier.align(Alignment.TopStart)) {
- CommunalHubLazyGrid(
- communalContent = communalContent,
- viewModel = viewModel,
- contentPadding = contentPadding,
- contentOffset = contentOffset,
- setGridCoordinates = { gridCoordinates = it },
- updateDragPositionForRemove = { offset ->
- isDraggingToRemove =
- isPointerWithinCoordinates(
- offset = gridCoordinates?.let { it.positionInWindow() + offset },
- containerToCheck = removeButtonCoordinates
- )
- isDraggingToRemove
- },
- onOpenWidgetPicker = onOpenWidgetPicker,
- gridState = gridState,
- contentListState = contentListState,
- selectedKey = selectedKey,
- widgetConfigurator = widgetConfigurator,
- )
- // TODO(b/326060686): Remove this once keyguard indication area can persist over hub
- if (viewModel is CommunalViewModel) {
- val isUnlocked by viewModel.deviceUnlocked.collectAsState(initial = false)
- Spacer(Modifier.height(24.dp))
- LockStateIcon(
- isUnlocked = isUnlocked,
- modifier = Modifier.align(Alignment.CenterHorizontally),
- )
- }
- }
+ CommunalHubLazyGrid(
+ communalContent = communalContent,
+ viewModel = viewModel,
+ contentPadding = contentPadding,
+ contentOffset = contentOffset,
+ setGridCoordinates = { gridCoordinates = it },
+ updateDragPositionForRemove = { offset ->
+ isDraggingToRemove =
+ isPointerWithinCoordinates(
+ offset = gridCoordinates?.let { it.positionInWindow() + offset },
+ containerToCheck = removeButtonCoordinates
+ )
+ isDraggingToRemove
+ },
+ onOpenWidgetPicker = onOpenWidgetPicker,
+ gridState = gridState,
+ contentListState = contentListState,
+ selectedKey = selectedKey,
+ widgetConfigurator = widgetConfigurator,
+ )
if (viewModel.isEditMode && onOpenWidgetPicker != null && onEditDone != null) {
Toolbar(
@@ -281,7 +268,7 @@
@OptIn(ExperimentalFoundationApi::class)
@Composable
-private fun ColumnScope.CommunalHubLazyGrid(
+private fun BoxScope.CommunalHubLazyGrid(
communalContent: List<CommunalContentModel>,
viewModel: BaseCommunalViewModel,
contentPadding: PaddingValues,
@@ -295,7 +282,7 @@
widgetConfigurator: WidgetConfigurator?,
) {
var gridModifier =
- Modifier.align(Alignment.Start).onGloballyPositioned { setGridCoordinates(it) }
+ Modifier.align(Alignment.TopStart).onGloballyPositioned { setGridCoordinates(it) }
var list = communalContent
var dragDropState: GridDragDropState? = null
if (viewModel.isEditMode && viewModel is CommunalEditModeViewModel) {
@@ -377,26 +364,6 @@
}
}
-@Composable
-private fun LockStateIcon(
- isUnlocked: Boolean,
- modifier: Modifier = Modifier,
-) {
- val colors = LocalAndroidColorScheme.current
- val resource =
- if (isUnlocked) {
- R.drawable.ic_unlocked
- } else {
- R.drawable.ic_lock
- }
- Icon(
- painter = painterResource(id = resource),
- contentDescription = null,
- tint = colors.onPrimaryContainer,
- modifier = modifier.size(52.dp)
- )
-}
-
/**
* Toolbar that contains action buttons to
* 1) open the widget picker
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
index b3fcc30..53de5bc 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
@@ -27,12 +27,14 @@
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.togetherWith
+import androidx.compose.foundation.background
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
@@ -137,12 +139,14 @@
}
}
) { targetViewModel ->
- Expandable(
- modifier = Modifier.fillMaxSize(),
- color = targetViewModel.backgroundColor.toColor(),
- shape = RoundedCornerShape(12.dp),
- onClick = { viewModel.onDeviceClick(it) },
- ) {}
+ Spacer(
+ modifier =
+ Modifier.fillMaxSize()
+ .background(
+ color = targetViewModel.backgroundColor.toColor(),
+ shape = RoundedCornerShape(12.dp),
+ ),
+ )
}
transition.AnimatedContent(
contentKey = { it.icon },
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index 92eb8f8..4950b96 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -45,32 +45,32 @@
import com.android.systemui.biometrics.ui.viewmodel.DeviceEntryUdfpsTouchOverlayViewModel
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
-import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.LockscreenShadeTransitionController
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -102,6 +102,7 @@
@RunWith(AndroidJUnit4::class)
@RunWithLooper(setAsMainLooper = true)
class UdfpsControllerOverlayTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
@JvmField @Rule var rule = MockitoJUnit.rule()
@@ -148,28 +149,11 @@
@Before
fun setup() {
- testScope = TestScope(StandardTestDispatcher())
- powerRepository = FakePowerRepository()
- powerInteractor =
- PowerInteractor(
- powerRepository,
- mock(FalsingCollector::class.java),
- mock(ScreenOffAnimationController::class.java),
- statusBarStateController,
- )
- keyguardTransitionRepository = FakeKeyguardTransitionRepository()
- keyguardTransitionInteractor =
- KeyguardTransitionInteractor(
- scope = testScope.backgroundScope,
- repository = keyguardTransitionRepository,
- fromLockscreenTransitionInteractor = {
- mock(FromLockscreenTransitionInteractor::class.java)
- },
- fromPrimaryBouncerTransitionInteractor = {
- mock(FromPrimaryBouncerTransitionInteractor::class.java)
- },
- fromAodTransitionInteractor = { mock(FromAodTransitionInteractor::class.java) },
- )
+ testScope = kosmos.testScope
+ powerRepository = kosmos.fakePowerRepository
+ powerInteractor = kosmos.powerInteractor
+ keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
whenever(inflater.inflate(R.layout.udfps_view, null, false)).thenReturn(udfpsView)
whenever(inflater.inflate(R.layout.udfps_bp_view, null))
.thenReturn(mock(UdfpsBpView::class.java))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 8f802b8..563aad1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -39,7 +39,6 @@
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel.Companion.POPUP_AUTO_HIDE_TIMEOUT_MS
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -114,7 +113,6 @@
kosmos.communalInteractor,
kosmos.communalTutorialInteractor,
kosmos.shadeInteractor,
- kosmos.deviceEntryInteractor,
mediaHost,
logcatLogBuffer("CommunalViewModelTest"),
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
index fb46ed9d..b3380ff 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
@@ -25,18 +25,17 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
-import com.android.systemui.power.data.repository.FakePowerRepository
-import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory
+import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.statusbar.CircleReveal
import com.android.systemui.statusbar.LightRevealEffect
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -49,9 +48,10 @@
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
class LightRevealScrimRepositoryTest : SysuiTestCase() {
- private lateinit var fakeKeyguardRepository: FakeKeyguardRepository
- private lateinit var powerRepository: FakePowerRepository
- private lateinit var powerInteractor: PowerInteractor
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val fakeKeyguardRepository = kosmos.fakeKeyguardRepository
+ private val powerInteractor = kosmos.powerInteractor
private lateinit var underTest: LightRevealScrimRepositoryImpl
@get:Rule val animatorTestRule = AnimatorTestRule(this)
@@ -59,13 +59,13 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- fakeKeyguardRepository = FakeKeyguardRepository()
- powerRepository = FakePowerRepository()
- powerInteractor =
- PowerInteractorFactory.create(repository = powerRepository).powerInteractor
-
underTest =
- LightRevealScrimRepositoryImpl(fakeKeyguardRepository, context, powerInteractor, mock())
+ LightRevealScrimRepositoryImpl(
+ kosmos.fakeKeyguardRepository,
+ context,
+ kosmos.powerInteractor,
+ mock()
+ )
}
@Test
@@ -168,8 +168,9 @@
@Test
@TestableLooper.RunWithLooper(setAsMainLooper = true)
fun revealAmount_emitsTo1AfterAnimationStarted() =
- runTest(UnconfinedTestDispatcher()) {
+ testScope.runTest {
val value by collectLastValue(underTest.revealAmount)
+ runCurrent()
underTest.startRevealAmountAnimator(true)
assertEquals(0.0f, value)
animatorTestRule.advanceTimeBy(500L)
@@ -179,8 +180,9 @@
@Test
@TestableLooper.RunWithLooper(setAsMainLooper = true)
fun revealAmount_startingRevealTwiceWontRerunAnimator() =
- runTest(UnconfinedTestDispatcher()) {
+ testScope.runTest {
val value by collectLastValue(underTest.revealAmount)
+ runCurrent()
underTest.startRevealAmountAnimator(true)
assertEquals(0.0f, value)
animatorTestRule.advanceTimeBy(250L)
@@ -193,12 +195,14 @@
@Test
@TestableLooper.RunWithLooper(setAsMainLooper = true)
fun revealAmount_emitsTo0AfterAnimationStartedReversed() =
- runTest(UnconfinedTestDispatcher()) {
- val value by collectLastValue(underTest.revealAmount)
- underTest.startRevealAmountAnimator(false)
- assertEquals(1.0f, value)
+ testScope.runTest {
+ val lastValue by collectLastValue(underTest.revealAmount)
+ runCurrent()
+ underTest.startRevealAmountAnimator(true)
animatorTestRule.advanceTimeBy(500L)
- assertEquals(0.0f, value)
+ underTest.startRevealAmountAnimator(false)
+ animatorTestRule.advanceTimeBy(500L)
+ assertEquals(0.0f, lastValue)
}
/**
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index f9ec3d1..24c651f3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -18,9 +18,11 @@
package com.android.systemui.keyguard.domain.interactor
import android.app.StatusBarManager
+import android.platform.test.annotations.DisableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
@@ -120,6 +122,7 @@
}
@Test
+ @DisableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun testKeyguardGuardVisibilityStopsSecureCamera() =
testScope.runTest {
val secureCameraActive = collectLastValue(underTest.isSecureCameraActive)
@@ -144,6 +147,7 @@
}
@Test
+ @DisableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun testBouncerShowingResetsSecureCameraState() =
testScope.runTest {
val secureCameraActive = collectLastValue(underTest.isSecureCameraActive)
@@ -166,6 +170,7 @@
}
@Test
+ @DisableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun keyguardVisibilityIsDefinedAsKeyguardShowingButNotOccluded() = runTest {
val isVisible = collectLastValue(underTest.isKeyguardVisible)
repository.setKeyguardShowing(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 63abc8f..0ebcf56 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -27,6 +27,7 @@
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
import com.android.systemui.dock.DockManager
import com.android.systemui.dock.DockManagerFake
import com.android.systemui.flags.FakeFeatureFlags
@@ -49,6 +50,7 @@
import com.android.systemui.res.R
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.FakeSharedPreferences
@@ -57,6 +59,7 @@
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
@@ -80,6 +83,7 @@
@Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var launchAnimator: DialogTransitionAnimator
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
+ @Mock private lateinit var shadeInteractor: ShadeInteractor
@Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger
private lateinit var underTest: KeyguardQuickAffordanceInteractor
@@ -179,6 +183,7 @@
underTest =
KeyguardQuickAffordanceInteractor(
keyguardInteractor = withDeps.keyguardInteractor,
+ shadeInteractor = shadeInteractor,
lockPatternUtils = lockPatternUtils,
keyguardStateController = keyguardStateController,
userTracker = userTracker,
@@ -193,6 +198,8 @@
backgroundDispatcher = testDispatcher,
appContext = context,
)
+
+ whenever(shadeInteractor.anyExpansion).thenReturn(MutableStateFlow(0f))
}
@Test
@@ -339,6 +346,25 @@
}
@Test
+ fun quickAffordance_updateOncePerShadeExpansion() =
+ testScope.runTest {
+ val shadeExpansion = MutableStateFlow(0f)
+ whenever(shadeInteractor.anyExpansion).thenReturn(shadeExpansion)
+
+ val collectedValue by
+ collectValues(
+ underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
+ )
+
+ val initialSize = collectedValue.size
+ for (i in 0..10) {
+ shadeExpansion.value = i / 10f
+ }
+
+ assertThat(collectedValue.size).isEqualTo(initialSize + 1)
+ }
+
+ @Test
fun quickAffordanceAlwaysVisible_notVisible_restrictedByPolicyManager() =
testScope.runTest {
whenever(devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileDataInteractorTest.kt
new file mode 100644
index 0000000..c80eb13
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileDataInteractorTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.battery.doman.interactor
+
+import android.os.UserHandle
+import android.platform.test.annotations.EnabledOnRavenwood
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.battery.domain.interactor.BatterySaverTileDataInteractor
+import com.android.systemui.utils.leaks.FakeBatteryController
+import com.google.common.truth.Truth
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.toCollection
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class BatterySaverTileDataInteractorTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val testScope = kosmos.testScope
+ private val batteryController = FakeBatteryController(LeakCheck())
+ private val testUser = UserHandle.of(1)
+ private val underTest =
+ BatterySaverTileDataInteractor(testScope.testScheduler, batteryController)
+
+ @Test
+ fun availability_isTrue() =
+ testScope.runTest {
+ val availability = underTest.availability(testUser).toCollection(mutableListOf())
+
+ Truth.assertThat(availability).hasSize(1)
+ Truth.assertThat(availability.last()).isTrue()
+ }
+
+ @Test
+ fun tileData_matchesBatteryControllerPowerSaving() =
+ testScope.runTest {
+ val data by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ runCurrent()
+ Truth.assertThat(data!!.isPowerSaving).isFalse()
+
+ batteryController.setPowerSaveMode(true)
+ runCurrent()
+ Truth.assertThat(data!!.isPowerSaving).isTrue()
+
+ batteryController.setPowerSaveMode(false)
+ runCurrent()
+ Truth.assertThat(data!!.isPowerSaving).isFalse()
+ }
+
+ @Test
+ fun tileData_matchesBatteryControllerIsPluggedIn() =
+ testScope.runTest {
+ val data by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ runCurrent()
+ Truth.assertThat(data!!.isPluggedIn).isFalse()
+
+ batteryController.isPluggedIn = true
+ runCurrent()
+ Truth.assertThat(data!!.isPluggedIn).isTrue()
+
+ batteryController.isPluggedIn = false
+ runCurrent()
+ Truth.assertThat(data!!.isPluggedIn).isFalse()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileUserActionInteractorTest.kt
new file mode 100644
index 0000000..62c51e6
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileUserActionInteractorTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.battery.doman.interactor
+
+import android.platform.test.annotations.EnabledOnRavenwood
+import android.provider.Settings
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.impl.battery.domain.interactor.BatterySaverTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.battery.domain.model.BatterySaverTileModel
+import com.android.systemui.utils.leaks.FakeBatteryController
+import com.google.common.truth.Truth
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class BatterySaverTileUserActionInteractorTest : SysuiTestCase() {
+ private val inputHandler = FakeQSTileIntentUserInputHandler()
+ private val controller = FakeBatteryController(LeakCheck())
+ private val underTest = BatterySaverTileUserActionInteractor(inputHandler, controller)
+
+ @Test
+ fun handleClickWhenNotPluggedIn_flipsPowerSaverMode() = runTest {
+ val originalPowerSaveMode = controller.isPowerSave
+ controller.isPluggedIn = false
+
+ underTest.handleInput(
+ QSTileInputTestKtx.click(BatterySaverTileModel.Standard(false, originalPowerSaveMode))
+ )
+
+ Truth.assertThat(controller.isPowerSave).isNotEqualTo(originalPowerSaveMode)
+ }
+
+ @Test
+ fun handleClickWhenPluggedIn_doesNotTurnOnPowerSaverMode() = runTest {
+ controller.setPowerSaveMode(false)
+ val originalPowerSaveMode = controller.isPowerSave
+ controller.isPluggedIn = true
+
+ underTest.handleInput(
+ QSTileInputTestKtx.click(
+ BatterySaverTileModel.Standard(controller.isPluggedIn, originalPowerSaveMode)
+ )
+ )
+
+ Truth.assertThat(controller.isPowerSave).isEqualTo(originalPowerSaveMode)
+ }
+
+ @Test
+ fun handleLongClick() = runTest {
+ underTest.handleInput(
+ QSTileInputTestKtx.longClick(BatterySaverTileModel.Standard(false, false))
+ )
+
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+ Truth.assertThat(it.intent.action).isEqualTo(Settings.ACTION_BATTERY_SAVER_SETTINGS)
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt
new file mode 100644
index 0000000..6e9db2c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.battery.ui
+
+import android.graphics.drawable.TestStubDrawable
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.battery.domain.model.BatterySaverTileModel
+import com.android.systemui.qs.tiles.impl.battery.qsBatterySaverTileConfig
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BatterySaverTileMapperTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val batterySaverTileConfig = kosmos.qsBatterySaverTileConfig
+ private lateinit var mapper: BatterySaverTileMapper
+
+ @Before
+ fun setup() {
+ mapper =
+ BatterySaverTileMapper(
+ context.orCreateTestableResources
+ .apply {
+ addOverride(R.drawable.qs_battery_saver_icon_off, TestStubDrawable())
+ addOverride(R.drawable.qs_battery_saver_icon_on, TestStubDrawable())
+ }
+ .resources,
+ context.theme,
+ )
+ }
+
+ @Test
+ fun map_standard_notPluggedInNotPowerSaving() {
+ val inputModel = BatterySaverTileModel.Standard(false, false)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.INACTIVE,
+ "",
+ R.drawable.qs_battery_saver_icon_off,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_standard_notPluggedInPowerSaving() {
+ val inputModel = BatterySaverTileModel.Standard(false, true)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.ACTIVE,
+ "",
+ R.drawable.qs_battery_saver_icon_on,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_standard_pluggedInPowerSaving() {
+ val inputModel = BatterySaverTileModel.Standard(true, true)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.UNAVAILABLE,
+ "",
+ R.drawable.qs_battery_saver_icon_on,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_standard_pluggedInNotPowerSaving() {
+ val inputModel = BatterySaverTileModel.Standard(true, false)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.UNAVAILABLE,
+ "",
+ R.drawable.qs_battery_saver_icon_off,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_extremeSaverDisabledNotPluggedInNotPowerSaving() {
+ val inputModel = BatterySaverTileModel.Extreme(false, false, false)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.INACTIVE,
+ "",
+ R.drawable.qs_battery_saver_icon_off,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_extremeSaverDisabledNotPluggedInPowerSaving() {
+ val inputModel = BatterySaverTileModel.Extreme(false, true, false)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.ACTIVE,
+ context.getString(R.string.standard_battery_saver_text),
+ R.drawable.qs_battery_saver_icon_on,
+ context.getString(R.string.standard_battery_saver_text),
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_extremeSaverDisabledPluggedInPowerSaving() {
+ val inputModel = BatterySaverTileModel.Extreme(true, true, false)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.UNAVAILABLE,
+ "",
+ R.drawable.qs_battery_saver_icon_on,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_extremeSaverDisabledPluggedInNotPowerSaving() {
+ val inputModel = BatterySaverTileModel.Extreme(true, false, false)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.UNAVAILABLE,
+ "",
+ R.drawable.qs_battery_saver_icon_off,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_extremeSaverEnabledNotPluggedInNotPowerSaving() {
+ val inputModel = BatterySaverTileModel.Extreme(false, false, true)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.INACTIVE,
+ "",
+ R.drawable.qs_battery_saver_icon_off,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_extremeSaverEnabledNotPluggedInPowerSaving() {
+ val inputModel = BatterySaverTileModel.Extreme(false, true, true)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.ACTIVE,
+ context.getString(R.string.extreme_battery_saver_text),
+ R.drawable.qs_battery_saver_icon_on,
+ context.getString(R.string.extreme_battery_saver_text),
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_extremeSaverEnabledPluggedInPowerSaving() {
+ val inputModel = BatterySaverTileModel.Extreme(true, true, true)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.UNAVAILABLE,
+ "",
+ R.drawable.qs_battery_saver_icon_on,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_extremeSaverEnabledPluggedInNotPowerSaving() {
+ val inputModel = BatterySaverTileModel.Extreme(true, false, true)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.UNAVAILABLE,
+ "",
+ R.drawable.qs_battery_saver_icon_off,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ private fun createBatterySaverTileState(
+ activationState: QSTileState.ActivationState,
+ secondaryLabel: String,
+ iconRes: Int,
+ stateDescription: CharSequence?,
+ ): QSTileState {
+ val label = context.getString(R.string.battery_detail_switch_title)
+ return QSTileState(
+ { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ label,
+ activationState,
+ secondaryLabel,
+ if (activationState == QSTileState.ActivationState.UNAVAILABLE)
+ setOf(QSTileState.UserAction.LONG_CLICK)
+ else setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+ label,
+ stateDescription,
+ QSTileState.SideViewIcon.None,
+ QSTileState.EnabledState.ENABLED,
+ Switch::class.qualifiedName
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
index 243aab2..dcf635e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
@@ -32,8 +32,8 @@
import com.android.systemui.volume.localMediaRepository
import com.android.systemui.volume.mediaController
import com.android.systemui.volume.mediaControllerRepository
+import com.android.systemui.volume.mediaOutputActionsInteractor
import com.android.systemui.volume.mediaOutputInteractor
-import com.android.systemui.volume.panel.mediaOutputActionsInteractor
import com.android.systemui.volume.panel.volumePanelViewModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml
index 61f69c0..f6042e4 100644
--- a/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml
+++ b/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml
@@ -128,7 +128,8 @@
android:layout_marginStart="49dp"
android:layout_marginEnd="49dp"
android:overScrollMode="never"
- android:layout_marginBottom="16dp">
+ android:layout_marginBottom="16dp"
+ android:scrollbars="none">
<LinearLayout
android:id="@+id/keyboard_shortcuts_container"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b713417..5dbdd18 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -75,6 +75,11 @@
<!-- Battery saver notification dismiss action: Do not turn on battery saver. [CHAR LIMIT=NONE]-->
<string name="battery_saver_dismiss_action">No thanks</string>
+ <!-- Secondary label for Battery Saver tile when Battery Saver is enabled. [CHAR LIMIT=20] -->
+ <string name="standard_battery_saver_text">Standard</string>
+ <!-- Secondary label for Battery Saver tile when Extreme Battery Saver is enabled. [CHAR LIMIT=20] -->
+ <string name="extreme_battery_saver_text">Extreme</string>
+
<!-- Name of the button that links to the Settings app. [CHAR LIMIT=NONE] -->
<!-- Name of the button that links to the Wifi settings screen. [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 8853589..33f14d4 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -22,17 +22,13 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
-import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.Preconditions;
import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
-import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
import com.android.systemui.animation.DialogTransitionAnimator;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
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.flags.FeatureFlags;
import com.android.systemui.fragments.FragmentService;
@@ -47,7 +43,6 @@
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationMediaManager;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.stack.AmbientState;
@@ -58,7 +53,6 @@
import com.android.systemui.statusbar.phone.SystemUIDialogManager;
import com.android.systemui.statusbar.policy.BluetoothController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.FlashlightController;
import com.android.systemui.statusbar.window.StatusBarWindowController;
import com.android.systemui.tuner.TunablePadding.TunablePaddingService;
import com.android.systemui.tuner.TunerService;
@@ -70,6 +64,7 @@
import javax.inject.Inject;
import javax.inject.Named;
+
/**
* Class to handle ugly dependencies throughout sysui until we determine the
* long-term dependency injection solution.
@@ -96,10 +91,6 @@
* Key for getting a Handler for receiving time tick broadcasts on.
*/
public static final String TIME_TICK_HANDLER_NAME = "time_tick_handler";
- /**
- * Generic handler on the main thread.
- */
- private static final String MAIN_HANDLER_NAME = "main_handler";
/**
* An email address to send memory leak reports to by default.
@@ -121,11 +112,6 @@
*/
public static final DependencyKey<Handler> TIME_TICK_HANDLER =
new DependencyKey<>(TIME_TICK_HANDLER_NAME);
- /**
- * Generic handler on the main thread.
- */
- public static final DependencyKey<Handler> MAIN_HANDLER =
- new DependencyKey<>(MAIN_HANDLER_NAME);
private final ArrayMap<Object, Object> mDependencies = new ArrayMap<>();
private final ArrayMap<Object, LazyDependencyCreator> mProviders = new ArrayMap<>();
@@ -134,7 +120,6 @@
@Inject Lazy<BroadcastDispatcher> mBroadcastDispatcher;
@Inject Lazy<BluetoothController> mBluetoothController;
- @Inject Lazy<FlashlightController> mFlashlightController;
@Inject Lazy<KeyguardUpdateMonitor> mKeyguardUpdateMonitor;
@Inject Lazy<DeviceProvisionedController> mDeviceProvisionedController;
@Inject Lazy<PluginManager> mPluginManager;
@@ -150,15 +135,10 @@
@Inject Lazy<LightBarController> mLightBarController;
@Inject Lazy<OverviewProxyService> mOverviewProxyService;
@Inject Lazy<NavigationModeController> mNavBarModeController;
- @Inject Lazy<AccessibilityButtonModeObserver> mAccessibilityButtonModeObserver;
- @Inject Lazy<AccessibilityButtonTargetsObserver> mAccessibilityButtonListController;
- @Inject Lazy<IStatusBarService> mIStatusBarService;
- @Inject Lazy<NotificationRemoteInputManager.Callback> mNotificationRemoteInputManagerCallback;
@Inject Lazy<NavigationBarController> mNavigationBarController;
@Inject Lazy<StatusBarStateController> mStatusBarStateController;
@Inject Lazy<NotificationMediaManager> mNotificationMediaManager;
@Inject @Background Lazy<Looper> mBgLooper;
- @Inject @Main Lazy<Handler> mMainHandler;
@Inject @Named(TIME_TICK_HANDLER_NAME) Lazy<Handler> mTimeTickHandler;
@Inject Lazy<SysUiState> mSysUiStateFlagsContainer;
@Inject Lazy<CommandQueue> mCommandQueue;
@@ -187,10 +167,8 @@
// on imports.
mProviders.put(TIME_TICK_HANDLER, mTimeTickHandler::get);
mProviders.put(BG_LOOPER, mBgLooper::get);
- mProviders.put(MAIN_HANDLER, mMainHandler::get);
mProviders.put(BroadcastDispatcher.class, mBroadcastDispatcher::get);
mProviders.put(BluetoothController.class, mBluetoothController::get);
- mProviders.put(FlashlightController.class, mFlashlightController::get);
mProviders.put(KeyguardUpdateMonitor.class, mKeyguardUpdateMonitor::get);
mProviders.put(DeviceProvisionedController.class, mDeviceProvisionedController::get);
mProviders.put(PluginManager.class, mPluginManager::get);
@@ -205,13 +183,6 @@
mProviders.put(LightBarController.class, mLightBarController::get);
mProviders.put(OverviewProxyService.class, mOverviewProxyService::get);
mProviders.put(NavigationModeController.class, mNavBarModeController::get);
- mProviders.put(AccessibilityButtonModeObserver.class,
- mAccessibilityButtonModeObserver::get);
- mProviders.put(AccessibilityButtonTargetsObserver.class,
- mAccessibilityButtonListController::get);
- mProviders.put(IStatusBarService.class, mIStatusBarService::get);
- mProviders.put(NotificationRemoteInputManager.Callback.class,
- mNotificationRemoteInputManagerCallback::get);
mProviders.put(NavigationBarController.class, mNavigationBarController::get);
mProviders.put(StatusBarStateController.class, mStatusBarStateController::get);
mProviders.put(NotificationMediaManager.class, mNotificationMediaManager::get);
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatterySaverModule.kt b/packages/SystemUI/src/com/android/systemui/battery/BatterySaverModule.kt
index 8c9ae62..10a0d95 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatterySaverModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatterySaverModule.kt
@@ -1,9 +1,21 @@
package com.android.systemui.battery
+import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.qs.tiles.BatterySaverTile
+import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory
+import com.android.systemui.qs.tiles.impl.battery.domain.interactor.BatterySaverTileDataInteractor
+import com.android.systemui.qs.tiles.impl.battery.domain.interactor.BatterySaverTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.battery.domain.model.BatterySaverTileModel
+import com.android.systemui.qs.tiles.impl.battery.ui.BatterySaverTileMapper
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
+import com.android.systemui.res.R
import dagger.Binds
import dagger.Module
+import dagger.Provides
import dagger.multibindings.IntoMap
import dagger.multibindings.StringKey
@@ -15,4 +27,39 @@
@IntoMap
@StringKey(BatterySaverTile.TILE_SPEC)
fun bindBatterySaverTile(batterySaverTile: BatterySaverTile): QSTileImpl<*>
+
+ companion object {
+ private const val BATTERY_SAVER_TILE_SPEC = "battery"
+
+ @Provides
+ @IntoMap
+ @StringKey(BATTERY_SAVER_TILE_SPEC)
+ fun provideBatterySaverTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+ QSTileConfig(
+ tileSpec = TileSpec.create(BATTERY_SAVER_TILE_SPEC),
+ uiConfig =
+ QSTileUIConfig.Resource(
+ iconRes = R.drawable.qs_battery_saver_icon_off,
+ labelRes = R.string.battery_detail_switch_title,
+ ),
+ instanceId = uiEventLogger.getNewInstanceId(),
+ )
+
+ /** Inject BatterySaverTile into tileViewModelMap in QSModule */
+ @Provides
+ @IntoMap
+ @StringKey(BATTERY_SAVER_TILE_SPEC)
+ fun provideBatterySaverTileViewModel(
+ factory: QSTileViewModelFactory.Static<BatterySaverTileModel>,
+ mapper: BatterySaverTileMapper,
+ stateInteractor: BatterySaverTileDataInteractor,
+ userActionInteractor: BatterySaverTileUserActionInteractor
+ ): QSTileViewModel =
+ factory.create(
+ TileSpec.create(BATTERY_SAVER_TILE_SPEC),
+ userActionInteractor,
+ stateInteractor,
+ mapper,
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
index 1c8b84d..b42eda1 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
@@ -73,6 +73,14 @@
initialValue = isInputEnabled.value && !isTextFieldFocused.value,
)
+ /** The ID of the currently-selected user. */
+ val selectedUserId: StateFlow<Int> =
+ selectedUserInteractor.selectedUser.stateIn(
+ scope = viewModelScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = selectedUserInteractor.getSelectedUserId(),
+ )
+
override fun onHidden() {
super.onHidden()
isTextFieldFocused.value = false
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 35b27aa..fc9a7df 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
@@ -21,7 +21,6 @@
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
@@ -47,7 +46,6 @@
import kotlinx.coroutines.launch
/** The default view model used for showing the communal hub. */
-@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class CommunalViewModel
@Inject
@@ -56,7 +54,6 @@
private val communalInteractor: CommunalInteractor,
tutorialInteractor: CommunalTutorialInteractor,
shadeInteractor: ShadeInteractor,
- deviceEntryInteractor: DeviceEntryInteractor,
@Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
@CommunalLog logBuffer: LogBuffer,
) : BaseCommunalViewModel(communalInteractor, mediaHost) {
@@ -90,8 +87,6 @@
/** Whether touches should be disabled in communal */
val touchesAllowed: Flow<Boolean> = not(shadeInteractor.isAnyFullyExpanded)
- val deviceUnlocked: Flow<Boolean> = deviceEntryInteractor.isUnlocked
-
init {
// Initialize our media host for the UMO. This only needs to happen once and must be done
// before the MediaHierarchyManager attempts to move the UMO to the hub.
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt
index 79455eb..289dbd9 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt
@@ -24,9 +24,12 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.shared.model.BiometricMessage
import com.android.systemui.deviceentry.shared.model.FingerprintLockoutMessage
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -60,17 +63,23 @@
private val context: Context,
activityStarter: ActivityStarter,
powerInteractor: PowerInteractor,
+ keyguardTransitionInteractor: KeyguardTransitionInteractor,
) {
private val keyguardOccludedByApp: Flow<Boolean> =
- combine(
- keyguardInteractor.isKeyguardOccluded,
- keyguardInteractor.isKeyguardShowing,
- primaryBouncerInteractor.isShowing,
- alternateBouncerInteractor.isVisible,
- ) { occluded, showing, primaryBouncerShowing, alternateBouncerVisible ->
- occluded && showing && !primaryBouncerShowing && !alternateBouncerVisible
- }
- .distinctUntilChanged()
+ if (KeyguardWmStateRefactor.isEnabled) {
+ keyguardTransitionInteractor.currentKeyguardState.map { it == KeyguardState.OCCLUDED }
+ } else {
+ combine(
+ keyguardInteractor.isKeyguardOccluded,
+ keyguardInteractor.isKeyguardShowing,
+ primaryBouncerInteractor.isShowing,
+ alternateBouncerInteractor.isVisible,
+ ) { occluded, showing, primaryBouncerShowing, alternateBouncerVisible ->
+ occluded && showing && !primaryBouncerShowing && !alternateBouncerVisible
+ }
+ .distinctUntilChanged()
+ }
+
private val fingerprintUnlockSuccessEvents: Flow<Unit> =
fingerprintAuthRepository.authenticationStatus
.ifKeyguardOccludedByApp()
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt
index 931a869..ed82278 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt
@@ -163,6 +163,6 @@
}
companion object {
- const val KEY_UP_TIMEOUT = 100L
+ const val KEY_UP_TIMEOUT = 60L
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 2e233d8..3134e35 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -289,6 +289,8 @@
};
}
+ private final WindowManagerOcclusionManager mWmOcclusionManager;
+
@Inject
public KeyguardService(
KeyguardViewMediator keyguardViewMediator,
@@ -302,7 +304,8 @@
KeyguardSurfaceBehindParamsApplier keyguardSurfaceBehindAnimator,
@Application CoroutineScope scope,
FeatureFlags featureFlags,
- PowerInteractor powerInteractor) {
+ PowerInteractor powerInteractor,
+ WindowManagerOcclusionManager windowManagerOcclusionManager) {
super();
mKeyguardViewMediator = keyguardViewMediator;
mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher;
@@ -323,6 +326,8 @@
keyguardSurfaceBehindAnimator,
scope);
}
+
+ mWmOcclusionManager = windowManagerOcclusionManager;
}
@Override
@@ -414,7 +419,11 @@
Trace.beginSection("KeyguardService.mBinder#setOccluded");
checkPermission();
- mKeyguardViewMediator.setOccluded(isOccluded, animate);
+ if (!KeyguardWmStateRefactor.isEnabled()) {
+ mKeyguardViewMediator.setOccluded(isOccluded, animate);
+ } else {
+ mWmOcclusionManager.onKeyguardServiceSetOccluded(isOccluded);
+ }
Trace.endSection();
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 6d917bb..a37397d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -1365,7 +1365,9 @@
private final Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager;
+ private WindowManagerOcclusionManager mWmOcclusionManager;
/**
+
* Injected constructor. See {@link KeyguardModule}.
*/
public KeyguardViewMediator(
@@ -1411,7 +1413,8 @@
SystemPropertiesHelper systemPropertiesHelper,
Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager,
SelectedUserInteractor selectedUserInteractor,
- KeyguardInteractor keyguardInteractor) {
+ KeyguardInteractor keyguardInteractor,
+ WindowManagerOcclusionManager wmOcclusionManager) {
mContext = context;
mUserTracker = userTracker;
mFalsingCollector = falsingCollector;
@@ -1486,6 +1489,8 @@
mShowKeyguardWakeLock = mPM.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "show keyguard");
mShowKeyguardWakeLock.setReferenceCounted(false);
+
+ mWmOcclusionManager = wmOcclusionManager;
}
public void userActivity() {
@@ -2103,15 +2108,27 @@
}
public IRemoteAnimationRunner getOccludeAnimationRunner() {
- return validatingRemoteAnimationRunner(mOccludeAnimationRunner);
+ if (KeyguardWmStateRefactor.isEnabled()) {
+ return validatingRemoteAnimationRunner(mWmOcclusionManager.getOccludeAnimationRunner());
+ } else {
+ return validatingRemoteAnimationRunner(mOccludeAnimationRunner);
+ }
}
+ /**
+ * TODO(b/326464548): Move this to WindowManagerOcclusionManager
+ */
public IRemoteAnimationRunner getOccludeByDreamAnimationRunner() {
return validatingRemoteAnimationRunner(mOccludeByDreamAnimationRunner);
}
public IRemoteAnimationRunner getUnoccludeAnimationRunner() {
- return validatingRemoteAnimationRunner(mUnoccludeAnimationRunner);
+ if (KeyguardWmStateRefactor.isEnabled()) {
+ return validatingRemoteAnimationRunner(
+ mWmOcclusionManager.getUnoccludeAnimationRunner());
+ } else {
+ return validatingRemoteAnimationRunner(mUnoccludeAnimationRunner);
+ }
}
public boolean isHiding() {
@@ -2145,8 +2162,10 @@
if (mOccluded != isOccluded) {
mOccluded = isOccluded;
- mKeyguardViewControllerLazy.get().setOccluded(isOccluded, animate
- && mDeviceInteractive);
+ if (!KeyguardWmStateRefactor.isEnabled()) {
+ mKeyguardViewControllerLazy.get().setOccluded(isOccluded, animate
+ && mDeviceInteractive);
+ }
adjustStatusBarLocked();
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
index 8ebcece..00f5002 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
@@ -140,8 +140,14 @@
nonApps: Array<RemoteAnimationTarget>,
finishedCallback: IRemoteAnimationFinishedCallback
) {
- goingAwayRemoteAnimationFinishedCallback = finishedCallback
- keyguardSurfaceBehindAnimator.applyParamsToSurface(apps[0])
+ if (apps.isNotEmpty()) {
+ goingAwayRemoteAnimationFinishedCallback = finishedCallback
+ keyguardSurfaceBehindAnimator.applyParamsToSurface(apps[0])
+ } else {
+ // Nothing to do here if we have no apps, end the animation, which will cancel it and WM
+ // will make *something* visible.
+ finishedCallback.onAnimationFinished()
+ }
}
fun onKeyguardGoingAwayRemoteAnimationCancelled() {
@@ -174,13 +180,19 @@
* if so, true should be the right choice.
*/
private fun setWmLockscreenState(
- lockscreenShowing: Boolean = this.isLockscreenShowing ?: true.also {
- Log.d(TAG, "Using isLockscreenShowing=true default in setWmLockscreenState, " +
- "because setAodVisible was called before the first setLockscreenShown " +
- "call during boot. This is not typical, but is theoretically possible. " +
- "If you're investigating the lockscreen showing unexpectedly, start here.")
- },
- aodVisible: Boolean = this.isAodVisible
+ lockscreenShowing: Boolean =
+ this.isLockscreenShowing
+ ?: true.also {
+ Log.d(
+ TAG,
+ "Using isLockscreenShowing=true default in setWmLockscreenState, " +
+ "because setAodVisible was called before the first " +
+ "setLockscreenShown call during boot. This is not typical, but is " +
+ "theoretically possible. If you're investigating the lockscreen " +
+ "showing unexpectedly, start here."
+ )
+ },
+ aodVisible: Boolean = this.isAodVisible
) {
Log.d(
TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerOcclusionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerOcclusionManager.kt
new file mode 100644
index 0000000..aab90c3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerOcclusionManager.kt
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.content.Context
+import android.graphics.Matrix
+import android.os.RemoteException
+import android.util.Log
+import android.view.IRemoteAnimationFinishedCallback
+import android.view.IRemoteAnimationRunner
+import android.view.RemoteAnimationTarget
+import android.view.SyncRtSurfaceTransactionApplier
+import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+import androidx.annotation.VisibleForTesting
+import com.android.app.animation.Interpolators
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.policy.ScreenDecorationsUtils
+import com.android.keyguard.KeyguardViewController
+import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.animation.TransitionAnimator
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.domain.interactor.KeyguardOcclusionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.res.R
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+private val UNOCCLUDE_ANIMATION_DURATION = 250
+private val UNOCCLUDE_TRANSLATE_DISTANCE_PERCENT = 0.1f
+
+/**
+ * Keeps track of Window Manager's occlusion state and RemoteAnimations related to changes in
+ * occlusion state. Occlusion is when a [FLAG_SHOW_WHEN_LOCKED] activity is displaying over the
+ * lockscreen - we're still locked, but the user can interact with the activity.
+ *
+ * Typical "occlusion" use cases include launching the camera over the lockscreen, tapping a quick
+ * affordance to bring up Google Pay/Wallet/whatever it's called by the time you're reading this,
+ * and Maps Navigation.
+ *
+ * Window Manager considers the keyguard to be 'occluded' whenever a [FLAG_SHOW_WHEN_LOCKED]
+ * activity is on top of the task stack, even if the device is unlocked and the keyguard is not
+ * visible. System UI considers the keyguard to be [KeyguardState.OCCLUDED] only when we're on the
+ * keyguard and an activity is displaying over it.
+ *
+ * For all System UI use cases, you should use [KeyguardTransitionInteractor] to determine if we're
+ * in the [KeyguardState.OCCLUDED] state and react accordingly. If you are sure that you need to
+ * check whether Window Manager considers OCCLUDED=true even though the lockscreen is not showing,
+ * use [KeyguardShowWhenLockedActivityInteractor.isShowWhenLockedActivityOnTop] in combination with
+ * [KeyguardTransitionInteractor] state.
+ *
+ * This is a very sensitive piece of state that has caused many headaches in the past. Please be
+ * careful.
+ */
+@SysUISingleton
+class WindowManagerOcclusionManager
+@Inject
+constructor(
+ val keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
+ val activityTransitionAnimator: ActivityTransitionAnimator,
+ val keyguardViewController: dagger.Lazy<KeyguardViewController>,
+ val powerInteractor: PowerInteractor,
+ val context: Context,
+ val interactionJankMonitor: InteractionJankMonitor,
+ @Main executor: Executor,
+ val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
+ val occlusionInteractor: KeyguardOcclusionInteractor,
+) {
+ val powerButtonY =
+ context.resources.getDimensionPixelSize(
+ R.dimen.physical_power_button_center_screen_location_y
+ )
+ val windowCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
+
+ var occludeAnimationFinishedCallback: IRemoteAnimationFinishedCallback? = null
+
+ /**
+ * Animation runner provided to WindowManager, which will be used if an occluding activity is
+ * launched and Window Manager wants us to animate it in. This is used as a signal that we are
+ * now occluded, and should update our state accordingly.
+ */
+ val occludeAnimationRunner: IRemoteAnimationRunner =
+ object : IRemoteAnimationRunner.Stub() {
+ override fun onAnimationStart(
+ transit: Int,
+ apps: Array<RemoteAnimationTarget>,
+ wallpapers: Array<RemoteAnimationTarget>,
+ nonApps: Array<RemoteAnimationTarget>,
+ finishedCallback: IRemoteAnimationFinishedCallback?
+ ) {
+ Log.d(TAG, "occludeAnimationRunner#onAnimationStart")
+ // Wrap the callback so that it's guaranteed to be nulled out once called.
+ occludeAnimationFinishedCallback =
+ object : IRemoteAnimationFinishedCallback.Stub() {
+ override fun onAnimationFinished() {
+ finishedCallback?.onAnimationFinished()
+ occludeAnimationFinishedCallback = null
+ }
+ }
+ keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(
+ showWhenLockedActivityOnTop = true,
+ taskInfo = apps.firstOrNull()?.taskInfo,
+ )
+ activityTransitionAnimator
+ .createRunner(occludeAnimationController)
+ .onAnimationStart(
+ transit,
+ apps,
+ wallpapers,
+ nonApps,
+ occludeAnimationFinishedCallback,
+ )
+ }
+
+ override fun onAnimationCancelled() {
+ Log.d(TAG, "occludeAnimationRunner#onAnimationCancelled")
+ }
+ }
+
+ var unoccludeAnimationFinishedCallback: IRemoteAnimationFinishedCallback? = null
+
+ /**
+ * Animation runner provided to WindowManager, which will be used if an occluding activity is
+ * finished and Window Manager wants us to animate it out. This is used as a signal that we are
+ * no longer occluded, and should update our state accordingly.
+ *
+ * TODO(b/326464548): Restore dream specific animation.
+ */
+ val unoccludeAnimationRunner: IRemoteAnimationRunner =
+ object : IRemoteAnimationRunner.Stub() {
+ var unoccludeAnimator: ValueAnimator? = null
+ val unoccludeMatrix = Matrix()
+
+ /** TODO(b/326470033): Extract this logic into ViewModels. */
+ override fun onAnimationStart(
+ transit: Int,
+ apps: Array<RemoteAnimationTarget>,
+ wallpapers: Array<RemoteAnimationTarget>,
+ nonApps: Array<RemoteAnimationTarget>,
+ finishedCallback: IRemoteAnimationFinishedCallback?
+ ) {
+ Log.d(TAG, "unoccludeAnimationRunner#onAnimationStart")
+ // Wrap the callback so that it's guaranteed to be nulled out once called.
+ unoccludeAnimationFinishedCallback =
+ object : IRemoteAnimationFinishedCallback.Stub() {
+ override fun onAnimationFinished() {
+ finishedCallback?.onAnimationFinished()
+ unoccludeAnimationFinishedCallback = null
+ }
+ }
+ keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(
+ showWhenLockedActivityOnTop = false,
+ taskInfo = apps.firstOrNull()?.taskInfo,
+ )
+ interactionJankMonitor.begin(
+ createInteractionJankMonitorConf(
+ InteractionJankMonitor.CUJ_LOCKSCREEN_OCCLUSION,
+ "UNOCCLUDE"
+ )
+ )
+ if (apps.isEmpty()) {
+ Log.d(
+ TAG,
+ "No apps provided to unocclude runner; " +
+ "skipping animation and unoccluding."
+ )
+ unoccludeAnimationFinishedCallback?.onAnimationFinished()
+ return
+ }
+ val target = apps[0]
+ val localView: View = keyguardViewController.get().getViewRootImpl().getView()
+ val applier = SyncRtSurfaceTransactionApplier(localView)
+ // TODO(
+ executor.execute {
+ unoccludeAnimator?.cancel()
+ unoccludeAnimator =
+ ValueAnimator.ofFloat(1f, 0f).apply {
+ duration = UNOCCLUDE_ANIMATION_DURATION.toLong()
+ interpolator = Interpolators.TOUCH_RESPONSE
+ addUpdateListener { animation: ValueAnimator ->
+ val animatedValue = animation.animatedValue as Float
+ val surfaceHeight: Float =
+ target.screenSpaceBounds.height().toFloat()
+
+ unoccludeMatrix.setTranslate(
+ 0f,
+ (1f - animatedValue) *
+ surfaceHeight *
+ UNOCCLUDE_TRANSLATE_DISTANCE_PERCENT
+ )
+
+ SurfaceParams.Builder(target.leash)
+ .withAlpha(animatedValue)
+ .withMatrix(unoccludeMatrix)
+ .withCornerRadius(windowCornerRadius)
+ .build()
+ .also { applier.scheduleApply(it) }
+ }
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ try {
+ unoccludeAnimationFinishedCallback
+ ?.onAnimationFinished()
+ unoccludeAnimator = null
+ interactionJankMonitor.end(
+ InteractionJankMonitor.CUJ_LOCKSCREEN_OCCLUSION
+ )
+ } catch (e: RemoteException) {
+ e.printStackTrace()
+ }
+ }
+ }
+ )
+ start()
+ }
+ }
+ }
+
+ override fun onAnimationCancelled() {
+ Log.d(TAG, "unoccludeAnimationRunner#onAnimationCancelled")
+ context.mainExecutor.execute { unoccludeAnimator?.cancel() }
+ Log.d(TAG, "Unocclude animation cancelled.")
+ interactionJankMonitor.cancel(InteractionJankMonitor.CUJ_LOCKSCREEN_OCCLUSION)
+ }
+ }
+
+ /**
+ * Called when Window Manager tells the KeyguardService directly that we're occluded or not
+ * occluded, without starting an occlude/unocclude remote animation. This happens if occlusion
+ * state changes without an animation (such as if a SHOW_WHEN_LOCKED activity is launched while
+ * we're unlocked), or if an animation has been cancelled/interrupted and Window Manager wants
+ * to make sure that we're in the correct state.
+ */
+ fun onKeyguardServiceSetOccluded(occluded: Boolean) {
+ Log.d(TAG, "#onKeyguardServiceSetOccluded($occluded)")
+ keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(occluded)
+ }
+
+ @VisibleForTesting
+ val occludeAnimationController: ActivityTransitionAnimator.Controller =
+ object : ActivityTransitionAnimator.Controller {
+
+ override var transitionContainer: ViewGroup
+ get() = keyguardViewController.get().getViewRootImpl().view as ViewGroup
+ set(_) {
+ // Should never be set.
+ }
+
+ /** TODO(b/326470033): Extract this logic into ViewModels. */
+ override fun createAnimatorState(): TransitionAnimator.State {
+ val fullWidth = transitionContainer.width
+ val fullHeight = transitionContainer.height
+
+ if (
+ keyguardOcclusionInteractor.showWhenLockedActivityLaunchedFromPowerGesture.value
+ ) {
+ val initialHeight = fullHeight / 3f
+ val initialWidth = fullWidth / 3f
+
+ // Start the animation near the power button, at one-third size, since the
+ // camera was launched from the power button.
+ return TransitionAnimator.State(
+ top = (powerButtonY - initialHeight / 2f).toInt(),
+ bottom = (powerButtonY + initialHeight / 2f).toInt(),
+ left = (fullWidth - initialWidth).toInt(),
+ right = fullWidth,
+ topCornerRadius = windowCornerRadius,
+ bottomCornerRadius = windowCornerRadius,
+ )
+ } else {
+ val initialHeight = fullHeight / 2f
+ val initialWidth = fullWidth / 2f
+
+ // Start the animation in the center of the screen, scaled down to half
+ // size.
+ return TransitionAnimator.State(
+ top = (fullHeight - initialHeight).toInt() / 2,
+ bottom = (initialHeight + (fullHeight - initialHeight) / 2).toInt(),
+ left = (fullWidth - initialWidth).toInt() / 2,
+ right = (initialWidth + (fullWidth - initialWidth) / 2).toInt(),
+ topCornerRadius = windowCornerRadius,
+ bottomCornerRadius = windowCornerRadius,
+ )
+ }
+ }
+ }
+
+ private fun createInteractionJankMonitorConf(
+ cuj: Int,
+ tag: String?
+ ): InteractionJankMonitor.Configuration.Builder {
+ val builder =
+ InteractionJankMonitor.Configuration.Builder.withView(
+ cuj,
+ keyguardViewController.get().getViewRootImpl().view
+ )
+ return if (tag != null) builder.setTag(tag) else builder
+ }
+
+ companion object {
+ val TAG = "WindowManagerOcclusion"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 968c3e3..5306645 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -50,6 +50,7 @@
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager;
+import com.android.systemui.keyguard.WindowManagerOcclusionManager;
import com.android.systemui.keyguard.data.quickaffordance.KeyguardDataQuickAffordanceModule;
import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthModule;
import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
@@ -163,7 +164,8 @@
SystemPropertiesHelper systemPropertiesHelper,
Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager,
SelectedUserInteractor selectedUserInteractor,
- KeyguardInteractor keyguardInteractor) {
+ KeyguardInteractor keyguardInteractor,
+ WindowManagerOcclusionManager windowManagerOcclusionManager) {
return new KeyguardViewMediator(
context,
uiEventLogger,
@@ -209,7 +211,8 @@
systemPropertiesHelper,
wmLockscreenVisibilityManager,
selectedUserInteractor,
- keyguardInteractor);
+ keyguardInteractor,
+ windowManagerOcclusionManager);
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardOcclusionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardOcclusionRepository.kt
new file mode 100644
index 0000000..e3654b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardOcclusionRepository.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/**
+ * Information about the SHOW_WHEN_LOCKED activity that is either newly on top of the task stack, or
+ * newly not on top of the task stack.
+ */
+data class ShowWhenLockedActivityInfo(
+ /** Whether the activity is on top. If not, we're unoccluding and will be animating it out. */
+ val isOnTop: Boolean,
+
+ /**
+ * Information about the activity, which we use for transition internals and also to customize
+ * animations.
+ */
+ val taskInfo: RunningTaskInfo? = null
+) {
+ fun isDream(): Boolean {
+ return taskInfo?.topActivityType == WindowConfiguration.ACTIVITY_TYPE_DREAM
+ }
+}
+
+/**
+ * Maintains state about "occluding" activities - activities with FLAG_SHOW_WHEN_LOCKED, which are
+ * capable of displaying over the lockscreen while the device is still locked (such as Google Maps
+ * navigation).
+ *
+ * Window Manager considers the device to be in the "occluded" state whenever such an activity is on
+ * top of the task stack, including while we're unlocked, while keyguard code considers us to be
+ * occluded only when we're locked, with an occluding activity currently displaying over the
+ * lockscreen.
+ *
+ * This dual definition is confusing, so this repository collects all of the signals WM gives us,
+ * and consolidates them into [showWhenLockedActivityInfo.isOnTop], which is the actual question WM
+ * is answering when they say whether we're 'occluded'. Keyguard then uses this signal to
+ * conditionally transition to [KeyguardState.OCCLUDED] where appropriate.
+ */
+@SysUISingleton
+class KeyguardOcclusionRepository @Inject constructor() {
+ val showWhenLockedActivityInfo = MutableStateFlow(ShowWhenLockedActivityInfo(isOnTop = false))
+
+ /**
+ * Sets whether there's a SHOW_WHEN_LOCKED activity on top of the task stack, and optionally,
+ * information about the activity itself.
+ *
+ * If no value is provided for [taskInfo], we'll default to the current [taskInfo].
+ *
+ * The [taskInfo] is always present when this method is called from the occlude/unocclude
+ * animation runners. We use the default when calling from [KeyguardService.isOccluded], since
+ * we only receive a true/false value there. isOccluded is mostly redundant - it's almost always
+ * called with true after an occlusion animation has started, and with false after an unocclude
+ * animation has started. In those cases, we don't want to clear out the taskInfo just because
+ * it wasn't available at that call site.
+ */
+ fun setShowWhenLockedActivityInfo(
+ onTop: Boolean,
+ taskInfo: RunningTaskInfo? = showWhenLockedActivityInfo.value.taskInfo
+ ) {
+ showWhenLockedActivityInfo.value =
+ ShowWhenLockedActivityInfo(
+ isOnTop = onTop,
+ taskInfo = taskInfo,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 64e2870..3a6423d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -95,6 +95,7 @@
val isKeyguardShowing: Flow<Boolean>
/** Is an activity showing over the keyguard? */
+ @Deprecated("Use KeyguardTransitionInteractor + KeyguardState.OCCLUDED")
val isKeyguardOccluded: Flow<Boolean>
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
index 9b3f13d..d9479de 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
@@ -75,7 +75,6 @@
powerInteractor: PowerInteractor,
private val scrimLogger: ScrimLogger,
) : LightRevealScrimRepository {
-
companion object {
val TAG = LightRevealScrimRepository::class.simpleName!!
}
@@ -156,7 +155,11 @@
override fun startRevealAmountAnimator(reveal: Boolean) {
if (reveal == willBeOrIsRevealed) return
willBeOrIsRevealed = reveal
- if (reveal) revealAmountAnimator.start() else revealAmountAnimator.reverse()
+ if (reveal && !revealAmountAnimator.isRunning) {
+ revealAmountAnimator.start()
+ } else {
+ revealAmountAnimator.reverse()
+ }
scrimLogger.d(TAG, "startRevealAmountAnimator, reveal: ", reveal)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index 6aed944..88ddfd4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -21,6 +21,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -46,13 +47,16 @@
@Main mainDispatcher: CoroutineDispatcher,
private val keyguardInteractor: KeyguardInteractor,
private val communalInteractor: CommunalInteractor,
- private val powerInteractor: PowerInteractor,
+ powerInteractor: PowerInteractor,
+ keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.ALTERNATE_BOUNCER,
transitionInteractor = transitionInteractor,
mainDispatcher = mainDispatcher,
bgDispatcher = bgDispatcher,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
) {
override fun start() {
@@ -112,6 +116,11 @@
}
private fun listenForAlternateBouncerToGone() {
+ if (KeyguardWmStateRefactor.isEnabled) {
+ // Handled via #dismissAlternateBouncer.
+ return
+ }
+
scope.launch {
keyguardInteractor.isKeyguardGoingAway
.sampleUtil(finishedKeyguardState, ::Pair)
@@ -149,6 +158,10 @@
}
}
+ fun dismissAlternateBouncer() {
+ scope.launch { startTransitionTo(KeyguardState.GONE) }
+ }
+
companion object {
const val TAG = "FromAlternateBouncerTransitionInteractor"
val TRANSITION_DURATION_MS = 300.milliseconds
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index dbd5e26..9040e03 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -27,14 +27,17 @@
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
+import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.util.kotlin.Utils.Companion.sample
import com.android.systemui.util.kotlin.sample
+import java.util.UUID
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.debounce
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
@SysUISingleton
@@ -47,29 +50,118 @@
@Background bgDispatcher: CoroutineDispatcher,
@Main mainDispatcher: CoroutineDispatcher,
private val keyguardInteractor: KeyguardInteractor,
- private val powerInteractor: PowerInteractor,
+ powerInteractor: PowerInteractor,
+ keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.AOD,
transitionInteractor = transitionInteractor,
mainDispatcher = mainDispatcher,
bgDispatcher = bgDispatcher,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
) {
override fun start() {
- listenForAodToLockscreen()
+ listenForAodToAwake()
+ listenForAodToOccluded()
listenForAodToPrimaryBouncer()
listenForAodToGone()
- listenForAodToOccluded()
listenForTransitionToCamera(scope, keyguardInteractor)
}
/**
+ * Listen for the signal that we're waking up and figure what state we need to transition to.
+ */
+ private fun listenForAodToAwake() {
+ val transitionToLockscreen: suspend (TransitionStep) -> UUID? =
+ { startedStep: TransitionStep ->
+ val modeOnCanceled =
+ if (startedStep.from == KeyguardState.LOCKSCREEN) {
+ TransitionModeOnCanceled.REVERSE
+ } else if (startedStep.from == KeyguardState.GONE) {
+ TransitionModeOnCanceled.RESET
+ } else {
+ TransitionModeOnCanceled.LAST_VALUE
+ }
+ startTransitionTo(
+ toState = KeyguardState.LOCKSCREEN,
+ modeOnCanceled = modeOnCanceled,
+ )
+ }
+
+ if (KeyguardWmStateRefactor.isEnabled) {
+ // The refactor uses PowerInteractor's wakefulness, which is the earliest wake signal
+ // available. We have all of the information we need at this time to make a decision
+ // about where to transition.
+ scope.launch {
+ powerInteractor.detailedWakefulness
+ // React only to wake events.
+ .filter { it.isAwake() }
+ .sample(
+ startedKeyguardTransitionStep,
+ keyguardInteractor.biometricUnlockState,
+ keyguardInteractor.primaryBouncerShowing,
+ )
+ // Make sure we've at least STARTED a transition to AOD.
+ .filter { (_, startedStep, _, _) -> startedStep.to == KeyguardState.AOD }
+ .collect { (_, startedStep, biometricUnlockState, primaryBouncerShowing) ->
+ // Check with the superclass to see if an occlusion transition is needed.
+ // Also, don't react to wake and unlock events, as we'll be receiving a call
+ // to #dismissAod() shortly when the authentication completes.
+ if (
+ !maybeStartTransitionToOccludedOrInsecureCamera() &&
+ !isWakeAndUnlock(biometricUnlockState) &&
+ !primaryBouncerShowing
+ ) {
+ transitionToLockscreen(startedStep)
+ }
+ }
+ }
+ } else {
+ scope.launch {
+ keyguardInteractor
+ .dozeTransitionTo(DozeStateModel.FINISH)
+ .sample(
+ keyguardInteractor.isKeyguardShowing,
+ startedKeyguardTransitionStep,
+ keyguardInteractor.isKeyguardOccluded,
+ keyguardInteractor.biometricUnlockState,
+ keyguardInteractor.primaryBouncerShowing,
+ )
+ .collect {
+ (
+ _,
+ isKeyguardShowing,
+ lastStartedStep,
+ occluded,
+ biometricUnlockState,
+ primaryBouncerShowing) ->
+ if (
+ lastStartedStep.to == KeyguardState.AOD &&
+ !occluded &&
+ !isWakeAndUnlock(biometricUnlockState) &&
+ isKeyguardShowing &&
+ !primaryBouncerShowing
+ ) {
+ transitionToLockscreen(lastStartedStep)
+ }
+ }
+ }
+ }
+ }
+
+ /**
* There are cases where the transition to AOD begins but never completes, such as tapping power
* during an incoming phone call when unlocked. In this case, GONE->AOD should be interrupted to
* run AOD->OCCLUDED.
*/
private fun listenForAodToOccluded() {
+ if (KeyguardWmStateRefactor.isEnabled) {
+ // Handled by calls to maybeStartTransitionToOccludedOrInsecureCamera on waking.
+ return
+ }
+
scope.launch {
keyguardInteractor.isKeyguardOccluded
.sample(startedKeyguardTransitionStep, ::Pair)
@@ -84,49 +176,6 @@
}
}
- private fun listenForAodToLockscreen() {
- scope.launch {
- keyguardInteractor
- .dozeTransitionTo(DozeStateModel.FINISH)
- .sample(
- keyguardInteractor.isKeyguardShowing,
- startedKeyguardTransitionStep,
- keyguardInteractor.isKeyguardOccluded,
- keyguardInteractor.biometricUnlockState,
- keyguardInteractor.primaryBouncerShowing,
- )
- .collect {
- (
- _,
- isKeyguardShowing,
- lastStartedStep,
- occluded,
- biometricUnlockState,
- primaryBouncerShowing) ->
- if (
- lastStartedStep.to == KeyguardState.AOD &&
- !occluded &&
- !isWakeAndUnlock(biometricUnlockState) &&
- isKeyguardShowing &&
- !primaryBouncerShowing
- ) {
- val modeOnCanceled =
- if (lastStartedStep.from == KeyguardState.LOCKSCREEN) {
- TransitionModeOnCanceled.REVERSE
- } else if (lastStartedStep.from == KeyguardState.GONE) {
- TransitionModeOnCanceled.RESET
- } else {
- TransitionModeOnCanceled.LAST_VALUE
- }
- startTransitionTo(
- toState = KeyguardState.LOCKSCREEN,
- modeOnCanceled = modeOnCanceled,
- )
- }
- }
- }
- }
-
/**
* If there is a biometric lockout and FPS is tapped while on AOD, it should go directly to the
* PRIMARY_BOUNCER.
@@ -145,6 +194,7 @@
private fun listenForAodToGone() {
if (KeyguardWmStateRefactor.isEnabled) {
+ // Handled via #dismissAod.
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index 8591fe7..57b2a63 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -22,6 +22,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -34,6 +35,7 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
@SysUISingleton
@@ -46,18 +48,22 @@
@Background bgDispatcher: CoroutineDispatcher,
@Main mainDispatcher: CoroutineDispatcher,
private val keyguardInteractor: KeyguardInteractor,
- private val powerInteractor: PowerInteractor,
+ powerInteractor: PowerInteractor,
private val communalInteractor: CommunalInteractor,
+ keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.DOZING,
transitionInteractor = transitionInteractor,
mainDispatcher = mainDispatcher,
bgDispatcher = bgDispatcher,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
) {
override fun start() {
listenForDozingToAny()
+ listenForWakeFromDozing()
listenForTransitionToCamera(scope, keyguardInteractor)
}
@@ -70,6 +76,10 @@
}
private fun listenForDozingToAny() {
+ if (KeyguardWmStateRefactor.isEnabled) {
+ return
+ }
+
scope.launch {
powerInteractor.isAwake
.debounce(50L)
@@ -112,6 +122,58 @@
}
}
+ /** Figure out what state to transition to when we awake from DOZING. */
+ private fun listenForWakeFromDozing() {
+ if (!KeyguardWmStateRefactor.isEnabled) {
+ return
+ }
+
+ scope.launch {
+ powerInteractor.detailedWakefulness
+ .filter { it.isAwake() }
+ .sample(
+ startedKeyguardTransitionStep,
+ communalInteractor.isIdleOnCommunal,
+ keyguardInteractor.biometricUnlockState,
+ canDismissLockScreen,
+ keyguardInteractor.primaryBouncerShowing,
+ )
+ // If we haven't at least STARTED a transition to DOZING, ignore.
+ .filter { (_, startedStep, _, _) -> startedStep.to == KeyguardState.DOZING }
+ .collect {
+ (
+ _,
+ _,
+ isIdleOnCommunal,
+ biometricUnlockState,
+ canDismissLockscreen,
+ primaryBouncerShowing) ->
+ if (
+ !maybeStartTransitionToOccludedOrInsecureCamera() &&
+ // Handled by dismissFromDozing().
+ !isWakeAndUnlock(biometricUnlockState)
+ ) {
+ startTransitionTo(
+ if (canDismissLockscreen) {
+ KeyguardState.GONE
+ } else if (primaryBouncerShowing) {
+ KeyguardState.PRIMARY_BOUNCER
+ } else if (isIdleOnCommunal) {
+ KeyguardState.GLANCEABLE_HUB
+ } else {
+ KeyguardState.LOCKSCREEN
+ }
+ )
+ }
+ }
+ }
+ }
+
+ /** Dismisses keyguard from the DOZING state. */
+ fun dismissFromDozing() {
+ scope.launch { startTransitionTo(KeyguardState.GONE) }
+ }
+
override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
return ValueAnimator().apply {
interpolator = Interpolators.LINEAR
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
index a6cdaa8..6433d0e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
@@ -25,6 +25,7 @@
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
@@ -46,12 +47,16 @@
@Background bgDispatcher: CoroutineDispatcher,
@Main mainDispatcher: CoroutineDispatcher,
private val keyguardInteractor: KeyguardInteractor,
+ powerInteractor: PowerInteractor,
+ keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
transitionInteractor = transitionInteractor,
mainDispatcher = mainDispatcher,
bgDispatcher = bgDispatcher,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
) {
override fun start() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index acfa107..3137138 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -23,10 +23,14 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.util.kotlin.Utils
+import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
@@ -34,6 +38,7 @@
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
@SysUISingleton
@@ -47,17 +52,23 @@
@Main mainDispatcher: CoroutineDispatcher,
private val keyguardInteractor: KeyguardInteractor,
private val glanceableHubTransitions: GlanceableHubTransitions,
+ powerInteractor: PowerInteractor,
+ keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.DREAMING,
transitionInteractor = transitionInteractor,
mainDispatcher = mainDispatcher,
bgDispatcher = bgDispatcher,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
) {
override fun start() {
listenForDreamingToOccluded()
- listenForDreamingToGone()
+ listenForDreamingToGoneWhenDismissable()
+ listenForDreamingToGoneFromBiometricUnlock()
+ listenForDreamingToLockscreen()
listenForDreamingToAodOrDozing()
listenForTransitionToCamera(scope, keyguardInteractor)
listenForDreamingToGlanceableHub()
@@ -76,6 +87,7 @@
fun startToLockscreenTransition() {
scope.launch {
+ KeyguardWmStateRefactor.isUnexpectedlyInLegacyMode()
if (
transitionInteractor.startedKeyguardState.replayCache.last() ==
KeyguardState.DREAMING
@@ -86,22 +98,80 @@
}
private fun listenForDreamingToOccluded() {
+ if (KeyguardWmStateRefactor.isEnabled) {
+ scope.launch {
+ combine(
+ keyguardInteractor.isDreaming,
+ keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop,
+ ::Pair
+ )
+ .sample(startedKeyguardTransitionStep, ::toTriple)
+ .filter { (isDreaming, _, startedStep) ->
+ !isDreaming && startedStep.to == KeyguardState.DREAMING
+ }
+ .collect { maybeStartTransitionToOccludedOrInsecureCamera() }
+ }
+ } else {
+ scope.launch {
+ combine(
+ keyguardInteractor.isKeyguardOccluded,
+ keyguardInteractor.isDreaming,
+ ::Pair
+ )
+ .sample(startedKeyguardTransitionStep, Utils.Companion::toTriple)
+ .collect { (isOccluded, isDreaming, lastStartedTransition) ->
+ if (
+ isOccluded &&
+ !isDreaming &&
+ lastStartedTransition.to == KeyguardState.DREAMING
+ ) {
+ startTransitionTo(KeyguardState.OCCLUDED)
+ }
+ }
+ }
+ }
+ }
+
+ private fun listenForDreamingToLockscreen() {
+ if (!KeyguardWmStateRefactor.isEnabled) {
+ return
+ }
+
scope.launch {
- combine(keyguardInteractor.isKeyguardOccluded, keyguardInteractor.isDreaming, ::Pair)
- .sample(startedKeyguardTransitionStep, ::toTriple)
- .collect { (isOccluded, isDreaming, lastStartedTransition) ->
- if (
- isOccluded &&
- !isDreaming &&
- lastStartedTransition.to == KeyguardState.DREAMING
- ) {
- startTransitionTo(KeyguardState.OCCLUDED)
+ keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop
+ .filter { onTop -> !onTop }
+ .sample(startedKeyguardState)
+ .collect { startedState ->
+ if (startedState == KeyguardState.DREAMING) {
+ startTransitionTo(KeyguardState.LOCKSCREEN)
}
}
}
}
- private fun listenForDreamingToGone() {
+ private fun listenForDreamingToGoneWhenDismissable() {
+ scope.launch {
+ keyguardInteractor.isAbleToDream
+ .sampleCombine(
+ keyguardInteractor.isKeyguardShowing,
+ keyguardInteractor.isKeyguardDismissible,
+ startedKeyguardTransitionStep,
+ )
+ .collect {
+ (isDreaming, isKeyguardShowing, isKeyguardDismissible, lastStartedTransition) ->
+ if (
+ !isDreaming &&
+ lastStartedTransition.to == KeyguardState.DREAMING &&
+ isKeyguardDismissible &&
+ !isKeyguardShowing
+ ) {
+ startTransitionTo(KeyguardState.GONE)
+ }
+ }
+ }
+ }
+
+ private fun listenForDreamingToGoneFromBiometricUnlock() {
scope.launch {
keyguardInteractor.biometricUnlockState
.sample(startedKeyguardTransitionStep, ::Pair)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
index 786c3c6..51bc3ae 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -23,6 +23,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
@@ -35,6 +36,7 @@
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -49,14 +51,18 @@
private val keyguardInteractor: KeyguardInteractor,
override val transitionRepository: KeyguardTransitionRepository,
transitionInteractor: KeyguardTransitionInteractor,
- private val powerInteractor: PowerInteractor,
+ powerInteractor: PowerInteractor,
+ keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.GLANCEABLE_HUB,
transitionInteractor = transitionInteractor,
mainDispatcher = mainDispatcher,
bgDispatcher = bgDispatcher,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
) {
+
override fun start() {
if (!Flags.communalHub()) {
return
@@ -151,14 +157,27 @@
}
private fun listenForHubToOccluded() {
- scope.launch {
- and(keyguardInteractor.isKeyguardOccluded, not(keyguardInteractor.isDreaming))
- .sample(startedKeyguardState, ::Pair)
- .collect { (isOccludedAndNotDreaming, keyguardState) ->
- if (isOccludedAndNotDreaming && keyguardState == fromState) {
- startTransitionTo(KeyguardState.OCCLUDED)
+ if (KeyguardWmStateRefactor.isEnabled) {
+ scope.launch {
+ keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop
+ .filter { onTop -> onTop }
+ .sample(startedKeyguardState)
+ .collect {
+ if (it == KeyguardState.GLANCEABLE_HUB) {
+ maybeStartTransitionToOccludedOrInsecureCamera()
+ }
}
- }
+ }
+ } else {
+ scope.launch {
+ and(keyguardInteractor.isKeyguardOccluded, not(keyguardInteractor.isDreaming))
+ .sample(startedKeyguardState, ::Pair)
+ .collect { (isOccludedAndNotDreaming, keyguardState) ->
+ if (isOccludedAndNotDreaming && keyguardState == fromState) {
+ startTransitionTo(KeyguardState.OCCLUDED)
+ }
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index 7593ac2..d5a9bd1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -22,6 +22,8 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
@@ -34,6 +36,8 @@
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
@SysUISingleton
@@ -46,14 +50,18 @@
@Background bgDispatcher: CoroutineDispatcher,
@Main mainDispatcher: CoroutineDispatcher,
private val keyguardInteractor: KeyguardInteractor,
- private val powerInteractor: PowerInteractor,
+ powerInteractor: PowerInteractor,
private val communalInteractor: CommunalInteractor,
+ keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
+ private val biometricSettingsRepository: BiometricSettingsRepository,
) :
TransitionInteractor(
fromState = KeyguardState.GONE,
transitionInteractor = transitionInteractor,
mainDispatcher = mainDispatcher,
bgDispatcher = bgDispatcher,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
) {
override fun start() {
@@ -65,23 +73,46 @@
// Primarily for when the user chooses to lock down the device
private fun listenForGoneToLockscreenOrHub() {
- scope.launch {
- keyguardInteractor.isKeyguardShowing
- .sample(
- startedKeyguardState,
- communalInteractor.isIdleOnCommunal,
- )
- .collect { (isKeyguardShowing, startedState, isIdleOnCommunal) ->
- if (isKeyguardShowing && startedState == KeyguardState.GONE) {
- val to =
- if (isIdleOnCommunal) {
- KeyguardState.GLANCEABLE_HUB
- } else {
- KeyguardState.LOCKSCREEN
- }
- startTransitionTo(to)
+ if (KeyguardWmStateRefactor.isEnabled) {
+ scope.launch {
+ biometricSettingsRepository.isCurrentUserInLockdown
+ .distinctUntilChanged()
+ .filter { inLockdown -> inLockdown }
+ .sample(
+ startedKeyguardState,
+ communalInteractor.isIdleOnCommunal,
+ )
+ .collect { (_, startedState, isIdleOnCommunal) ->
+ if (startedState == KeyguardState.GONE) {
+ val to =
+ if (isIdleOnCommunal) {
+ KeyguardState.GLANCEABLE_HUB
+ } else {
+ KeyguardState.LOCKSCREEN
+ }
+ startTransitionTo(to, ownerReason = "User initiated lockdown")
+ }
}
- }
+ }
+ } else {
+ scope.launch {
+ keyguardInteractor.isKeyguardShowing
+ .sample(
+ startedKeyguardState,
+ communalInteractor.isIdleOnCommunal,
+ )
+ .collect { (isKeyguardShowing, startedState, isIdleOnCommunal) ->
+ if (isKeyguardShowing && startedState == KeyguardState.GONE) {
+ val to =
+ if (isIdleOnCommunal) {
+ KeyguardState.GLANCEABLE_HUB
+ } else {
+ KeyguardState.LOCKSCREEN
+ }
+ startTransitionTo(to)
+ }
+ }
+ }
}
}
@@ -122,24 +153,10 @@
private fun listenForGoneToAodOrDozing() {
scope.launch {
- powerInteractor.isAsleep
- .sample(
- combine(
- startedKeyguardTransitionStep,
- keyguardInteractor.isAodAvailable,
- ::Pair
- ),
- ::toTriple
- )
- .collect { (isAsleep, lastStartedStep, isAodAvailable) ->
- if (lastStartedStep.to == KeyguardState.GONE && isAsleep) {
- startTransitionTo(
- toState =
- if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING,
- modeOnCanceled = TransitionModeOnCanceled.RESET,
- )
- }
- }
+ listenForSleepTransition(
+ from = KeyguardState.GONE,
+ modeOnCanceledFromStartedStep = { TransitionModeOnCanceled.RESET },
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index cb1571e..bcad332 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -33,7 +33,6 @@
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.util.kotlin.Utils.Companion.toQuad
-import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
import java.util.UUID
import javax.inject.Inject
@@ -44,6 +43,7 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
@@ -62,21 +62,24 @@
private val keyguardInteractor: KeyguardInteractor,
private val flags: FeatureFlags,
private val shadeRepository: ShadeRepository,
- private val powerInteractor: PowerInteractor,
+ powerInteractor: PowerInteractor,
private val glanceableHubTransitions: GlanceableHubTransitions,
private val swipeToDismissInteractor: SwipeToDismissInteractor,
+ keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.LOCKSCREEN,
transitionInteractor = transitionInteractor,
mainDispatcher = mainDispatcher,
bgDispatcher = bgDispatcher,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
) {
override fun start() {
listenForLockscreenToGone()
listenForLockscreenToGoneDragging()
- listenForLockscreenToOccluded()
+ listenForLockscreenToOccludedOrDreaming()
listenForLockscreenToAodOrDozing()
listenForLockscreenToPrimaryBouncer()
listenForLockscreenToDreaming()
@@ -115,6 +118,10 @@
}
private fun listenForLockscreenToDreaming() {
+ if (KeyguardWmStateRefactor.isEnabled) {
+ return
+ }
+
val invalidFromStates = setOf(KeyguardState.AOD, KeyguardState.DOZING)
scope.launch {
keyguardInteractor.isAbleToDream
@@ -157,7 +164,10 @@
if (
isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.LOCKSCREEN
) {
- startTransitionTo(KeyguardState.PRIMARY_BOUNCER)
+ startTransitionTo(
+ KeyguardState.PRIMARY_BOUNCER,
+ ownerReason = "#listenForLockscreenToPrimaryBouncer"
+ )
}
}
}
@@ -254,7 +264,8 @@
transitionId =
startTransitionTo(
toState = KeyguardState.PRIMARY_BOUNCER,
- animator = null, // transition will be manually controlled
+ animator = null, // transition will be manually controlled,
+ ownerReason = "#listenForLockscreenToPrimaryBouncerDragging"
)
}
}
@@ -309,47 +320,51 @@
}
}
- private fun listenForLockscreenToOccluded() {
- scope.launch {
- keyguardInteractor.isKeyguardOccluded.sample(startedKeyguardState, ::Pair).collect {
- (isOccluded, keyguardState) ->
- if (isOccluded && keyguardState == KeyguardState.LOCKSCREEN) {
- startTransitionTo(KeyguardState.OCCLUDED)
- }
+ private fun listenForLockscreenToOccludedOrDreaming() {
+ if (KeyguardWmStateRefactor.isEnabled) {
+ scope.launch {
+ keyguardOcclusionInteractor.showWhenLockedActivityInfo
+ .filter { it.isOnTop }
+ .sample(startedKeyguardState, ::Pair)
+ .collect { (taskInfo, startedState) ->
+ if (startedState == KeyguardState.LOCKSCREEN) {
+ startTransitionTo(
+ if (taskInfo.isDream()) {
+ KeyguardState.DREAMING
+ } else {
+ KeyguardState.OCCLUDED
+ }
+ )
+ }
+ }
+ }
+ } else {
+ scope.launch {
+ keyguardInteractor.isKeyguardOccluded
+ .sample(startedKeyguardState, ::Pair)
+ .collect { (isOccluded, keyguardState) ->
+ if (isOccluded && keyguardState == KeyguardState.LOCKSCREEN) {
+ startTransitionTo(KeyguardState.OCCLUDED)
+ }
+ }
}
}
}
private fun listenForLockscreenToAodOrDozing() {
scope.launch {
- powerInteractor.isAsleep
- .sample(
- combine(
- startedKeyguardTransitionStep,
- keyguardInteractor.isAodAvailable,
- ::Pair
- ),
- ::toTriple
- )
- .collect { (isAsleep, lastStartedStep, isAodAvailable) ->
- if (lastStartedStep.to == KeyguardState.LOCKSCREEN && isAsleep) {
- val toState =
- if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING
- val modeOnCanceled =
- if (
- toState == KeyguardState.AOD &&
- lastStartedStep.from == KeyguardState.AOD
- ) {
- TransitionModeOnCanceled.REVERSE
- } else {
- TransitionModeOnCanceled.LAST_VALUE
- }
- startTransitionTo(
- toState = toState,
- modeOnCanceled = modeOnCanceled,
- )
+ listenForSleepTransition(
+ from = KeyguardState.LOCKSCREEN,
+ modeOnCanceledFromStartedStep = { startedStep ->
+ if (
+ startedStep.to == KeyguardState.AOD && startedStep.from == KeyguardState.AOD
+ ) {
+ TransitionModeOnCanceled.REVERSE
+ } else {
+ TransitionModeOnCanceled.LAST_VALUE
}
}
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index efb604d..f10327e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -22,17 +22,17 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.util.kotlin.Utils.Companion.sample
-import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
@SysUISingleton
@@ -45,20 +45,23 @@
@Background bgDispatcher: CoroutineDispatcher,
@Main mainDispatcher: CoroutineDispatcher,
private val keyguardInteractor: KeyguardInteractor,
- private val powerInteractor: PowerInteractor,
+ powerInteractor: PowerInteractor,
private val communalInteractor: CommunalInteractor,
+ keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.OCCLUDED,
transitionInteractor = transitionInteractor,
mainDispatcher = mainDispatcher,
bgDispatcher = bgDispatcher,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
) {
override fun start() {
listenForOccludedToLockscreenOrHub()
listenForOccludedToDreaming()
- listenForOccludedToAodOrDozing()
+ listenForOccludedToAsleep()
listenForOccludedToGone()
listenForOccludedToAlternateBouncer()
listenForOccludedToPrimaryBouncer()
@@ -90,43 +93,72 @@
}
private fun listenForOccludedToLockscreenOrHub() {
+ if (KeyguardWmStateRefactor.isEnabled) {
+ scope.launch {
+ keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop
+ .filter { onTop -> !onTop }
+ .sample(
+ startedKeyguardState,
+ communalInteractor.isIdleOnCommunal,
+ )
+ .collect { (_, startedState, isIdleOnCommunal) ->
+ // Occlusion signals come from the framework, and should interrupt any
+ // existing transition
+ if (startedState == KeyguardState.OCCLUDED) {
+ val to =
+ if (isIdleOnCommunal) {
+ KeyguardState.GLANCEABLE_HUB
+ } else {
+ KeyguardState.LOCKSCREEN
+ }
+ startTransitionTo(to)
+ }
+ }
+ }
+ } else {
+ scope.launch {
+ keyguardInteractor.isKeyguardOccluded
+ .sample(
+ keyguardInteractor.isKeyguardShowing,
+ startedKeyguardTransitionStep,
+ communalInteractor.isIdleOnCommunal,
+ )
+ .collect { (isOccluded, isShowing, lastStartedKeyguardState, isIdleOnCommunal)
+ ->
+ // Occlusion signals come from the framework, and should interrupt any
+ // existing transition
+ if (
+ !isOccluded &&
+ isShowing &&
+ lastStartedKeyguardState.to == KeyguardState.OCCLUDED
+ ) {
+ val to =
+ if (isIdleOnCommunal) {
+ KeyguardState.GLANCEABLE_HUB
+ } else {
+ KeyguardState.LOCKSCREEN
+ }
+ startTransitionTo(to)
+ }
+ }
+ }
+ }
+ }
+
+ private fun listenForOccludedToGone() {
+ if (KeyguardWmStateRefactor.isEnabled) {
+ // We don't think OCCLUDED to GONE is possible. You should always have to go via a
+ // *_BOUNCER state to end up GONE. Launching an activity over a dismissable keyguard
+ // dismisses it, and even "extend unlock" doesn't unlock the device in the background.
+ // If we're wrong - sorry, add it back here.
+ return
+ }
+
scope.launch {
keyguardInteractor.isKeyguardOccluded
.sample(
keyguardInteractor.isKeyguardShowing,
startedKeyguardTransitionStep,
- communalInteractor.isIdleOnCommunal,
- )
- .collect { (isOccluded, isShowing, lastStartedKeyguardState, isIdleOnCommunal) ->
- // Occlusion signals come from the framework, and should interrupt any
- // existing transition
- if (
- !isOccluded &&
- isShowing &&
- lastStartedKeyguardState.to == KeyguardState.OCCLUDED
- ) {
- val to =
- if (isIdleOnCommunal) {
- KeyguardState.GLANCEABLE_HUB
- } else {
- KeyguardState.LOCKSCREEN
- }
- startTransitionTo(to)
- }
- }
- }
- }
-
- private fun listenForOccludedToGone() {
- scope.launch {
- keyguardInteractor.isKeyguardOccluded
- .sample(
- combine(
- keyguardInteractor.isKeyguardShowing,
- startedKeyguardTransitionStep,
- ::Pair
- ),
- ::toTriple
)
.collect { (isOccluded, isShowing, lastStartedKeyguardState) ->
// Occlusion signals come from the framework, and should interrupt any
@@ -142,25 +174,12 @@
}
}
- private fun listenForOccludedToAodOrDozing() {
- scope.launch {
- powerInteractor.isAsleep
- .sample(
- combine(
- startedKeyguardTransitionStep,
- keyguardInteractor.isAodAvailable,
- ::Pair
- ),
- ::toTriple
- )
- .collect { (isAsleep, lastStartedStep, isAodAvailable) ->
- if (lastStartedStep.to == KeyguardState.OCCLUDED && isAsleep) {
- startTransitionTo(
- if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING
- )
- }
- }
- }
+ fun dismissToGone() {
+ scope.launch { startTransitionTo(KeyguardState.GONE) }
+ }
+
+ private fun listenForOccludedToAsleep() {
+ scope.launch { listenForSleepTransition(from = KeyguardState.OCCLUDED) }
}
private fun listenForOccludedToAlternateBouncer() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index c5a2846..391dccc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -31,7 +31,6 @@
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.kotlin.Utils.Companion.sample
-import com.android.systemui.util.kotlin.Utils.Companion.toQuad
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
import com.android.wm.shell.animation.Interpolators
@@ -42,6 +41,7 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
@@ -59,18 +59,21 @@
private val flags: FeatureFlags,
private val keyguardSecurityModel: KeyguardSecurityModel,
private val selectedUserInteractor: SelectedUserInteractor,
- private val powerInteractor: PowerInteractor,
+ powerInteractor: PowerInteractor,
+ keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.PRIMARY_BOUNCER,
transitionInteractor = transitionInteractor,
mainDispatcher = mainDispatcher,
bgDispatcher = bgDispatcher,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
) {
override fun start() {
listenForPrimaryBouncerToGone()
- listenForPrimaryBouncerToAodOrDozing()
+ listenForPrimaryBouncerToAsleep()
listenForPrimaryBouncerToLockscreenHubOrOccluded()
listenForPrimaryBouncerToDreamingLockscreenHosted()
listenForTransitionToCamera(scope, keyguardInteractor)
@@ -128,72 +131,86 @@
}
private fun listenForPrimaryBouncerToLockscreenHubOrOccluded() {
- scope.launch {
- keyguardInteractor.primaryBouncerShowing
- .sample(
- powerInteractor.isAwake,
- startedKeyguardTransitionStep,
- keyguardInteractor.isKeyguardOccluded,
- keyguardInteractor.isDreaming,
- keyguardInteractor.isActiveDreamLockscreenHosted,
- communalInteractor.isIdleOnCommunal,
- )
- .collect {
- (
- isBouncerShowing,
- isAwake,
- lastStartedTransitionStep,
- occluded,
- isDreaming,
- isActiveDreamLockscreenHosted,
- isIdleOnCommunal) ->
- if (
- !isBouncerShowing &&
- lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER &&
- isAwake &&
- !isActiveDreamLockscreenHosted
- ) {
- val toState =
- if (occluded && !isDreaming) {
- KeyguardState.OCCLUDED
- } else if (isIdleOnCommunal) {
- KeyguardState.GLANCEABLE_HUB
- } else if (isDreaming) {
- KeyguardState.DREAMING
- } else {
- KeyguardState.LOCKSCREEN
- }
- startTransitionTo(toState)
+ if (KeyguardWmStateRefactor.isEnabled) {
+ scope.launch {
+ keyguardInteractor.primaryBouncerShowing
+ .sample(
+ startedKeyguardTransitionStep,
+ powerInteractor.isAwake,
+ keyguardInteractor.isActiveDreamLockscreenHosted,
+ communalInteractor.isIdleOnCommunal
+ )
+ .filter { (_, startedStep, _, _) ->
+ startedStep.to == KeyguardState.PRIMARY_BOUNCER
}
- }
+ .collect {
+ (
+ isBouncerShowing,
+ _,
+ isAwake,
+ isActiveDreamLockscreenHosted,
+ isIdleOnCommunal) ->
+ if (
+ !maybeStartTransitionToOccludedOrInsecureCamera() &&
+ !isBouncerShowing &&
+ isAwake &&
+ !isActiveDreamLockscreenHosted
+ ) {
+ val toState =
+ if (isIdleOnCommunal) {
+ KeyguardState.GLANCEABLE_HUB
+ } else {
+ KeyguardState.LOCKSCREEN
+ }
+ startTransitionTo(toState)
+ }
+ }
+ }
+ } else {
+ scope.launch {
+ keyguardInteractor.primaryBouncerShowing
+ .sample(
+ powerInteractor.isAwake,
+ startedKeyguardTransitionStep,
+ keyguardInteractor.isKeyguardOccluded,
+ keyguardInteractor.isDreaming,
+ keyguardInteractor.isActiveDreamLockscreenHosted,
+ communalInteractor.isIdleOnCommunal,
+ )
+ .collect {
+ (
+ isBouncerShowing,
+ isAwake,
+ lastStartedTransitionStep,
+ occluded,
+ isDreaming,
+ isActiveDreamLockscreenHosted,
+ isIdleOnCommunal) ->
+ if (
+ !isBouncerShowing &&
+ lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER &&
+ isAwake &&
+ !isActiveDreamLockscreenHosted
+ ) {
+ val toState =
+ if (occluded && !isDreaming) {
+ KeyguardState.OCCLUDED
+ } else if (isIdleOnCommunal) {
+ KeyguardState.GLANCEABLE_HUB
+ } else if (isDreaming) {
+ KeyguardState.DREAMING
+ } else {
+ KeyguardState.LOCKSCREEN
+ }
+ startTransitionTo(toState)
+ }
+ }
+ }
}
}
- private fun listenForPrimaryBouncerToAodOrDozing() {
- scope.launch {
- keyguardInteractor.primaryBouncerShowing
- .sample(
- combine(
- powerInteractor.isAsleep,
- startedKeyguardTransitionStep,
- keyguardInteractor.isAodAvailable,
- ::Triple
- ),
- ::toQuad
- )
- .collect { (isBouncerShowing, isAsleep, lastStartedTransitionStep, isAodAvailable)
- ->
- if (
- !isBouncerShowing &&
- lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER &&
- isAsleep
- ) {
- startTransitionTo(
- if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING
- )
- }
- }
- }
+ private fun listenForPrimaryBouncerToAsleep() {
+ scope.launch { listenForSleepTransition(from = KeyguardState.PRIMARY_BOUNCER) }
}
private fun listenForPrimaryBouncerToDreamingLockscreenHosted() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 5410b10..f321bd7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -163,15 +163,18 @@
.distinctUntilChanged()
/** Whether the keyguard is showing or not. */
+ @Deprecated("Use KeyguardTransitionInteractor + KeyguardState")
val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
/** Whether the keyguard is dismissible or not. */
val isKeyguardDismissible: Flow<Boolean> = repository.isKeyguardDismissible
/** Whether the keyguard is occluded (covered by an activity). */
+ @Deprecated("Use KeyguardTransitionInteractor + KeyguardState.OCCLUDED")
val isKeyguardOccluded: Flow<Boolean> = repository.isKeyguardOccluded
/** Whether the keyguard is going away. */
+ @Deprecated("Use KeyguardTransitionInteractor + KeyguardState.GONE")
val isKeyguardGoingAway: Flow<Boolean> = repository.isKeyguardGoingAway
/** Keyguard can be clipped at the top as the shade is dragged */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt
new file mode 100644
index 0000000..9aa2202
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.app.ActivityManager.RunningTaskInfo
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardOcclusionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Logic related to keyguard occlusion. The keyguard is occluded when an activity with
+ * FLAG_SHOW_WHEN_LOCKED is on top of the activity task stack, with that activity displaying on top
+ * of ("occluding") the lockscreen UI. Common examples of this are Google Maps Navigation and the
+ * secure camera.
+ *
+ * This should usually be used only by keyguard internal classes. Most System UI use cases should
+ * use [KeyguardTransitionInteractor] to see if we're in [KeyguardState.OCCLUDED] instead.
+ */
+@SysUISingleton
+class KeyguardOcclusionInteractor
+@Inject
+constructor(
+ @Application scope: CoroutineScope,
+ val repository: KeyguardOcclusionRepository,
+ val powerInteractor: PowerInteractor,
+ val transitionInteractor: KeyguardTransitionInteractor,
+ val keyguardInteractor: KeyguardInteractor,
+) {
+ val showWhenLockedActivityInfo = repository.showWhenLockedActivityInfo.asStateFlow()
+
+ /**
+ * Whether a SHOW_WHEN_LOCKED activity is on top of the task stack. This does not necessarily
+ * mean we're OCCLUDED, as we could be GONE (unlocked), with an activity that can (but is not
+ * currently) displaying over the lockscreen.
+ *
+ * Transition interactors use this to determine when we should transition to the OCCLUDED state.
+ *
+ * Outside of the transition/occlusion interactors, you almost certainly don't want to use this.
+ * Instead, use KeyguardTransitionInteractor to figure out if we're in KeyguardState.OCCLUDED.
+ */
+ val isShowWhenLockedActivityOnTop = showWhenLockedActivityInfo.map { it.isOnTop }
+
+ /** Whether we should start a transition due to the power button launch gesture. */
+ fun shouldTransitionFromPowerButtonGesture(): Boolean {
+ // powerButtonLaunchGestureTriggered remains true while we're awake from a power button
+ // gesture. Check that we were asleep or transitioning to asleep before starting a
+ // transition, to ensure we don't transition while moving between, for example,
+ // *_BOUNCER -> LOCKSCREEN.
+ return powerInteractor.detailedWakefulness.value.powerButtonLaunchGestureTriggered &&
+ KeyguardState.deviceIsAsleepInState(transitionInteractor.getStartedState())
+ }
+
+ /**
+ * Whether the SHOW_WHEN_LOCKED activity was launched from the double tap power button gesture.
+ * This remains true while the activity is running and emits false once it is killed.
+ */
+ val showWhenLockedActivityLaunchedFromPowerGesture =
+ merge(
+ // Emit true when the power launch gesture is triggered, since this means a
+ // SHOW_WHEN_LOCKED activity will be launched from the gesture (unless we're
+ // currently
+ // GONE, in which case we're going back to GONE and launching the insecure camera).
+ powerInteractor.detailedWakefulness
+ .sample(transitionInteractor.currentKeyguardState, ::Pair)
+ .map { (wakefulness, currentKeyguardState) ->
+ wakefulness.powerButtonLaunchGestureTriggered &&
+ currentKeyguardState != KeyguardState.GONE
+ },
+ // Emit false once that activity goes away.
+ isShowWhenLockedActivityOnTop.filter { !it }.map { false }
+ )
+ .stateIn(scope, SharingStarted.Eagerly, false)
+
+ /**
+ * Whether launching an occluding activity will automatically dismiss keyguard. This happens if
+ * the keyguard is dismissable.
+ */
+ val occludingActivityWillDismissKeyguard =
+ keyguardInteractor.isKeyguardDismissible.stateIn(scope, SharingStarted.Eagerly, false)
+
+ /**
+ * Called to let System UI know that WM says a SHOW_WHEN_LOCKED activity is on top (or no longer
+ * on top).
+ *
+ * This signal arrives from WM when a SHOW_WHEN_LOCKED activity is started or killed - it is
+ * never set directly by System UI. While we might be the reason the activity was started
+ * (launching the camera from the power button gesture), we ultimately only receive this signal
+ * once that activity starts. It's up to us to start the appropriate keyguard transitions,
+ * because that activity is going to be visible (or not) regardless.
+ */
+ fun setWmNotifiedShowWhenLockedActivityOnTop(
+ showWhenLockedActivityOnTop: Boolean,
+ taskInfo: RunningTaskInfo? = null
+ ) {
+ repository.setShowWhenLockedActivityInfo(showWhenLockedActivityOnTop, taskInfo)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index b0a3881..8eb1a50 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -46,6 +46,7 @@
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -55,6 +56,7 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
@@ -66,6 +68,7 @@
@Inject
constructor(
private val keyguardInteractor: KeyguardInteractor,
+ private val shadeInteractor: ShadeInteractor,
private val lockPatternUtils: LockPatternUtils,
private val keyguardStateController: KeyguardStateController,
private val userTracker: UserTracker,
@@ -100,9 +103,10 @@
quickAffordanceAlwaysVisible(position),
keyguardInteractor.isDozing,
keyguardInteractor.isKeyguardShowing,
+ shadeInteractor.anyExpansion.map { it < 1.0f }.distinctUntilChanged(),
biometricSettingsRepository.isCurrentUserInLockdown,
- ) { affordance, isDozing, isKeyguardShowing, isUserInLockdown ->
- if (!isDozing && isKeyguardShowing && !isUserInLockdown) {
+ ) { affordance, isDozing, isKeyguardShowing, isQuickSettingsVisible, isUserInLockdown ->
+ if (!isDozing && isKeyguardShowing && isQuickSettingsVisible && !isUserInLockdown) {
affordance
} else {
KeyguardQuickAffordanceModel.Hidden
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
index d81f1f1..c28e49d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
@@ -41,6 +41,7 @@
private val powerInteractor: PowerInteractor,
private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
private val shadeInteractor: ShadeInteractor,
+ private val keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
) {
fun start() {
@@ -91,6 +92,12 @@
}
scope.launch {
+ keyguardInteractor.isKeyguardDismissible.collect {
+ logger.log(TAG, VERBOSE, "isKeyguardDismissable", it)
+ }
+ }
+
+ scope.launch {
keyguardInteractor.isAbleToDream.collect {
logger.log(TAG, VERBOSE, "isAbleToDream", it)
}
@@ -125,5 +132,11 @@
logger.log(TAG, VERBOSE, "onCameraLaunchDetected", it)
}
}
+
+ scope.launch {
+ keyguardOcclusionInteractor.showWhenLockedActivityInfo.collect {
+ logger.log(TAG, VERBOSE, "showWhenLockedActivityInfo", it)
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 37b331c..00902b4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -20,6 +20,7 @@
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
@@ -42,6 +43,7 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
@@ -56,11 +58,15 @@
@Inject
constructor(
@Application val scope: CoroutineScope,
+ private val keyguardRepository: KeyguardRepository,
private val repository: KeyguardTransitionRepository,
private val fromLockscreenTransitionInteractor: dagger.Lazy<FromLockscreenTransitionInteractor>,
private val fromPrimaryBouncerTransitionInteractor:
dagger.Lazy<FromPrimaryBouncerTransitionInteractor>,
private val fromAodTransitionInteractor: dagger.Lazy<FromAodTransitionInteractor>,
+ private val fromAlternateBouncerTransitionInteractor:
+ dagger.Lazy<FromAlternateBouncerTransitionInteractor>,
+ private val fromDozingTransitionInteractor: dagger.Lazy<FromDozingTransitionInteractor>,
) {
private val TAG = this::class.simpleName
@@ -207,6 +213,12 @@
.map { step -> step.to }
.shareIn(scope, SharingStarted.Eagerly, replay = 1)
+ /** Which keyguard state to use when the device goes to sleep. */
+ val asleepKeyguardState: StateFlow<KeyguardState> =
+ keyguardRepository.isAodAvailable
+ .map { aodAvailable -> if (aodAvailable) AOD else DOZING }
+ .stateIn(scope, SharingStarted.Eagerly, DOZING)
+
/**
* A pair of the most recent STARTED step, and the transition step immediately preceding it. The
* transition framework enforces that the previous step is either a CANCELED or FINISHED step,
@@ -368,7 +380,10 @@
when (val startedState = startedKeyguardState.replayCache.last()) {
LOCKSCREEN -> fromLockscreenTransitionInteractor.get().dismissKeyguard()
PRIMARY_BOUNCER -> fromPrimaryBouncerTransitionInteractor.get().dismissPrimaryBouncer()
+ ALTERNATE_BOUNCER ->
+ fromAlternateBouncerTransitionInteractor.get().dismissAlternateBouncer()
AOD -> fromAodTransitionInteractor.get().dismissAod()
+ DOZING -> fromDozingTransitionInteractor.get().dismissFromDozing()
else ->
Log.e(
"KeyguardTransitionInteractor",
@@ -421,12 +436,17 @@
fromStatePredicate: (KeyguardState) -> Boolean,
toStatePredicate: (KeyguardState) -> Boolean,
): Flow<Boolean> {
+ return isInTransitionWhere { from, to -> fromStatePredicate(from) && toStatePredicate(to) }
+ }
+
+ fun isInTransitionWhere(
+ fromToStatePredicate: (KeyguardState, KeyguardState) -> Boolean
+ ): Flow<Boolean> {
return repository.transitions
.filter { it.transitionState != TransitionState.CANCELED }
.mapLatest {
it.transitionState != TransitionState.FINISHED &&
- fromStatePredicate(it.from) &&
- toStatePredicate(it.to)
+ fromToStatePredicate(it.from, it.to)
}
.distinctUntilChanged()
}
@@ -447,4 +467,16 @@
*/
fun isFinishedInStateWhereValue(stateMatcher: (KeyguardState) -> Boolean) =
stateMatcher(finishedKeyguardState.replayCache.last())
+
+ fun getCurrentState(): KeyguardState {
+ return currentKeyguardState.replayCache.last()
+ }
+
+ fun getStartedState(): KeyguardState {
+ return startedKeyguardState.replayCache.last()
+ }
+
+ fun getFinishedState(): KeyguardState {
+ return finishedKeyguardState.replayCache.last()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
index 4d731ec..8905c9e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
@@ -43,7 +43,6 @@
private val scrimLogger: ScrimLogger,
private val powerInteractor: PowerInteractor,
) {
-
init {
listenForStartedKeyguardTransitionStep()
}
@@ -52,9 +51,7 @@
scope.launch {
transitionInteractor.startedKeyguardTransitionStep.collect {
scrimLogger.d(TAG, "listenForStartedKeyguardTransitionStep", it)
- lightRevealScrimRepository.startRevealAmountAnimator(
- willBeRevealedInState(it.to),
- )
+ lightRevealScrimRepository.startRevealAmountAnimator(willBeRevealedInState(it.to))
}
}
}
@@ -89,25 +86,25 @@
companion object {
- /**
- * Whether the light reveal scrim will be fully revealed (revealAmount = 1.0f) in the given
- * state after the transition is complete. If false, scrim will be fully hidden.
- */
- private fun willBeRevealedInState(state: KeyguardState): Boolean {
- return when (state) {
- KeyguardState.OFF -> false
- KeyguardState.DOZING -> false
- KeyguardState.AOD -> false
- KeyguardState.DREAMING -> true
- KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> true
- KeyguardState.GLANCEABLE_HUB -> true
- KeyguardState.ALTERNATE_BOUNCER -> true
- KeyguardState.PRIMARY_BOUNCER -> true
- KeyguardState.LOCKSCREEN -> true
- KeyguardState.GONE -> true
- KeyguardState.OCCLUDED -> true
+ /**
+ * Whether the light reveal scrim will be fully revealed (revealAmount = 1.0f) in the given
+ * state after the transition is complete. If false, scrim will be fully hidden.
+ */
+ private fun willBeRevealedInState(state: KeyguardState): Boolean {
+ return when (state) {
+ KeyguardState.OFF -> false
+ KeyguardState.DOZING -> false
+ KeyguardState.AOD -> false
+ KeyguardState.DREAMING -> true
+ KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> true
+ KeyguardState.GLANCEABLE_HUB -> true
+ KeyguardState.ALTERNATE_BOUNCER -> true
+ KeyguardState.PRIMARY_BOUNCER -> true
+ KeyguardState.LOCKSCREEN -> true
+ KeyguardState.GONE -> true
+ KeyguardState.OCCLUDED -> true
+ }
}
- }
val TAG = LightRevealScrimInteractor::class.simpleName!!
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index 3ccbdba..375df3e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -18,15 +18,20 @@
import android.animation.ValueAnimator
import android.util.Log
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.util.kotlin.sample
import java.util.UUID
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -46,6 +51,8 @@
val transitionInteractor: KeyguardTransitionInteractor,
val mainDispatcher: CoroutineDispatcher,
val bgDispatcher: CoroutineDispatcher,
+ val powerInteractor: PowerInteractor,
+ val keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
) {
val name = this::class.simpleName ?: "UnknownTransitionInteractor"
abstract val transitionRepository: KeyguardTransitionRepository
@@ -65,7 +72,11 @@
suspend fun startTransitionTo(
toState: KeyguardState,
animator: ValueAnimator? = getDefaultAnimatorForTransitionsToState(toState),
- modeOnCanceled: TransitionModeOnCanceled = TransitionModeOnCanceled.LAST_VALUE
+ modeOnCanceled: TransitionModeOnCanceled = TransitionModeOnCanceled.LAST_VALUE,
+ // Even more information about why the owner started this transition, if this is a dangerous
+ // transition (*cough* occlusion) where you'd be sad to not have all the info you can get in
+ // a bugreport.
+ ownerReason: String = "",
): UUID? {
if (
fromState != transitionInteractor.startedKeyguardState.replayCache.last() &&
@@ -85,7 +96,7 @@
return withContext(mainDispatcher) {
transitionRepository.startTransition(
TransitionInfo(
- name,
+ name + if (ownerReason.isNotBlank()) "($ownerReason)" else "",
fromState,
toState,
animator,
@@ -95,24 +106,107 @@
}
}
+ /**
+ * Check whether we need to transition to [KeyguardState.OCCLUDED], based on the presence of a
+ * SHOW_WHEN_LOCKED activity, or back to [KeyguardState.GONE], for some power button launch
+ * gesture cases. If so, start the transition.
+ *
+ * Returns true if a transition was started, false otherwise.
+ */
+ suspend fun maybeStartTransitionToOccludedOrInsecureCamera(): Boolean {
+ if (keyguardOcclusionInteractor.shouldTransitionFromPowerButtonGesture()) {
+ if (transitionInteractor.getCurrentState() == KeyguardState.GONE) {
+ // If the current state is GONE when the launch gesture is triggered, it means we
+ // were in transition from GONE -> DOZING/AOD due to the first power button tap. The
+ // second tap indicates that the user's intent was actually to launch the unlocked
+ // (insecure) camera, so we should transition back to GONE.
+ startTransitionTo(
+ KeyguardState.GONE,
+ ownerReason = "Power button gesture while GONE"
+ )
+ } else if (keyguardOcclusionInteractor.occludingActivityWillDismissKeyguard.value) {
+ // The double tap gesture occurred while not GONE (AOD/LOCKSCREEN/etc.), but the
+ // keyguard is dismissable. The activity launch will dismiss the keyguard, so we
+ // should transition to GONE.
+ startTransitionTo(
+ KeyguardState.GONE,
+ ownerReason = "Power button gesture on dismissable keyguard"
+ )
+ } else {
+ // Otherwise, the double tap gesture occurred while not GONE and not dismissable,
+ // which means we will launch the secure camera, which OCCLUDES the keyguard.
+ startTransitionTo(
+ KeyguardState.OCCLUDED,
+ ownerReason = "Power button gesture on lockscreen"
+ )
+ }
+
+ return true
+ } else if (keyguardOcclusionInteractor.showWhenLockedActivityInfo.value.isOnTop) {
+ // A SHOW_WHEN_LOCKED activity is on top of the task stack. Transition to OCCLUDED so
+ // it's visible.
+ // TODO(b/307976454) - Centralize transition to DREAMING here.
+ startTransitionTo(
+ KeyguardState.OCCLUDED,
+ ownerReason = "SHOW_WHEN_LOCKED activity on top"
+ )
+
+ return true
+ } else {
+ // No transition needed, let the interactor figure out where to go.
+ return false
+ }
+ }
+
+ /**
+ * Transition to the appropriate state when the device goes to sleep while in [from].
+ *
+ * We could also just use [fromState], but it's more readable in the From*TransitionInteractor
+ * if you're explicitly declaring which state you're listening from. If you passed in the wrong
+ * state, [startTransitionTo] would complain anyway.
+ */
+ suspend fun listenForSleepTransition(
+ from: KeyguardState,
+ modeOnCanceledFromStartedStep: (TransitionStep) -> TransitionModeOnCanceled = {
+ TransitionModeOnCanceled.LAST_VALUE
+ }
+ ) {
+ powerInteractor.isAsleep
+ .filter { isAsleep -> isAsleep }
+ .sample(startedKeyguardTransitionStep)
+ .filter { startedStep -> startedStep.to == from }
+ .map(modeOnCanceledFromStartedStep)
+ .collect { modeOnCanceled ->
+ startTransitionTo(
+ toState = transitionInteractor.asleepKeyguardState.value,
+ modeOnCanceled = modeOnCanceled,
+ ownerReason = "Sleep transition triggered"
+ )
+ }
+ }
+
/** This signal may come in before the occlusion signal, and can provide a custom transition */
fun listenForTransitionToCamera(
scope: CoroutineScope,
keyguardInteractor: KeyguardInteractor,
) {
- scope.launch {
- keyguardInteractor.onCameraLaunchDetected
- .sample(transitionInteractor.finishedKeyguardState)
- .collect { finishedKeyguardState ->
- // Other keyguard state transitions may trigger on the first power button push,
- // so use the last finishedKeyguardState to determine the overriding FROM state
- if (finishedKeyguardState == fromState) {
- startTransitionTo(
- toState = KeyguardState.OCCLUDED,
- modeOnCanceled = TransitionModeOnCanceled.RESET,
- )
+ if (!KeyguardWmStateRefactor.isEnabled) {
+ scope.launch {
+ keyguardInteractor.onCameraLaunchDetected
+ .sample(transitionInteractor.finishedKeyguardState)
+ .collect { finishedKeyguardState ->
+ // Other keyguard state transitions may trigger on the first power button
+ // push,
+ // so use the last finishedKeyguardState to determine the overriding FROM
+ // state
+ if (finishedKeyguardState == fromState) {
+ startTransitionTo(
+ toState = KeyguardState.OCCLUDED,
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
+ )
+ }
}
- }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt
index a9eec18..7e39a88 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt
@@ -43,6 +43,10 @@
to = KeyguardState.GONE,
)
+ /**
+ * AOD -> GONE should fade out the lockscreen contents. This transition plays both during wake
+ * and unlock, and also during insecure camera launch (which is GONE -> AOD (canceled) -> GONE).
+ */
fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> {
var startAlpha = 1f
return transitionAnimation.sharedFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt
index 105a7ed..445575f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt
@@ -16,12 +16,15 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.util.MathUtils
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.flow.Flow
/** Breaks down AOD->OCCLUDED transition into discrete steps for corresponding views to consume. */
@SysUISingleton
@@ -37,5 +40,23 @@
to = KeyguardState.OCCLUDED,
)
+ /**
+ * Fade out the lockscreen during a transition to OCCLUDED.
+ *
+ * This happens when pressing the power button while a SHOW_WHEN_LOCKED activity is on the top
+ * of the task stack, as well as when the power button is double tapped on the LOCKSCREEN (the
+ * first tap transitions to AOD, the second cancels that transition and starts AOD -> OCCLUDED.
+ */
+ fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> {
+ var currentAlpha = 0f
+ return transitionAnimation.sharedFlow(
+ duration = 250.milliseconds,
+ startTime = 100.milliseconds, // Wait for the light reveal to "hit" the LS elements.
+ onStart = { currentAlpha = viewState.alpha() },
+ onStep = { MathUtils.lerp(currentAlpha, 0f, it) },
+ onCancel = { 0f },
+ )
+ }
+
override val deviceEntryParentViewAlpha = transitionAnimation.immediatelyTransitionTo(0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt
new file mode 100644
index 0000000..c0b1195
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.util.MathUtils
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Breaks down DOZING->OCCLUDED transition into discrete steps for corresponding views to consume.
+ */
+@SysUISingleton
+class DozingToOccludedTransitionViewModel
+@Inject
+constructor(
+ animationFlow: KeyguardTransitionAnimationFlow,
+) : DeviceEntryIconTransition {
+ private val transitionAnimation =
+ animationFlow.setup(
+ duration = FromAodTransitionInteractor.TO_OCCLUDED_DURATION,
+ from = KeyguardState.DOZING,
+ to = KeyguardState.OCCLUDED,
+ )
+
+ /**
+ * Fade out the lockscreen during a transition to OCCLUDED.
+ *
+ * This happens when pressing the power button while a SHOW_WHEN_LOCKED activity is on the top
+ * of the task stack, as well as when the power button is double tapped on the LOCKSCREEN (the
+ * first tap transitions to DOZING, the second cancels that transition and starts DOZING ->
+ * OCCLUDED.
+ */
+ fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> {
+ var currentAlpha = 0f
+ return transitionAnimation.sharedFlow(
+ duration = 250.milliseconds,
+ startTime = 100.milliseconds, // Wait for the light reveal to "hit" the LS elements.
+ onStart = { currentAlpha = viewState.alpha() },
+ onStep = { MathUtils.lerp(currentAlpha, 0f, it) },
+ onCancel = { 0f },
+ )
+ }
+
+ override val deviceEntryParentViewAlpha = transitionAnimation.immediatelyTransitionTo(0f)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 1760b92..f848717 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -73,8 +73,10 @@
AlternateBouncerToGoneTransitionViewModel,
private val aodToGoneTransitionViewModel: AodToGoneTransitionViewModel,
private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
+ private val aodToOccludedTransitionViewModel: AodToOccludedTransitionViewModel,
private val dozingToGoneTransitionViewModel: DozingToGoneTransitionViewModel,
private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel,
+ private val dozingToOccludedTransitionViewModel: DozingToOccludedTransitionViewModel,
private val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
private val glanceableHubToLockscreenTransitionViewModel:
GlanceableHubToLockscreenTransitionViewModel,
@@ -170,8 +172,10 @@
alternateBouncerToGoneTransitionViewModel.lockscreenAlpha,
aodToGoneTransitionViewModel.lockscreenAlpha(viewState),
aodToLockscreenTransitionViewModel.lockscreenAlpha(viewState),
+ aodToOccludedTransitionViewModel.lockscreenAlpha(viewState),
dozingToGoneTransitionViewModel.lockscreenAlpha(viewState),
dozingToLockscreenTransitionViewModel.lockscreenAlpha,
+ dozingToOccludedTransitionViewModel.lockscreenAlpha(viewState),
dreamingToLockscreenTransitionViewModel.lockscreenAlpha,
glanceableHubToLockscreenTransitionViewModel.keyguardAlpha,
goneToAodTransitionViewModel.enterFromTopAnimationAlpha,
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialog.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialog.kt
deleted file mode 100644
index fc45228..0000000
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialog.kt
+++ /dev/null
@@ -1,33 +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.mediaprojection.devicepolicy
-
-import android.content.Context
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.phone.SystemUIDialog
-
-/** Dialog that shows that screen capture is disabled on this device. */
-class ScreenCaptureDisabledDialog(context: Context) : SystemUIDialog(context) {
-
- init {
- setTitle(context.getString(R.string.screen_capturing_disabled_by_policy_dialog_title))
- setMessage(
- context.getString(R.string.screen_capturing_disabled_by_policy_dialog_description)
- )
- setIcon(R.drawable.ic_cast)
- setButton(BUTTON_POSITIVE, context.getString(android.R.string.ok)) { _, _ -> cancel() }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialogDelegate.kt
new file mode 100644
index 0000000..8aed535
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialogDelegate.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.mediaprojection.devicepolicy
+
+import android.content.DialogInterface.BUTTON_POSITIVE
+import android.content.res.Resources
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import javax.inject.Inject
+
+/** Dialog that shows that screen capture is disabled on this device. */
+class ScreenCaptureDisabledDialogDelegate @Inject constructor(
+ @Main private val resources: Resources,
+ private val systemUIDialogFactory: SystemUIDialog.Factory
+) : SystemUIDialog.Delegate {
+
+ override fun createDialog(): SystemUIDialog {
+ val dialog = systemUIDialogFactory.create(this)
+ dialog.setTitle(resources.getString(R.string.screen_capturing_disabled_by_policy_dialog_title))
+ dialog.setMessage(
+ resources.getString(R.string.screen_capturing_disabled_by_policy_dialog_description)
+ )
+ dialog.setIcon(R.drawable.ic_cast)
+ dialog.setButton(BUTTON_POSITIVE, resources.getString(android.R.string.ok)) {
+ _, _ -> dialog.cancel()
+ }
+
+ return dialog
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index 8b034b2..17f9caf 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -59,7 +59,7 @@
import com.android.systemui.mediaprojection.SessionCreationSource;
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity;
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
-import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialogDelegate;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.AlertDialogWithDelegate;
import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -79,6 +79,7 @@
private final Lazy<ScreenCaptureDevicePolicyResolver> mScreenCaptureDevicePolicyResolver;
private final StatusBarManager mStatusBarManager;
private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
+ private final ScreenCaptureDisabledDialogDelegate mScreenCaptureDisabledDialogDelegate;
private String mPackageName;
private int mUid;
@@ -93,14 +94,17 @@
private boolean mUserSelectingTask = false;
@Inject
- public MediaProjectionPermissionActivity(FeatureFlags featureFlags,
+ public MediaProjectionPermissionActivity(
+ FeatureFlags featureFlags,
Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver,
StatusBarManager statusBarManager,
- MediaProjectionMetricsLogger mediaProjectionMetricsLogger) {
+ MediaProjectionMetricsLogger mediaProjectionMetricsLogger,
+ ScreenCaptureDisabledDialogDelegate screenCaptureDisabledDialogDelegate) {
mFeatureFlags = featureFlags;
mScreenCaptureDevicePolicyResolver = screenCaptureDevicePolicyResolver;
mStatusBarManager = statusBarManager;
mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger;
+ mScreenCaptureDisabledDialogDelegate = screenCaptureDisabledDialogDelegate;
}
@Override
@@ -315,10 +319,7 @@
final UserHandle hostUserHandle = getHostUserHandle();
if (mScreenCaptureDevicePolicyResolver.get()
.isScreenCaptureCompletelyDisabled(hostUserHandle)) {
- // Using application context for the dialog, instead of the activity context, so we get
- // the correct screen width when in split screen.
- Context dialogContext = getApplicationContext();
- AlertDialog dialog = new ScreenCaptureDisabledDialog(dialogContext);
+ AlertDialog dialog = mScreenCaptureDisabledDialogDelegate.createDialog();
setUpDialog(dialog);
dialog.show();
return true;
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt
index cc8cc51..d6629e0 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt
@@ -18,14 +18,18 @@
import android.app.ActivityManager.RunningTaskInfo
import android.util.Log
+import androidx.annotation.VisibleForTesting
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState
import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState
import javax.inject.Inject
+import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.withContext
class TaskSwitcherNotificationViewModel
@@ -36,21 +40,30 @@
) {
val uiState: Flow<TaskSwitcherNotificationUiState> =
- interactor.taskSwitchChanges.map { taskSwitchChange ->
- Log.d(TAG, "taskSwitchChange: $taskSwitchChange")
- when (taskSwitchChange) {
- is TaskSwitchState.TaskSwitched -> {
- TaskSwitcherNotificationUiState.Showing(
- projectedTask = taskSwitchChange.projectedTask,
- foregroundTask = taskSwitchChange.foregroundTask,
- )
- }
- is TaskSwitchState.NotProjectingTask,
- is TaskSwitchState.TaskUnchanged -> {
- TaskSwitcherNotificationUiState.NotShowing
+ interactor.taskSwitchChanges
+ .map { taskSwitchChange ->
+ Log.d(TAG, "taskSwitchChange: $taskSwitchChange")
+ when (taskSwitchChange) {
+ is TaskSwitchState.TaskSwitched -> {
+ TaskSwitcherNotificationUiState.Showing(
+ projectedTask = taskSwitchChange.projectedTask,
+ foregroundTask = taskSwitchChange.foregroundTask,
+ )
+ }
+ is TaskSwitchState.NotProjectingTask,
+ is TaskSwitchState.TaskUnchanged -> {
+ TaskSwitcherNotificationUiState.NotShowing
+ }
}
}
- }
+ .transformLatest { uiState ->
+ emit(uiState)
+ if (uiState is TaskSwitcherNotificationUiState.Showing) {
+ delay(NOTIFICATION_MAX_SHOW_DURATION)
+ Log.d(TAG, "Auto hiding notification after $NOTIFICATION_MAX_SHOW_DURATION")
+ emit(TaskSwitcherNotificationUiState.NotShowing)
+ }
+ }
suspend fun onSwitchTaskClicked(task: RunningTaskInfo) {
interactor.switchProjectedTask(task)
@@ -60,6 +73,7 @@
withContext(backgroundDispatcher) { interactor.goBackToTask(task) }
companion object {
+ @VisibleForTesting val NOTIFICATION_MAX_SHOW_DURATION = 5.seconds
private const val TAG = "TaskSwitchNotifVM"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
index 152f193..9f7d1b3 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
@@ -19,6 +19,7 @@
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE;
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;
@@ -28,7 +29,6 @@
import android.content.res.Configuration;
import android.hardware.display.DisplayManager;
import android.os.Bundle;
-import android.os.Handler;
import android.os.RemoteException;
import android.os.Trace;
import android.os.UserHandle;
@@ -70,9 +70,11 @@
import java.io.PrintWriter;
import java.util.Optional;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
+
@SysUISingleton
public class NavigationBarControllerImpl implements
ConfigurationController.ConfigurationListener,
@@ -82,7 +84,7 @@
private static final String TAG = NavigationBarControllerImpl.class.getSimpleName();
private final Context mContext;
- private final Handler mHandler;
+ private final Executor mExecutor;
private final NavigationBarComponent.Factory mNavigationBarComponentFactory;
private final SecureSettings mSecureSettings;
private final DisplayTracker mDisplayTracker;
@@ -119,7 +121,7 @@
NavigationModeController navigationModeController,
SysUiState sysUiFlagsContainer,
CommandQueue commandQueue,
- @Main Handler mainHandler,
+ @Main Executor mainExecutor,
ConfigurationController configurationController,
NavBarHelper navBarHelper,
TaskbarDelegate taskbarDelegate,
@@ -133,7 +135,7 @@
SecureSettings secureSettings,
DisplayTracker displayTracker) {
mContext = context;
- mHandler = mainHandler;
+ mExecutor = mainExecutor;
mNavigationBarComponentFactory = navigationBarComponentFactory;
mSecureSettings = secureSettings;
mDisplayTracker = displayTracker;
@@ -193,7 +195,7 @@
mNavMode = mode;
updateAccessibilityButtonModeIfNeeded();
- mHandler.post(() -> {
+ mExecutor.execute(() -> {
// create/destroy nav bar based on nav mode only in unfolded state
if (oldMode != mNavMode) {
updateNavbarForTaskbar();
diff --git a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt
index e1d1ec2..5432793d 100644
--- a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt
@@ -20,20 +20,24 @@
* the [KeyguardTransitionInteractor].
*/
internal val internalWakefulnessState: WakefulnessState = WakefulnessState.AWAKE,
-
val lastWakeReason: WakeSleepReason = WakeSleepReason.OTHER,
val lastSleepReason: WakeSleepReason = WakeSleepReason.OTHER,
- /**
+ /**
* Whether the power button double tap gesture was triggered since the last time went to sleep.
* If this value is true while [isAsleep]=true, it means we'll be waking back up shortly. If it
* is true while [isAwake]=true, it means we're awake because of the button gesture.
*
- * This value remains true until the next time [isAsleep]=true.
+ * This value remains true until the next time [isAsleep]=true, since it would otherwise be
+ * totally arbitrary at what point we decide the gesture was no longer "triggered". Since a
+ * sleep event is guaranteed to arrive prior to the next power button gesture (as the first tap
+ * of the double tap always begins a sleep transition), this will always be reset to false prior
+ * to a subsequent power gesture.
*/
val powerButtonLaunchGestureTriggered: Boolean = false,
) {
- fun isAwake() = internalWakefulnessState == WakefulnessState.AWAKE ||
+ fun isAwake() =
+ internalWakefulnessState == WakefulnessState.AWAKE ||
internalWakefulnessState == WakefulnessState.STARTING_TO_WAKE
fun isAsleep() = !isAwake()
@@ -48,11 +52,10 @@
fun isAsleepFrom(wakeSleepReason: WakeSleepReason) =
isAsleep() && lastSleepReason == wakeSleepReason
- fun isAwakeOrAsleepFrom(reason: WakeSleepReason) =
- isAsleepFrom(reason) || isAwakeFrom(reason)
+ fun isAwakeOrAsleepFrom(reason: WakeSleepReason) = isAsleepFrom(reason) || isAwakeFrom(reason)
fun isAwakeFromTapOrGesture(): Boolean {
- return isAwake() && (lastWakeReason == WakeSleepReason.TAP ||
- lastWakeReason == WakeSleepReason.GESTURE)
+ return isAwake() &&
+ (lastWakeReason == WakeSleepReason.TAP || lastWakeReason == WakeSleepReason.GESTURE)
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
index 0dd0a60..52cf4ec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
@@ -481,7 +481,8 @@
mSecondaryMobileTitleText.setTextAppearance(
R.style.TextAppearance_InternetDialog_Active);
- TextView mSecondaryMobileSummaryText = mDialogView.requireViewById(R.id.secondary_mobile_summary);
+ TextView mSecondaryMobileSummaryText =
+ mDialogView.requireViewById(R.id.secondary_mobile_summary);
summary = getMobileNetworkSummary(autoSwitchNonDdsSubId);
if (!TextUtils.isEmpty(summary)) {
mSecondaryMobileSummaryText.setText(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileDataInteractor.kt
new file mode 100644
index 0000000..22bbbbb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileDataInteractor.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.battery.domain.interactor
+
+import android.os.UserHandle
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.battery.domain.model.BatterySaverTileModel
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.util.kotlin.combine
+import com.android.systemui.util.kotlin.getBatteryLevel
+import com.android.systemui.util.kotlin.isBatteryPowerSaveEnabled
+import com.android.systemui.util.kotlin.isDevicePluggedIn
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+
+/** Observes BatterySaver mode state changes providing the [BatterySaverTileModel.Standard]. */
+open class BatterySaverTileDataInteractor
+@Inject
+constructor(
+ @Background private val bgCoroutineContext: CoroutineContext,
+ private val batteryController: BatteryController,
+) : QSTileDataInteractor<BatterySaverTileModel> {
+
+ override fun tileData(
+ user: UserHandle,
+ triggers: Flow<DataUpdateTrigger>
+ ): Flow<BatterySaverTileModel> =
+ combine(
+ batteryController.isDevicePluggedIn().distinctUntilChanged().flowOn(bgCoroutineContext),
+ batteryController
+ .isBatteryPowerSaveEnabled()
+ .distinctUntilChanged()
+ .flowOn(bgCoroutineContext),
+ batteryController.getBatteryLevel().distinctUntilChanged().flowOn(bgCoroutineContext),
+ ) {
+ isPluggedIn: Boolean,
+ isPowerSaverEnabled: Boolean,
+ _, // we are only interested in battery level change, not the actual level
+ ->
+ BatterySaverTileModel.Standard(isPluggedIn, isPowerSaverEnabled)
+ }
+
+ override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileUserActionInteractor.kt
new file mode 100644
index 0000000..1e4eb38
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileUserActionInteractor.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.battery.domain.interactor
+
+import android.content.Intent
+import android.provider.Settings
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.battery.domain.model.BatterySaverTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.statusbar.policy.BatteryController
+import javax.inject.Inject
+
+/** Handles airplane mode tile clicks and long clicks. */
+class BatterySaverTileUserActionInteractor
+@Inject
+constructor(
+ private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+ private val batteryController: BatteryController
+) : QSTileUserActionInteractor<BatterySaverTileModel> {
+
+ override suspend fun handleInput(input: QSTileInput<BatterySaverTileModel>) =
+ with(input) {
+ when (action) {
+ is QSTileUserAction.Click -> {
+ if (!data.isPluggedIn) {
+ batteryController.setPowerSaveMode(!data.isPowerSaving, action.view)
+ }
+ }
+ is QSTileUserAction.LongClick -> {
+ qsTileIntentUserActionHandler.handle(
+ action.view,
+ Intent(Settings.ACTION_BATTERY_SAVER_SETTINGS)
+ )
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/model/BatterySaverTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/model/BatterySaverTileModel.kt
new file mode 100644
index 0000000..dbec50d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/model/BatterySaverTileModel.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.battery.domain.model
+
+/** BatterySaver mode tile model. */
+sealed interface BatterySaverTileModel {
+
+ val isPluggedIn: Boolean
+ val isPowerSaving: Boolean
+
+ /** For when the device does not support extreme battery saver mode. */
+ data class Standard(
+ override val isPluggedIn: Boolean,
+ override val isPowerSaving: Boolean,
+ ) : BatterySaverTileModel
+
+ /**
+ * For when device supports extreme battery saver mode. Whether or not that mode is enabled is
+ * determined through [isExtremeSaving].
+ */
+ data class Extreme(
+ override val isPluggedIn: Boolean,
+ override val isPowerSaving: Boolean,
+ val isExtremeSaving: Boolean,
+ ) : BatterySaverTileModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt
new file mode 100644
index 0000000..0c08fba
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.battery.ui
+
+import android.content.res.Resources
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.battery.domain.model.BatterySaverTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Maps [BatterySaverTileModel] to [QSTileState]. */
+open class BatterySaverTileMapper
+@Inject
+constructor(
+ @Main protected val resources: Resources,
+ private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<BatterySaverTileModel> {
+
+ override fun map(config: QSTileConfig, data: BatterySaverTileModel): QSTileState =
+ QSTileState.build(resources, theme, config.uiConfig) {
+ label = resources.getString(R.string.battery_detail_switch_title)
+ contentDescription = label
+
+ icon = {
+ Icon.Loaded(
+ resources.getDrawable(
+ if (data.isPowerSaving) R.drawable.qs_battery_saver_icon_on
+ else R.drawable.qs_battery_saver_icon_off,
+ theme
+ ),
+ null
+ )
+ }
+
+ sideViewIcon = QSTileState.SideViewIcon.None
+
+ if (data.isPluggedIn) {
+ activationState = QSTileState.ActivationState.UNAVAILABLE
+ supportedActions = setOf(QSTileState.UserAction.LONG_CLICK)
+ secondaryLabel = ""
+ } else if (data.isPowerSaving) {
+ activationState = QSTileState.ActivationState.ACTIVE
+ supportedActions =
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+
+ if (data is BatterySaverTileModel.Extreme) {
+ secondaryLabel =
+ resources.getString(
+ if (data.isExtremeSaving) R.string.extreme_battery_saver_text
+ else R.string.standard_battery_saver_text
+ )
+ stateDescription = secondaryLabel
+ } else {
+ secondaryLabel = ""
+ }
+ } else {
+ activationState = QSTileState.ActivationState.INACTIVE
+ supportedActions =
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ secondaryLabel = ""
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
index 80f11f1..1c07d00 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
@@ -40,7 +40,7 @@
import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
import com.android.systemui.mediaprojection.SessionCreationSource
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver
-import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialogDelegate
import com.android.systemui.qs.tiles.RecordIssueTile
import com.android.systemui.res.R
import com.android.systemui.screenrecord.RecordingService
@@ -65,6 +65,7 @@
private val devicePolicyResolver: dagger.Lazy<ScreenCaptureDevicePolicyResolver>,
private val mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
private val userFileManager: UserFileManager,
+ private val screenCaptureDisabledDialogDelegate: ScreenCaptureDisabledDialogDelegate,
@Assisted private val onStarted: Runnable,
) : SystemUIDialog.Delegate {
@@ -124,7 +125,7 @@
.isScreenCaptureCompletelyDisabled(UserHandle.of(userTracker.userId))
) {
mainExecutor.execute {
- ScreenCaptureDisabledDialog(context).show()
+ screenCaptureDisabledDialogDelegate.createDialog().show()
screenRecordSwitch.isChecked = false
}
return@execute
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index a4ba2a2..8fe84c9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -42,11 +42,9 @@
import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
import com.android.systemui.mediaprojection.SessionCreationSource;
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
-import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialogDelegate;
import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.CallbackController;
import dagger.Lazy;
@@ -71,18 +69,19 @@
private CountDownTimer mCountDownTimer = null;
private final Executor mMainExecutor;
private final BroadcastDispatcher mBroadcastDispatcher;
- private final Context mContext;
private final FeatureFlags mFlags;
- private final UserContextProvider mUserContextProvider;
private final UserTracker mUserTracker;
private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
- private final SystemUIDialog.Factory mDialogFactory;
+ private final ScreenCaptureDisabledDialogDelegate mScreenCaptureDisabledDialogDelegate;
+ private final ScreenRecordDialogDelegate.Factory mScreenRecordDialogFactory;
+ private final ScreenRecordPermissionDialogDelegate.Factory
+ mScreenRecordPermissionDialogDelegateFactory;
protected static final String INTENT_UPDATE_STATE =
"com.android.systemui.screenrecord.UPDATE_STATE";
protected static final String EXTRA_STATE = "extra_state";
- private CopyOnWriteArrayList<RecordingStateChangeCallback> mListeners =
+ private final CopyOnWriteArrayList<RecordingStateChangeCallback> mListeners =
new CopyOnWriteArrayList<>();
private final Lazy<ScreenCaptureDevicePolicyResolver> mDevicePolicyResolver;
@@ -115,24 +114,26 @@
* Create a new RecordingController
*/
@Inject
- public RecordingController(@Main Executor mainExecutor,
+ public RecordingController(
+ @Main Executor mainExecutor,
BroadcastDispatcher broadcastDispatcher,
- Context context,
FeatureFlags flags,
- UserContextProvider userContextProvider,
Lazy<ScreenCaptureDevicePolicyResolver> devicePolicyResolver,
UserTracker userTracker,
MediaProjectionMetricsLogger mediaProjectionMetricsLogger,
- SystemUIDialog.Factory dialogFactory) {
+ ScreenCaptureDisabledDialogDelegate screenCaptureDisabledDialogDelegate,
+ ScreenRecordDialogDelegate.Factory screenRecordDialogFactory,
+ ScreenRecordPermissionDialogDelegate.Factory
+ screenRecordPermissionDialogDelegateFactory) {
mMainExecutor = mainExecutor;
- mContext = context;
mFlags = flags;
mDevicePolicyResolver = devicePolicyResolver;
mBroadcastDispatcher = broadcastDispatcher;
- mUserContextProvider = userContextProvider;
mUserTracker = userTracker;
mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger;
- mDialogFactory = dialogFactory;
+ mScreenCaptureDisabledDialogDelegate = screenCaptureDisabledDialogDelegate;
+ mScreenRecordDialogFactory = screenRecordDialogFactory;
+ mScreenRecordPermissionDialogDelegateFactory = screenRecordPermissionDialogDelegateFactory;
BroadcastOptions options = BroadcastOptions.makeBasic();
options.setInteractive(true);
@@ -163,27 +164,18 @@
if (mFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)
&& mDevicePolicyResolver.get()
.isScreenCaptureCompletelyDisabled(getHostUserHandle())) {
- return new ScreenCaptureDisabledDialog(mContext);
+ return mScreenCaptureDisabledDialogDelegate.createDialog();
}
mMediaProjectionMetricsLogger.notifyProjectionInitiated(
getHostUid(), SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER);
- return flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)
- ? mDialogFactory.create(new ScreenRecordPermissionDialogDelegate(
- getHostUserHandle(),
- getHostUid(),
- /* controller= */ this,
- activityStarter,
- mUserContextProvider,
- onStartRecordingClicked,
- mMediaProjectionMetricsLogger,
- mDialogFactory))
- : new ScreenRecordDialog(
- context,
- /* controller= */ this,
- mUserContextProvider,
- onStartRecordingClicked);
+ return (flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)
+ ? mScreenRecordPermissionDialogDelegateFactory
+ .create(this, getHostUserHandle(), getHostUid(), onStartRecordingClicked)
+ : mScreenRecordDialogFactory
+ .create(this, onStartRecordingClicked))
+ .createDialog();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialogDelegate.java
similarity index 77%
rename from packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
rename to packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialogDelegate.java
index b98093e..9f1447b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialogDelegate.java
@@ -49,52 +49,69 @@
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.statusbar.phone.SystemUIDialog;
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
+
import java.util.Arrays;
import java.util.List;
/**
* Dialog to select screen recording options
*/
-public class ScreenRecordDialog extends SystemUIDialog {
+public class ScreenRecordDialogDelegate implements SystemUIDialog.Delegate {
private static final List<ScreenRecordingAudioSource> MODES = Arrays.asList(INTERNAL, MIC,
MIC_AND_INTERNAL);
private static final long DELAY_MS = 3000;
private static final long INTERVAL_MS = 1000;
- private final RecordingController mController;
+ private final SystemUIDialog.Factory mSystemUIDialogFactory;
private final UserContextProvider mUserContextProvider;
- @Nullable
+ private final RecordingController mController;
private final Runnable mOnStartRecordingClicked;
private Switch mTapsSwitch;
private Switch mAudioSwitch;
private Spinner mOptions;
- public ScreenRecordDialog(Context context,
- RecordingController controller,
- UserContextProvider userContextProvider,
- @Nullable Runnable onStartRecordingClicked) {
- super(context);
- mController = controller;
+ @AssistedFactory
+ public interface Factory {
+ ScreenRecordDialogDelegate create(
+ RecordingController recordingController,
+ @Nullable Runnable onStartRecordingClicked
+ );
+ }
+
+ @AssistedInject
+ public ScreenRecordDialogDelegate(
+ SystemUIDialog.Factory systemUIDialogFactory,
+ UserContextProvider userContextProvider,
+ @Assisted RecordingController controller,
+ @Assisted @Nullable Runnable onStartRecordingClicked) {
+ mSystemUIDialogFactory = systemUIDialogFactory;
mUserContextProvider = userContextProvider;
+ mController = controller;
mOnStartRecordingClicked = onStartRecordingClicked;
}
@Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
+ public SystemUIDialog createDialog() {
+ return mSystemUIDialogFactory.create(this);
+ }
- Window window = getWindow();
+ @Override
+ public void onCreate(SystemUIDialog dialog, Bundle savedInstanceState) {
+ Window window = dialog.getWindow();
window.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS);
window.setGravity(Gravity.CENTER);
- setTitle(R.string.screenrecord_title);
+ dialog.setTitle(R.string.screenrecord_title);
- setContentView(R.layout.screen_record_dialog);
+ dialog.setContentView(R.layout.screen_record_dialog);
- TextView cancelBtn = findViewById(R.id.button_cancel);
- cancelBtn.setOnClickListener(v -> dismiss());
- TextView startBtn = findViewById(R.id.button_start);
+ TextView cancelBtn = dialog.findViewById(R.id.button_cancel);
+ cancelBtn.setOnClickListener(v -> dialog.dismiss());
+ TextView startBtn = dialog.findViewById(R.id.button_start);
startBtn.setOnClickListener(v -> {
if (mOnStartRecordingClicked != null) {
// Note that it is important to run this callback before dismissing, so that the
@@ -104,13 +121,13 @@
// Start full-screen recording
requestScreenCapture(/* captureTarget= */ null);
- dismiss();
+ dialog.dismiss();
});
- mAudioSwitch = findViewById(R.id.screenrecord_audio_switch);
- mTapsSwitch = findViewById(R.id.screenrecord_taps_switch);
- mOptions = findViewById(R.id.screen_recording_options);
- ArrayAdapter a = new ScreenRecordingAdapter(getContext().getApplicationContext(),
+ mAudioSwitch = dialog.findViewById(R.id.screenrecord_audio_switch);
+ mTapsSwitch = dialog.findViewById(R.id.screenrecord_taps_switch);
+ mOptions = dialog.findViewById(R.id.screen_recording_options);
+ ArrayAdapter a = new ScreenRecordingAdapter(dialog.getContext().getApplicationContext(),
android.R.layout.simple_spinner_dropdown_item,
MODES);
a.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
index 3eb26f4..ba775cd3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
@@ -46,17 +46,20 @@
import com.android.systemui.res.R
import com.android.systemui.settings.UserContextProvider
import com.android.systemui.statusbar.phone.SystemUIDialog
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
/** Dialog to select screen recording options */
-class ScreenRecordPermissionDialogDelegate(
- private val hostUserHandle: UserHandle,
- private val hostUid: Int,
- private val controller: RecordingController,
+class ScreenRecordPermissionDialogDelegate @AssistedInject constructor(
+ @Assisted private val hostUserHandle: UserHandle,
+ @Assisted private val hostUid: Int,
+ @Assisted private val controller: RecordingController,
private val activityStarter: ActivityStarter,
private val userContextProvider: UserContextProvider,
- private val onStartRecordingClicked: Runnable?,
+ @Assisted private val onStartRecordingClicked: Runnable?,
mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
- private val systemUIDialogFactory: SystemUIDialog.Factory
+ private val systemUIDialogFactory: SystemUIDialog.Factory,
) :
BaseMediaProjectionPermissionDialogDelegate<SystemUIDialog>(
createOptionList(),
@@ -65,8 +68,19 @@
mediaProjectionMetricsLogger,
R.drawable.ic_screenrecord,
R.color.screenrecord_icon_color
- ),
- SystemUIDialog.Delegate {
+ ), SystemUIDialog.Delegate {
+
+
+ @AssistedFactory
+ interface Factory {
+ fun create(
+ recordingController: RecordingController,
+ hostUserHandle: UserHandle,
+ hostUid: Int,
+ onStartRecordingClicked: Runnable?
+ ): ScreenRecordPermissionDialogDelegate
+ }
+
private lateinit var tapsSwitch: Switch
private lateinit var tapsSwitchContainer: ViewGroup
private lateinit var tapsView: View
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
new file mode 100644
index 0000000..2294fc0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+import android.animation.Animator
+import android.app.Notification
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+import android.view.Display
+import android.view.LayoutInflater
+import android.view.ScrollCaptureResponse
+import android.view.View
+import android.view.ViewTreeObserver
+import android.view.WindowInsets
+import android.window.OnBackInvokedDispatcher
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.res.R
+
+/**
+ * Legacy implementation of screenshot view methods. Just proxies the calls down into the original
+ * ScreenshotView.
+ */
+class LegacyScreenshotViewProxy(context: Context) : ScreenshotViewProxy {
+ override val view: ScreenshotView =
+ LayoutInflater.from(context).inflate(R.layout.screenshot, null) as ScreenshotView
+ override val internalInsetsListener: ViewTreeObserver.OnComputeInternalInsetsListener
+ override val screenshotPreview: View
+
+ override var defaultDisplay: Int = Display.DEFAULT_DISPLAY
+ set(value) {
+ view.setDefaultDisplay(value)
+ }
+ override var defaultTimeoutMillis: Long = 6000
+ set(value) {
+ view.setDefaultTimeoutMillis(value)
+ }
+ override var onKeyListener: View.OnKeyListener? = null
+ set(value) {
+ view.setOnKeyListener(value)
+ }
+ override var flags: FeatureFlags? = null
+ set(value) {
+ view.setFlags(value)
+ }
+ override var packageName: String = ""
+ set(value) {
+ view.setPackageName(value)
+ }
+ override var logger: UiEventLogger? = null
+ set(value) {
+ view.setUiEventLogger(value)
+ }
+ override var callbacks: ScreenshotView.ScreenshotViewCallback? = null
+ set(value) {
+ view.setCallbacks(value)
+ }
+ override var screenshot: ScreenshotData? = null
+ set(value) {
+ view.setScreenshot(value)
+ }
+
+ override val isAttachedToWindow
+ get() = view.isAttachedToWindow
+ override val isDismissing
+ get() = view.isDismissing
+ override val isPendingSharedTransition
+ get() = view.isPendingSharedTransition
+
+ init {
+ internalInsetsListener = view
+ screenshotPreview = view.screenshotPreview
+ }
+
+ override fun reset() = view.reset()
+ override fun updateInsets(insets: WindowInsets) = view.updateInsets(insets)
+ override fun updateOrientation(insets: WindowInsets) = view.updateOrientation(insets)
+
+ override fun badgeScreenshot(userBadgedIcon: Drawable) = view.badgeScreenshot(userBadgedIcon)
+
+ override fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator =
+ view.createScreenshotDropInAnimation(screenRect, showFlash)
+
+ override fun addQuickShareChip(quickShareAction: Notification.Action) =
+ view.addQuickShareChip(quickShareAction)
+
+ override fun setChipIntents(imageData: ScreenshotController.SavedImageData) =
+ view.setChipIntents(imageData)
+
+ override fun animateDismissal() = view.animateDismissal()
+
+ override fun showScrollChip(packageName: String, onClick: Runnable) =
+ view.showScrollChip(packageName, onClick)
+
+ override fun hideScrollChip() = view.hideScrollChip()
+
+ override fun prepareScrollingTransition(
+ response: ScrollCaptureResponse,
+ screenBitmap: Bitmap,
+ newScreenshot: Bitmap,
+ screenshotTakenInPortrait: Boolean
+ ) =
+ view.prepareScrollingTransition(
+ response,
+ screenBitmap,
+ newScreenshot,
+ screenshotTakenInPortrait
+ )
+
+ override fun startLongScreenshotTransition(
+ transitionDestination: Rect,
+ onTransitionEnd: Runnable,
+ longScreenshot: ScrollCaptureController.LongScreenshot
+ ) = view.startLongScreenshotTransition(transitionDestination, onTransitionEnd, longScreenshot)
+
+ override fun restoreNonScrollingUi() = view.restoreNonScrollingUi()
+
+ override fun stopInputListening() = view.stopInputListening()
+
+ override fun requestFocus() {
+ view.requestFocus()
+ }
+
+ override fun announceForAccessibility(string: String) = view.announceForAccessibility(string)
+
+ override fun addOnAttachStateChangeListener(listener: View.OnAttachStateChangeListener) =
+ view.addOnAttachStateChangeListener(listener)
+
+ override fun findOnBackInvokedDispatcher(): OnBackInvokedDispatcher? =
+ view.findOnBackInvokedDispatcher()
+
+ override fun getViewTreeObserver(): ViewTreeObserver = view.viewTreeObserver
+
+ override fun post(runnable: Runnable) {
+ view.post(runnable)
+ }
+
+ class Factory : ScreenshotViewProxy.Factory {
+ override fun getProxy(context: Context): ScreenshotViewProxy {
+ return LegacyScreenshotViewProxy(context)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index ee3e7ba..13448d2 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -65,7 +65,6 @@
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
import android.view.KeyEvent;
-import android.view.LayoutInflater;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationTarget;
import android.view.ScrollCaptureResponse;
@@ -165,7 +164,7 @@
/**
* Structure returned by the SaveImageInBackgroundTask
*/
- static class SavedImageData {
+ public static class SavedImageData {
public Uri uri;
public List<Notification.Action> smartActions;
public Notification.Action quickShareAction;
@@ -237,6 +236,7 @@
private final WindowContext mContext;
private final FeatureFlags mFlags;
+ private final ScreenshotViewProxy mViewProxy;
private final ScreenshotNotificationsController mNotificationsController;
private final ScreenshotSmartActions mScreenshotSmartActions;
private final UiEventLogger mUiEventLogger;
@@ -272,7 +272,6 @@
respondToKeyDismissal();
};
- private ScreenshotView mScreenshotView;
private final MessageContainerController mMessageContainerController;
private Bitmap mScreenBitmap;
private SaveImageInBackgroundTask mSaveInBgTask;
@@ -305,6 +304,7 @@
ScreenshotController(
Context context,
FeatureFlags flags,
+ ScreenshotViewProxy.Factory viewProxyFactory,
ScreenshotSmartActions screenshotSmartActions,
ScreenshotNotificationsController.Factory screenshotNotificationsControllerFactory,
ScrollCaptureClient scrollCaptureClient,
@@ -360,6 +360,8 @@
mMessageContainerController = messageContainerController;
mAssistContentRequester = assistContentRequester;
+ mViewProxy = viewProxyFactory.getProxy(mContext);
+
mAccessibilityManager = AccessibilityManager.getInstance(mContext);
// Setup the window that we are going to use
@@ -461,7 +463,7 @@
// The window is focusable by default
setWindowFocusable(true);
- mScreenshotView.requestFocus();
+ mViewProxy.requestFocus();
enqueueScrollCaptureRequest(screenshot.getUserHandle());
@@ -485,10 +487,10 @@
mMessageContainerController.onScreenshotTaken(screenshot);
});
- mScreenshotView.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon(
+ mViewProxy.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon(
mContext.getDrawable(R.drawable.overlay_badge_background),
screenshot.getUserHandle()));
- mScreenshotView.setScreenshot(screenshot);
+ mViewProxy.setScreenshot(screenshot);
// ignore system bar insets for the purpose of window layout
mWindow.getDecorView().setOnApplyWindowInsetsListener(
@@ -503,31 +505,31 @@
void prepareViewForNewScreenshot(ScreenshotData screenshot, String oldPackageName) {
withWindowAttached(() -> {
if (mUserManager.isManagedProfile(screenshot.getUserHandle().getIdentifier())) {
- mScreenshotView.announceForAccessibility(mContext.getResources().getString(
+ mViewProxy.announceForAccessibility(mContext.getResources().getString(
R.string.screenshot_saving_work_profile_title));
} else {
- mScreenshotView.announceForAccessibility(
+ mViewProxy.announceForAccessibility(
mContext.getResources().getString(R.string.screenshot_saving_title));
}
});
- mScreenshotView.reset();
+ mViewProxy.reset();
- if (mScreenshotView.isAttachedToWindow()) {
+ if (mViewProxy.isAttachedToWindow()) {
// if we didn't already dismiss for another reason
- if (!mScreenshotView.isDismissing()) {
+ if (!mViewProxy.isDismissing()) {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_REENTERED, 0,
oldPackageName);
}
if (DEBUG_WINDOW) {
Log.d(TAG, "saveScreenshot: screenshotView is already attached, resetting. "
- + "(dismissing=" + mScreenshotView.isDismissing() + ")");
+ + "(dismissing=" + mViewProxy.isDismissing() + ")");
}
}
- mScreenshotView.setPackageName(mPackageName);
+ mViewProxy.setPackageName(mPackageName);
- mScreenshotView.updateOrientation(
+ mViewProxy.updateOrientation(
mWindowManager.getCurrentWindowMetrics().getWindowInsets());
}
@@ -539,7 +541,7 @@
Log.d(TAG, "dismissScreenshot");
}
// If we're already animating out, don't restart the animation
- if (mScreenshotView.isDismissing()) {
+ if (mViewProxy.isDismissing()) {
if (DEBUG_DISMISS) {
Log.v(TAG, "Already dismissing, ignoring duplicate command");
}
@@ -547,11 +549,11 @@
}
mUiEventLogger.log(event, 0, mPackageName);
mScreenshotHandler.cancelTimeout();
- mScreenshotView.animateDismissal();
+ mViewProxy.animateDismissal();
}
boolean isPendingSharedTransition() {
- return mScreenshotView.isPendingSharedTransition();
+ return mViewProxy.isPendingSharedTransition();
}
// Any cleanup needed when the service is being destroyed.
@@ -576,7 +578,7 @@
private void releaseMediaPlayer() {
if (mScreenshotSoundController == null) return;
- mScreenshotSoundController.releaseScreenshotSound();
+ mScreenshotSoundController.releaseScreenshotSoundAsync();
}
private void respondToKeyDismissal() {
@@ -591,18 +593,15 @@
Log.d(TAG, "reloadAssets()");
}
- // Inflate the screenshot layout
- mScreenshotView = (ScreenshotView)
- LayoutInflater.from(mContext).inflate(R.layout.screenshot, null);
- mMessageContainerController.setView(mScreenshotView);
- mScreenshotView.addOnAttachStateChangeListener(
+ mMessageContainerController.setView(mViewProxy.getView());
+ mViewProxy.addOnAttachStateChangeListener(
new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(@NonNull View v) {
if (DEBUG_INPUT) {
Log.d(TAG, "Registering Predictive Back callback");
}
- mScreenshotView.findOnBackInvokedDispatcher().registerOnBackInvokedCallback(
+ mViewProxy.findOnBackInvokedDispatcher().registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback);
}
@@ -611,11 +610,12 @@
if (DEBUG_INPUT) {
Log.d(TAG, "Unregistering Predictive Back callback");
}
- mScreenshotView.findOnBackInvokedDispatcher()
+ mViewProxy.findOnBackInvokedDispatcher()
.unregisterOnBackInvokedCallback(mOnBackInvokedCallback);
}
});
- mScreenshotView.init(mUiEventLogger, new ScreenshotView.ScreenshotViewCallback() {
+ mViewProxy.setLogger(mUiEventLogger);
+ mViewProxy.setCallbacks(new ScreenshotView.ScreenshotViewCallback() {
@Override
public void onUserInteraction() {
if (DEBUG_INPUT) {
@@ -640,11 +640,12 @@
// TODO(159460485): Remove this when focus is handled properly in the system
setWindowFocusable(false);
}
- }, mFlags);
- mScreenshotView.setDefaultDisplay(mDisplayId);
- mScreenshotView.setDefaultTimeoutMillis(mScreenshotHandler.getDefaultTimeoutMillis());
+ });
+ mViewProxy.setFlags(mFlags);
+ mViewProxy.setDefaultDisplay(mDisplayId);
+ mViewProxy.setDefaultTimeoutMillis(mScreenshotHandler.getDefaultTimeoutMillis());
- mScreenshotView.setOnKeyListener((v, keyCode, event) -> {
+ mViewProxy.setOnKeyListener((v, keyCode, event) -> {
if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) {
if (DEBUG_INPUT) {
Log.d(TAG, "onKeyEvent: " + keyCode);
@@ -658,23 +659,24 @@
if (DEBUG_WINDOW) {
Log.d(TAG, "adding OnComputeInternalInsetsListener");
}
- mScreenshotView.getViewTreeObserver().addOnComputeInternalInsetsListener(mScreenshotView);
+ mViewProxy.getViewTreeObserver().addOnComputeInternalInsetsListener(
+ mViewProxy.getInternalInsetsListener());
if (DEBUG_WINDOW) {
- Log.d(TAG, "setContentView: " + mScreenshotView);
+ Log.d(TAG, "setContentView: " + mViewProxy.getView());
}
- setContentView(mScreenshotView);
+ setContentView(mViewProxy.getView());
}
private void prepareAnimation(Rect screenRect, boolean showFlash,
Runnable onAnimationComplete) {
- mScreenshotView.getViewTreeObserver().addOnPreDrawListener(
+ mViewProxy.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
if (DEBUG_WINDOW) {
Log.d(TAG, "onPreDraw: startAnimation");
}
- mScreenshotView.getViewTreeObserver().removeOnPreDrawListener(this);
+ mViewProxy.getViewTreeObserver().removeOnPreDrawListener(this);
startAnimation(screenRect, showFlash, onAnimationComplete);
return true;
}
@@ -694,13 +696,13 @@
if (mConfigChanges.applyNewConfig(mContext.getResources())) {
// Hide the scroll chip until we know it's available in this
// orientation
- mScreenshotView.hideScrollChip();
+ mViewProxy.hideScrollChip();
// Delay scroll capture eval a bit to allow the underlying activity
// to set up in the new orientation.
mScreenshotHandler.postDelayed(() -> {
requestScrollCapture(owner);
}, 150);
- mScreenshotView.updateInsets(
+ mViewProxy.updateInsets(
mWindowManager.getCurrentWindowMetrics().getWindowInsets());
// Screenshot animation calculations won't be valid anymore,
// so just end
@@ -759,16 +761,16 @@
+ mLastScrollCaptureResponse.getWindowTitle() + "]");
final ScrollCaptureResponse response = mLastScrollCaptureResponse;
- mScreenshotView.showScrollChip(response.getPackageName(), /* onClick */ () -> {
+ mViewProxy.showScrollChip(response.getPackageName(), /* onClick */ () -> {
DisplayMetrics displayMetrics = new DisplayMetrics();
getDisplay().getRealMetrics(displayMetrics);
Bitmap newScreenshot = mImageCapture.captureDisplay(mDisplayId,
new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels));
- mScreenshotView.prepareScrollingTransition(response, mScreenBitmap, newScreenshot,
+ mViewProxy.prepareScrollingTransition(response, mScreenBitmap, newScreenshot,
mScreenshotTakenInPortrait);
// delay starting scroll capture to make sure the scrim is up before the app moves
- mScreenshotView.post(() -> runBatchScrollCapture(response, owner));
+ mViewProxy.post(() -> runBatchScrollCapture(response, owner));
});
} catch (InterruptedException | ExecutionException e) {
Log.e(TAG, "requestScrollCapture failed", e);
@@ -794,19 +796,19 @@
return;
} catch (InterruptedException | ExecutionException e) {
Log.e(TAG, "Exception", e);
- mScreenshotView.restoreNonScrollingUi();
+ mViewProxy.restoreNonScrollingUi();
return;
}
if (longScreenshot.getHeight() == 0) {
- mScreenshotView.restoreNonScrollingUi();
+ mViewProxy.restoreNonScrollingUi();
return;
}
mLongScreenshotHolder.setLongScreenshot(longScreenshot);
mLongScreenshotHolder.setTransitionDestinationCallback(
(transitionDestination, onTransitionEnd) -> {
- mScreenshotView.startLongScreenshotTransition(
+ mViewProxy.startLongScreenshotTransition(
transitionDestination, onTransitionEnd,
longScreenshot);
// TODO: Do this via ActionIntentExecutor instead.
@@ -882,16 +884,14 @@
}
mWindowManager.removeViewImmediate(decorView);
}
- // Ensure that we remove the input monitor
- if (mScreenshotView != null) {
- mScreenshotView.stopInputListening();
- }
+
+ mViewProxy.stopInputListening();
}
private void playCameraSoundIfNeeded() {
if (mScreenshotSoundController == null) return;
// the controller is not-null only on the default display controller
- mScreenshotSoundController.playCameraSound();
+ mScreenshotSoundController.playScreenshotSoundAsync();
}
/**
@@ -932,7 +932,7 @@
}
mScreenshotAnimation =
- mScreenshotView.createScreenshotDropInAnimation(screenRect, showFlash);
+ mViewProxy.createScreenshotDropInAnimation(screenRect, showFlash);
if (onAnimationComplete != null) {
mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
@Override
@@ -975,7 +975,7 @@
};
Pair<ActivityOptions, ExitTransitionCoordinator> transition =
ActivityOptions.startSharedElementAnimation(mWindow, callbacks, null,
- Pair.create(mScreenshotView.getScreenshotPreview(),
+ Pair.create(mViewProxy.getScreenshotPreview(),
ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME));
return transition;
@@ -999,7 +999,7 @@
mCurrentRequestCallback.onFinish();
mCurrentRequestCallback = null;
}
- mScreenshotView.reset();
+ mViewProxy.reset();
removeWindow();
mScreenshotHandler.cancelTimeout();
}
@@ -1067,7 +1067,7 @@
}
private void doPostAnimation(ScreenshotController.SavedImageData imageData) {
- mScreenshotView.setChipIntents(imageData);
+ mViewProxy.setChipIntents(imageData);
}
/**
@@ -1084,11 +1084,11 @@
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
- mScreenshotView.addQuickShareChip(quickShareData.quickShareAction);
+ mViewProxy.addQuickShareChip(quickShareData.quickShareAction);
}
});
} else {
- mScreenshotView.addQuickShareChip(quickShareData.quickShareAction);
+ mViewProxy.addQuickShareChip(quickShareData.quickShareAction);
}
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
index 2c0bdde..d3a7fc4a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
@@ -21,22 +21,34 @@
import com.android.app.tracing.coroutines.async
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
-import com.google.errorprone.annotations.CanIgnoreReturnValue
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.TimeoutCancellationException
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout
/** Controls sound reproduction after a screenshot is taken. */
interface ScreenshotSoundController {
/** Reproduces the camera sound. */
- @CanIgnoreReturnValue fun playCameraSound(): Deferred<Unit>
+ suspend fun playScreenshotSound()
- /** Releases the sound. [playCameraSound] behaviour is undefined after this has been called. */
- @CanIgnoreReturnValue fun releaseScreenshotSound(): Deferred<Unit>
+ /**
+ * Releases the sound. [playScreenshotSound] behaviour is undefined after this has been called.
+ */
+ suspend fun releaseScreenshotSound()
+
+ /** Reproduces the camera sound. Used for compatibility with Java code. */
+ fun playScreenshotSoundAsync()
+
+ /**
+ * Releases the sound. [playScreenshotSound] behaviour is undefined after this has been called.
+ * Used for compatibility with Java code.
+ */
+ fun releaseScreenshotSoundAsync()
}
class ScreenshotSoundControllerImpl
@@ -47,8 +59,8 @@
@Background private val bgDispatcher: CoroutineDispatcher
) : ScreenshotSoundController {
- val player: Deferred<MediaPlayer?> =
- coroutineScope.async("loadCameraSound", bgDispatcher) {
+ private val player: Deferred<MediaPlayer?> =
+ coroutineScope.async("loadScreenshotSound", bgDispatcher) {
try {
soundProvider.getScreenshotSound()
} catch (e: IllegalStateException) {
@@ -57,8 +69,8 @@
}
}
- override fun playCameraSound(): Deferred<Unit> {
- return coroutineScope.async("playCameraSound", bgDispatcher) {
+ override suspend fun playScreenshotSound() {
+ withContext(bgDispatcher) {
try {
player.await()?.start()
} catch (e: IllegalStateException) {
@@ -68,8 +80,8 @@
}
}
- override fun releaseScreenshotSound(): Deferred<Unit> {
- return coroutineScope.async("releaseScreenshotSound", bgDispatcher) {
+ override suspend fun releaseScreenshotSound() {
+ withContext(bgDispatcher) {
try {
withTimeout(1.seconds) { player.await()?.release() }
} catch (e: TimeoutCancellationException) {
@@ -79,6 +91,14 @@
}
}
+ override fun playScreenshotSoundAsync() {
+ coroutineScope.launch { playScreenshotSound() }
+ }
+
+ override fun releaseScreenshotSoundAsync() {
+ coroutineScope.launch { releaseScreenshotSound() }
+ }
+
private companion object {
const val TAG = "ScreenshotSoundControllerImpl"
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index be30a15..8a8766d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -102,7 +102,7 @@
public class ScreenshotView extends FrameLayout implements
ViewTreeObserver.OnComputeInternalInsetsListener {
- interface ScreenshotViewCallback {
+ public interface ScreenshotViewCallback {
void onUserInteraction();
void onAction(Intent intent, UserHandle owner, boolean overrideTransition);
@@ -426,15 +426,15 @@
return mScreenshotPreview;
}
- /**
- * Set up the logger and callback on dismissal.
- *
- * Note: must be called before any other (non-constructor) method or null pointer exceptions
- * may occur.
- */
- void init(UiEventLogger uiEventLogger, ScreenshotViewCallback callbacks, FeatureFlags flags) {
+ void setUiEventLogger(UiEventLogger uiEventLogger) {
mUiEventLogger = uiEventLogger;
+ }
+
+ void setCallbacks(ScreenshotViewCallback callbacks) {
mCallbacks = callbacks;
+ }
+
+ void setFlags(FeatureFlags flags) {
mFlags = flags;
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
new file mode 100644
index 0000000..0064521
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+import android.animation.Animator
+import android.app.Notification
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+import android.view.ScrollCaptureResponse
+import android.view.View
+import android.view.View.OnKeyListener
+import android.view.ViewGroup
+import android.view.ViewTreeObserver
+import android.view.WindowInsets
+import android.window.OnBackInvokedDispatcher
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.flags.FeatureFlags
+
+/** Abstraction of the surface between ScreenshotController and ScreenshotView */
+interface ScreenshotViewProxy {
+ val view: ViewGroup
+ val internalInsetsListener: ViewTreeObserver.OnComputeInternalInsetsListener
+ val screenshotPreview: View
+
+ var defaultDisplay: Int
+ var defaultTimeoutMillis: Long
+ var onKeyListener: OnKeyListener?
+ var flags: FeatureFlags?
+ var packageName: String
+ var logger: UiEventLogger?
+ var callbacks: ScreenshotView.ScreenshotViewCallback?
+ var screenshot: ScreenshotData?
+
+ val isAttachedToWindow: Boolean
+ val isDismissing: Boolean
+ val isPendingSharedTransition: Boolean
+
+ fun reset()
+ fun updateInsets(insets: WindowInsets)
+ fun updateOrientation(insets: WindowInsets)
+ fun badgeScreenshot(userBadgedIcon: Drawable)
+ fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator
+ fun addQuickShareChip(quickShareAction: Notification.Action)
+ fun setChipIntents(imageData: ScreenshotController.SavedImageData)
+ fun animateDismissal()
+
+ fun showScrollChip(packageName: String, onClick: Runnable)
+ fun hideScrollChip()
+ fun prepareScrollingTransition(
+ response: ScrollCaptureResponse,
+ screenBitmap: Bitmap,
+ newScreenshot: Bitmap,
+ screenshotTakenInPortrait: Boolean
+ )
+ fun startLongScreenshotTransition(
+ transitionDestination: Rect,
+ onTransitionEnd: Runnable,
+ longScreenshot: ScrollCaptureController.LongScreenshot
+ )
+ fun restoreNonScrollingUi()
+
+ fun stopInputListening()
+ fun requestFocus()
+ fun announceForAccessibility(string: String)
+ fun addOnAttachStateChangeListener(listener: View.OnAttachStateChangeListener)
+ fun findOnBackInvokedDispatcher(): OnBackInvokedDispatcher?
+ fun getViewTreeObserver(): ViewTreeObserver
+ fun post(runnable: Runnable)
+
+ interface Factory {
+ fun getProxy(context: Context): ScreenshotViewProxy
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index bb34ede..8a2678c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -75,7 +75,7 @@
private String mWindowOwner;
private volatile boolean mCancelled;
- static class LongScreenshot {
+ public static class LongScreenshot {
private final ImageTileSet mImageTileSet;
private final Session mSession;
// TODO: Add UserHandle so LongScreenshots can adhere to work profile screenshot policy
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
index 3797b8b..a00c81d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
@@ -20,6 +20,7 @@
import com.android.systemui.screenshot.ImageCapture;
import com.android.systemui.screenshot.ImageCaptureImpl;
+import com.android.systemui.screenshot.LegacyScreenshotViewProxy;
import com.android.systemui.screenshot.RequestProcessor;
import com.android.systemui.screenshot.ScreenshotPolicy;
import com.android.systemui.screenshot.ScreenshotPolicyImpl;
@@ -29,12 +30,14 @@
import com.android.systemui.screenshot.ScreenshotSoundControllerImpl;
import com.android.systemui.screenshot.ScreenshotSoundProvider;
import com.android.systemui.screenshot.ScreenshotSoundProviderImpl;
+import com.android.systemui.screenshot.ScreenshotViewProxy;
import com.android.systemui.screenshot.TakeScreenshotService;
import com.android.systemui.screenshot.appclips.AppClipsScreenshotHelperService;
import com.android.systemui.screenshot.appclips.AppClipsService;
import dagger.Binds;
import dagger.Module;
+import dagger.Provides;
import dagger.multibindings.ClassKey;
import dagger.multibindings.IntoMap;
@@ -81,4 +84,9 @@
@Binds
abstract ScreenshotSoundController bindScreenshotSoundController(
ScreenshotSoundControllerImpl screenshotSoundProviderImpl);
+
+ @Provides
+ static ScreenshotViewProxy.Factory providesScreenshotViewProxyFactory() {
+ return new LegacyScreenshotViewProxy.Factory();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 1566de5..2fd438b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -351,7 +351,7 @@
private final AccessibilityDelegate mAccessibilityDelegate = new ShadeAccessibilityDelegate();
private final NotificationGutsManager mGutsManager;
private final AlternateBouncerInteractor mAlternateBouncerInteractor;
- private final QuickSettingsController mQsController;
+ private final QuickSettingsControllerImpl mQsController;
private final NaturalScrollingSettingObserver mNaturalScrollingSettingObserver;
private final TouchHandler mTouchHandler = new TouchHandler();
@@ -726,7 +726,7 @@
TapAgainViewController tapAgainViewController,
NavigationModeController navigationModeController,
NavigationBarController navigationBarController,
- QuickSettingsController quickSettingsController,
+ QuickSettingsControllerImpl quickSettingsController,
FragmentService fragmentService,
IStatusBarService statusBarService,
ContentResolver contentResolver,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.kt b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.kt
new file mode 100644
index 0000000..c96a339
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+interface QuickSettingsController {
+ /** Returns whether or not QuickSettings is expanded. */
+ val expanded: Boolean
+
+ /** Returns whether or not QuickSettings is being customized. */
+ val isCustomizing: Boolean
+
+ /** Returns Whether we should intercept a gesture to open Quick Settings. */
+ @Deprecated("specific to legacy touch handling")
+ fun shouldQuickSettingsIntercept(x: Float, y: Float, yDiff: Float): Boolean
+
+ /** Closes the Qs customizer. */
+ fun closeQsCustomizer()
+
+ /**
+ * This method closes QS but in split shade it should be used only in special cases: to make
+ * sure QS closes when shade is closed as well. Otherwise it will result in QS disappearing from
+ * split shade
+ */
+ @Deprecated("specific to legacy split shade") fun closeQs()
+
+ /** Calculate top padding for notifications */
+ @Deprecated("specific to legacy DebugDrawable")
+ fun calculateNotificationsTopPadding(
+ isShadeExpanding: Boolean,
+ keyguardNotificationStaticPadding: Int,
+ expandedFraction: Float,
+ ): Float
+
+ /** Calculate height of QS panel */
+ @Deprecated("specific to legacy DebugDrawable")
+ fun calculatePanelHeightExpanded(stackScrollerPadding: Int): Int
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
rename to packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
index a5c0553..19d98a0 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
@@ -118,7 +118,7 @@
* TODO (b/264460656) make this dumpable
*/
@SysUISingleton
-public class QuickSettingsController implements Dumpable {
+public class QuickSettingsControllerImpl implements QuickSettingsController, Dumpable {
public static final String TAG = "QuickSettingsController";
public static final int SHADE_BACK_ANIM_SCALE_MULTIPLIER = 100;
@@ -252,7 +252,7 @@
/**
* The window width currently in effect -- used together with
- * {@link QuickSettingsController#mCachedGestureInsets} to decide whether a back gesture should
+ * {@link QuickSettingsControllerImpl#mCachedGestureInsets} to decide whether a back gesture should
* receive a horizontal swipe inwards from the left/right vertical edge of the screen.
* We cache this on ACTION_DOWN, and query it during both ACTION_DOWN and ACTION_MOVE events.
*/
@@ -304,7 +304,7 @@
private final QS.ScrollListener mQsScrollListener = this::onScroll;
@Inject
- public QuickSettingsController(
+ public QuickSettingsControllerImpl(
Lazy<NotificationPanelViewController> panelViewControllerLazy,
NotificationPanelView panelView,
QsFrameTranslateController qsFrameTranslateController,
@@ -399,23 +399,23 @@
mQs = qs;
}
- public void setExpansionHeightListener(ExpansionHeightListener listener) {
+ void setExpansionHeightListener(ExpansionHeightListener listener) {
mExpansionHeightListener = listener;
}
- public void setQsStateUpdateListener(QsStateUpdateListener listener) {
+ void setQsStateUpdateListener(QsStateUpdateListener listener) {
mQsStateUpdateListener = listener;
}
- public void setApplyClippingImmediatelyListener(ApplyClippingImmediatelyListener listener) {
+ void setApplyClippingImmediatelyListener(ApplyClippingImmediatelyListener listener) {
mApplyClippingImmediatelyListener = listener;
}
- public void setFlingQsWithoutClickListener(FlingQsWithoutClickListener listener) {
+ void setFlingQsWithoutClickListener(FlingQsWithoutClickListener listener) {
mFlingQsWithoutClickListener = listener;
}
- public void setExpansionHeightSetToMaxListener(ExpansionHeightSetToMaxListener callback) {
+ void setExpansionHeightSetToMaxListener(ExpansionHeightSetToMaxListener callback) {
mExpansionHeightSetToMaxListener = callback;
}
@@ -507,15 +507,11 @@
&& mPanelView.getRootWindowInsets().isVisible(ime());
}
- public boolean isExpansionEnabled() {
+ boolean isExpansionEnabled() {
return mExpansionEnabledPolicy && mExpansionEnabledAmbient
&& !isRemoteInputActiveWithKeyboardUp();
}
- public float getTransitioningToFullShadeProgress() {
- return mTransitioningToFullShadeProgress;
- }
-
/** */
@VisibleForTesting
boolean isExpandImmediate() {
@@ -536,7 +532,7 @@
* Computes (and caches) the gesture insets for the current window. Intended to be called
* on ACTION_DOWN, and safely queried repeatedly thereafter during ACTION_MOVE events.
*/
- public void updateGestureInsetsCache() {
+ void updateGestureInsetsCache() {
WindowManager wm = this.mPanelView.getContext().getSystemService(WindowManager.class);
WindowMetrics windowMetrics = wm.getCurrentWindowMetrics();
mCachedGestureInsets = windowMetrics.getWindowInsets().getInsets(
@@ -548,7 +544,7 @@
* Returns whether x coordinate lies in the vertical edges of the screen
* (the only place where a back gesture can be initiated).
*/
- public boolean shouldBackBypassQuickSettings(float touchX) {
+ boolean shouldBackBypassQuickSettings(float touchX) {
return (touchX < mCachedGestureInsets.left)
|| (touchX > mCachedWindowWidth - mCachedGestureInsets.right);
}
@@ -592,6 +588,7 @@
return twoFingerDrag || stylusButtonClickDrag || mouseButtonClickDrag;
}
+ @Override
public boolean getExpanded() {
return mShadeRepository.getLegacyIsQsExpanded().getValue();
}
@@ -601,7 +598,7 @@
return mShadeRepository.getLegacyQsTracking().getValue();
}
- public boolean getFullyExpanded() {
+ boolean getFullyExpanded() {
return mFullyExpanded;
}
@@ -623,27 +620,28 @@
return mQs != null;
}
+ @Override
public boolean isCustomizing() {
return isQsFragmentCreated() && mQs.isCustomizing();
}
- public float getExpansionHeight() {
+ float getExpansionHeight() {
return mExpansionHeight;
}
- public boolean getExpandedWhenExpandingStarted() {
+ boolean getExpandedWhenExpandingStarted() {
return mExpandedWhenExpandingStarted;
}
- public int getMinExpansionHeight() {
+ int getMinExpansionHeight() {
return mMinExpansionHeight;
}
- public boolean isFullyExpandedAndTouchesDisallowed() {
+ boolean isFullyExpandedAndTouchesDisallowed() {
return isQsFragmentCreated() && getFullyExpanded() && disallowTouches();
}
- public int getMaxExpansionHeight() {
+ int getMaxExpansionHeight() {
return mMaxExpansionHeight;
}
@@ -654,13 +652,14 @@
return !mTouchAboveFalsingThreshold;
}
- public int getFalsingThreshold() {
+ int getFalsingThreshold() {
return mFalsingThreshold;
}
/**
* Returns Whether we should intercept a gesture to open Quick Settings.
*/
+ @Override
public boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) {
boolean keyguardShowing = mBarState == KEYGUARD;
if (!isExpansionEnabled() || mCollapsedOnDown || (keyguardShowing
@@ -717,7 +716,7 @@
* @param downY the y location where the touch started
* Returns true if the panel could be collapsed because it stared on QQS
*/
- public boolean canPanelCollapseOnQQS(float downX, float downY) {
+ boolean canPanelCollapseOnQQS(float downX, float downY) {
if (mCollapsedOnDown || mBarState == KEYGUARD || getExpanded()) {
return false;
}
@@ -727,6 +726,7 @@
}
/** Closes the Qs customizer. */
+ @Override
public void closeQsCustomizer() {
if (mQs != null) {
mQs.closeCustomizer();
@@ -734,7 +734,7 @@
}
/** Returns whether touches from the notification panel should be disallowed */
- public boolean disallowTouches() {
+ boolean disallowTouches() {
if (mQs != null) {
return mQs.disallowPanelTouches();
} else {
@@ -754,15 +754,11 @@
}
}
- public void setDozing(boolean dozing) {
+ void setDozing(boolean dozing) {
mDozing = dozing;
}
- /**
- * This method closes QS but in split shade it should be used only in special cases: to make
- * sure QS closes when shade is closed as well. Otherwise it will result in QS disappearing
- * from split shade
- */
+ @Override
public void closeQs() {
if (mSplitShadeEnabled) {
mShadeLog.d("Closing QS while in split shade");
@@ -794,7 +790,7 @@
}
/** update Qs height state */
- public void setExpansionHeight(float height) {
+ void setExpansionHeight(float height) {
int maxHeight = getMaxExpansionHeight();
height = Math.min(Math.max(
height, getMinExpansionHeight()), maxHeight);
@@ -817,7 +813,7 @@
}
/** */
- public void setHeightOverrideToDesiredHeight() {
+ void setHeightOverrideToDesiredHeight() {
if (isSizeChangeAnimationRunning() && isQsFragmentCreated()) {
mQs.setHeightOverride(mQs.getDesiredHeight());
}
@@ -919,7 +915,7 @@
}
/** Sets Qs ScrimEnabled and updates QS state. */
- public void setScrimEnabled(boolean scrimEnabled) {
+ void setScrimEnabled(boolean scrimEnabled) {
boolean changed = mScrimEnabled != scrimEnabled;
mScrimEnabled = scrimEnabled;
if (changed) {
@@ -995,7 +991,7 @@
}
/** update expanded state of QS */
- public void updateExpansion() {
+ void updateExpansion() {
if (mQs == null) return;
final float squishiness;
if ((isExpandImmediate() || getExpanded()) && !mSplitShadeEnabled) {
@@ -1053,13 +1049,13 @@
// mTransitioningToFullShadeProgress > 0 means we're doing regular lockscreen to shade
// transition. If that's not the case we should follow QS expansion fraction for when
// user is pulling from the same top to go directly to expanded QS
- return getTransitioningToFullShadeProgress() > 0
+ return mTransitioningToFullShadeProgress > 0
? mLockscreenShadeTransitionController.getQSDragProgress()
: computeExpansionFraction();
}
/** */
- public void updateExpansionEnabledAmbient() {
+ void updateExpansionEnabledAmbient() {
final float scrollRangeToTop = mAmbientState.getTopPadding() - mQuickQsHeaderHeight;
mExpansionEnabledAmbient = mSplitShadeEnabled
|| (mAmbientState.getScrollY() <= scrollRangeToTop);
@@ -1081,7 +1077,7 @@
}
/** Calculate fraction of current QS expansion state */
- public float computeExpansionFraction() {
+ float computeExpansionFraction() {
if (mAnimatingHiddenFromCollapsed) {
// When hiding QS from collapsed state, the expansion can sometimes temporarily
// be larger than 0 because of the timing, leading to flickers.
@@ -1112,7 +1108,7 @@
}
/** Called when shade starts expanding. */
- public void onExpandingStarted(boolean qsFullyExpanded) {
+ void onExpandingStarted(boolean qsFullyExpanded) {
mNotificationStackScrollLayoutController.onExpansionStarted();
mExpandedWhenExpandingStarted = qsFullyExpanded;
mMediaHierarchyManager.setCollapsingShadeFromQS(mExpandedWhenExpandingStarted
@@ -1363,7 +1359,7 @@
}
}
- /** Calculate top padding for notifications */
+ @Override
public float calculateNotificationsTopPadding(boolean isShadeExpanding,
int keyguardNotificationStaticPadding, float expandedFraction) {
float topPadding;
@@ -1404,7 +1400,7 @@
}
}
- /** Calculate height of QS panel */
+ @Override
public int calculatePanelHeightExpanded(int stackScrollerPadding) {
float
notificationHeight =
@@ -1576,7 +1572,6 @@
}
}
- @VisibleForTesting
boolean isTrackingBlocked() {
return mConflictingExpansionGesture && getExpanded();
}
@@ -1591,7 +1586,7 @@
}
/** handles touches in Qs panel area */
- public boolean handleTouch(MotionEvent event, boolean isFullyCollapsed,
+ boolean handleTouch(MotionEvent event, boolean isFullyCollapsed,
boolean isShadeOrQsHeightAnimationRunning) {
if (isSplitShadeAndTouchXOutsideQs(event.getX())) {
return false;
@@ -1747,7 +1742,7 @@
}
/** intercepts touches on Qs panel area. */
- public boolean onIntercept(MotionEvent event) {
+ boolean onIntercept(MotionEvent event) {
int pointerIndex = event.findPointerIndex(mTrackingPointer);
if (pointerIndex < 0) {
pointerIndex = 0;
@@ -1858,7 +1853,7 @@
*
* @param animateAway Do not stop when QS becomes QQS. Fling until QS isn't visible anymore.
*/
- public void animateCloseQs(boolean animateAway) {
+ void animateCloseQs(boolean animateAway) {
if (mExpansionAnimator != null) {
if (!mAnimatorExpand) {
return;
@@ -1877,7 +1872,7 @@
}
/** @see #flingQs(float, int, Runnable, boolean) */
- public void flingQs(float vel, int type) {
+ void flingQs(float vel, int type) {
flingQs(vel, type, null /* onFinishRunnable */, false /* isClick */);
}
@@ -2144,7 +2139,7 @@
}
/** */
- public FragmentHostManager.FragmentListener getQsFragmentListener() {
+ FragmentHostManager.FragmentListener getQsFragmentListener() {
return new QsFragmentListener();
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerSceneImpl.kt
new file mode 100644
index 0000000..b8250cc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerSceneImpl.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.qs.QSContainerController
+import com.android.systemui.qs.ui.adapter.QSSceneAdapter
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import javax.inject.Inject
+
+@SysUISingleton
+class QuickSettingsControllerSceneImpl
+@Inject
+constructor(
+ private val shadeInteractor: ShadeInteractor,
+ private val qsSceneAdapter: QSSceneAdapter,
+ private val qsContainerController: QSContainerController,
+) : QuickSettingsController {
+
+ override val expanded: Boolean
+ get() = shadeInteractor.isQsExpanded.value
+
+ override val isCustomizing: Boolean
+ get() = qsSceneAdapter.isCustomizing.value
+
+ @Deprecated("specific to legacy touch handling")
+ override fun shouldQuickSettingsIntercept(x: Float, y: Float, yDiff: Float): Boolean {
+ throw UnsupportedOperationException()
+ }
+
+ override fun closeQsCustomizer() {
+ qsContainerController.setCustomizerShowing(false)
+ }
+
+ @Deprecated("specific to legacy split shade")
+ override fun closeQs() {
+ // Do nothing
+ }
+
+ @Deprecated("specific to legacy DebugDrawable")
+ override fun calculateNotificationsTopPadding(
+ isShadeExpanding: Boolean,
+ keyguardNotificationStaticPadding: Int,
+ expandedFraction: Float
+ ): Float {
+ throw UnsupportedOperationException()
+ }
+
+ @Deprecated("specific to legacy DebugDrawable")
+ override fun calculatePanelHeightExpanded(stackScrollerPadding: Int): Int {
+ throw UnsupportedOperationException()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index 5632766..504dbfd 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -17,6 +17,8 @@
package com.android.systemui.shade
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.qs.QSContainerController
+import com.android.systemui.qs.ui.adapter.QSSceneAdapterImpl
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.shade.data.repository.PrivacyChipRepository
import com.android.systemui.shade.data.repository.PrivacyChipRepositoryImpl
@@ -111,6 +113,26 @@
sceneContainerOff.get()
}
}
+
+ @Provides
+ @SysUISingleton
+ fun provideQuickSettingsController(
+ sceneContainerFlags: SceneContainerFlags,
+ sceneContainerOn: Provider<QuickSettingsControllerSceneImpl>,
+ sceneContainerOff: Provider<QuickSettingsControllerImpl>,
+ ): QuickSettingsController {
+ return if (sceneContainerFlags.isEnabled()) {
+ sceneContainerOn.get()
+ } else {
+ sceneContainerOff.get()
+ }
+ }
+
+ @Provides
+ @SysUISingleton
+ fun providesQSContainerController(impl: QSSceneAdapterImpl): QSContainerController {
+ return impl
+ }
}
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
index ef4e530..a12b970 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
@@ -84,6 +84,7 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -457,14 +458,43 @@
List<KeyboardShortcutMultiMappingGroup> keyboardShortcutMultiMappingGroups =
new ArrayList<>();
for (KeyboardShortcutGroup group : keyboardShortcutGroups) {
- CharSequence categoryTitle = group.getLabel();
- List<ShortcutMultiMappingInfo> shortcutMultiMappingInfos = new ArrayList<>();
- for (KeyboardShortcutInfo info : group.getItems()) {
- shortcutMultiMappingInfos.add(new ShortcutMultiMappingInfo(info));
- }
- keyboardShortcutMultiMappingGroups.add(
+ KeyboardShortcutMultiMappingGroup mappedGroup =
new KeyboardShortcutMultiMappingGroup(
- categoryTitle, shortcutMultiMappingInfos));
+ group.getLabel(),
+ new ArrayList<>());
+ Map<String, List<ShortcutMultiMappingInfo>> shortcutMap = new LinkedHashMap<>();
+ for (KeyboardShortcutInfo info : group.getItems()) {
+ String label = info.getLabel().toString();
+ Icon icon = info.getIcon();
+ if (shortcutMap.containsKey(label)) {
+ List<ShortcutMultiMappingInfo> shortcuts = shortcutMap.get(label);
+ boolean foundSameIcon = false;
+ for (ShortcutMultiMappingInfo shortcut : shortcuts) {
+ Icon shortcutIcon = shortcut.getIcon();
+ if ((shortcutIcon != null
+ && icon != null
+ && shortcutIcon.sameAs(icon))
+ || (shortcutIcon == null && icon == null)) {
+ foundSameIcon = true;
+ shortcut.addShortcutKeyGroup(new ShortcutKeyGroup(info, null));
+ break;
+ }
+ }
+ if (!foundSameIcon) {
+ shortcuts.add(new ShortcutMultiMappingInfo(info));
+ }
+ } else {
+ List<ShortcutMultiMappingInfo> shortcuts = new ArrayList<>();
+ shortcuts.add(new ShortcutMultiMappingInfo(info));
+ shortcutMap.put(label, shortcuts);
+ }
+ }
+ for (List<ShortcutMultiMappingInfo> shortcutInfos : shortcutMap.values()) {
+ for (ShortcutMultiMappingInfo shortcutInfo : shortcutInfos) {
+ mappedGroup.addItem(shortcutInfo);
+ }
+ }
+ keyboardShortcutMultiMappingGroups.add(mappedGroup);
}
return keyboardShortcutMultiMappingGroups;
}
@@ -1377,7 +1407,9 @@
ShortcutMultiMappingInfo(KeyboardShortcutInfo info) {
mLabel = info.getLabel();
mIcon = info.getIcon();
- mShortcutKeyGroups = Arrays.asList(new ShortcutKeyGroup(info, null));
+ mShortcutKeyGroups = new ArrayList<>(
+ Arrays.asList(new ShortcutKeyGroup(info, null))
+ );
}
CharSequence getLabel() {
@@ -1388,6 +1420,10 @@
return mIcon;
}
+ void addShortcutKeyGroup(ShortcutKeyGroup group) {
+ mShortcutKeyGroups.add(group);
+ }
+
List<ShortcutKeyGroup> getShortcutKeyGroups() {
return mShortcutKeyGroups;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractor.kt
new file mode 100644
index 0000000..1bebbfd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractor.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardOcclusionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChangedBy
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+
+/**
+ * Whether to set the status bar keyguard view occluded or not, and whether to animate that change.
+ */
+data class OccludedState(
+ val occluded: Boolean,
+ val animate: Boolean = false,
+)
+
+/** Handles logic around calls to [StatusBarKeyguardViewManager] in legacy code. */
+@Deprecated("Will be removed once all of SBKVM's responsibilies are refactored.")
+@SysUISingleton
+class StatusBarKeyguardViewManagerInteractor
+@Inject
+constructor(
+ keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
+ powerInteractor: PowerInteractor,
+) {
+ /** Occlusion state to apply whenever a keyguard transition is STARTED, if any. */
+ private val occlusionStateFromStartedStep: Flow<OccludedState> =
+ keyguardTransitionInteractor.startedKeyguardTransitionStep
+ .sample(powerInteractor.detailedWakefulness, ::Pair)
+ .map { (startedStep, wakefulness) ->
+ val transitioningFromPowerButtonGesture =
+ KeyguardState.deviceIsAsleepInState(startedStep.from) &&
+ startedStep.to == KeyguardState.OCCLUDED &&
+ wakefulness.powerButtonLaunchGestureTriggered
+
+ if (
+ startedStep.to == KeyguardState.OCCLUDED && !transitioningFromPowerButtonGesture
+ ) {
+ // Set occluded upon STARTED, *unless* we're transitioning from the power
+ // button, in which case we're going to play an animation over the lockscreen UI
+ // and need to remain unoccluded until the transition finishes.
+ return@map OccludedState(occluded = true, animate = false)
+ }
+
+ if (
+ startedStep.from == KeyguardState.OCCLUDED &&
+ startedStep.to == KeyguardState.LOCKSCREEN
+ ) {
+ // Set unoccluded immediately ONLY if we're transitioning back to the lockscreen
+ // since we need the views visible to animate them back down. This is a special
+ // case due to the way unocclusion remote animations are run. We can remove this
+ // once the unocclude animation uses the return animation framework.
+ return@map OccludedState(occluded = false, animate = false)
+ }
+
+ // Otherwise, wait for the transition to FINISH to decide.
+ return@map null
+ }
+ .filterNotNull()
+
+ /** Occlusion state to apply whenever a keyguard transition is FINISHED. */
+ private val occlusionStateFromFinishedStep =
+ keyguardTransitionInteractor.finishedKeyguardTransitionStep
+ .sample(keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop, ::Pair)
+ .map { (finishedStep, showWhenLockedOnTop) ->
+ // If we're FINISHED in OCCLUDED, we want to render as occluded. We also need to
+ // remain occluded if a SHOW_WHEN_LOCKED activity is on the top of the task stack,
+ // and we're in any state other than GONE. This is necessary, for example, when we
+ // transition from OCCLUDED to a bouncer state. Otherwise, we should not be
+ // occluded.
+ val occluded =
+ finishedStep.to == KeyguardState.OCCLUDED ||
+ (showWhenLockedOnTop && finishedStep.to != KeyguardState.GONE)
+ OccludedState(occluded = occluded, animate = false)
+ }
+
+ /** Occlusion state to apply to SKBVM's setOccluded call. */
+ val keyguardViewOcclusionState =
+ merge(occlusionStateFromStartedStep, occlusionStateFromFinishedStep)
+ .distinctUntilChangedBy {
+ // Don't switch 'animate' values mid-transition.
+ it.occluded
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt
index dab89c5..b90aa10 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt
@@ -19,7 +19,9 @@
import android.widget.flags.Flags.notifLinearlayoutOptimized
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.notification.shared.NotificationViewFlipperPausing
import javax.inject.Inject
+import javax.inject.Provider
interface NotifRemoteViewsFactoryContainer {
val factories: Set<NotifRemoteViewsFactory>
@@ -31,7 +33,8 @@
featureFlags: FeatureFlags,
precomputedTextViewFactory: PrecomputedTextViewFactory,
bigPictureLayoutInflaterFactory: BigPictureLayoutInflaterFactory,
- optimizedLinearLayoutFactory: NotificationOptimizedLinearLayoutFactory
+ optimizedLinearLayoutFactory: NotificationOptimizedLinearLayoutFactory,
+ notificationViewFlipperFactory: Provider<NotificationViewFlipperFactory>,
) : NotifRemoteViewsFactoryContainer {
override val factories: Set<NotifRemoteViewsFactory> = buildSet {
add(precomputedTextViewFactory)
@@ -41,5 +44,8 @@
if (notifLinearlayoutOptimized()) {
add(optimizedLinearLayoutFactory)
}
+ if (NotificationViewFlipperPausing.isEnabled) {
+ add(notificationViewFlipperFactory.get())
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index d308fa5..c17ee39 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -833,143 +833,143 @@
@Nullable InflationCallback endListener, NotificationEntry entry,
ExpandableNotificationRow row, NotificationContentInflaterLogger logger) {
Assert.isMainThread();
+ if (!runningInflations.isEmpty()) {
+ return false;
+ }
NotificationContentView privateLayout = row.getPrivateLayout();
NotificationContentView publicLayout = row.getPublicLayout();
- if (runningInflations.isEmpty()) {
- logger.logAsyncTaskProgress(entry, "finishing");
- boolean setRepliesAndActions = true;
- if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) {
- if (result.inflatedContentView != null) {
- // New view case
- privateLayout.setContractedChild(result.inflatedContentView);
- remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED,
- result.newContentView);
- } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)) {
- // Reinflation case. Only update if it's still cached (i.e. view has not been
- // freed while inflating).
- remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED,
- result.newContentView);
- }
- setRepliesAndActions = true;
+ logger.logAsyncTaskProgress(entry, "finishing");
+ boolean setRepliesAndActions = true;
+ if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) {
+ if (result.inflatedContentView != null) {
+ // New view case
+ privateLayout.setContractedChild(result.inflatedContentView);
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED,
+ result.newContentView);
+ } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)) {
+ // Reinflation case. Only update if it's still cached (i.e. view has not been
+ // freed while inflating).
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED,
+ result.newContentView);
}
-
- if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) {
- if (result.inflatedExpandedView != null) {
- privateLayout.setExpandedChild(result.inflatedExpandedView);
- remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED,
- result.newExpandedView);
- } else if (result.newExpandedView == null) {
- privateLayout.setExpandedChild(null);
- remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED);
- } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)) {
- remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED,
- result.newExpandedView);
- }
- if (result.newExpandedView != null) {
- privateLayout.setExpandedInflatedSmartReplies(
- result.expandedInflatedSmartReplies);
- } else {
- privateLayout.setExpandedInflatedSmartReplies(null);
- }
- row.setExpandable(result.newExpandedView != null);
- setRepliesAndActions = true;
- }
-
- if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) {
- if (result.inflatedHeadsUpView != null) {
- privateLayout.setHeadsUpChild(result.inflatedHeadsUpView);
- remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP,
- result.newHeadsUpView);
- } else if (result.newHeadsUpView == null) {
- privateLayout.setHeadsUpChild(null);
- remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP);
- } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)) {
- remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP,
- result.newHeadsUpView);
- }
- if (result.newHeadsUpView != null) {
- privateLayout.setHeadsUpInflatedSmartReplies(
- result.headsUpInflatedSmartReplies);
- } else {
- privateLayout.setHeadsUpInflatedSmartReplies(null);
- }
- setRepliesAndActions = true;
- }
-
- if (AsyncHybridViewInflation.isEnabled()
- && (reInflateFlags & FLAG_CONTENT_VIEW_SINGLE_LINE) != 0) {
- HybridNotificationView viewHolder = result.mInflatedSingleLineViewHolder;
- SingleLineViewModel viewModel = result.mInflatedSingleLineViewModel;
- if (viewHolder != null && viewModel != null) {
- if (viewModel.isConversation()) {
- SingleLineConversationViewBinder.bind(
- result.mInflatedSingleLineViewModel,
- result.mInflatedSingleLineViewHolder
- );
- } else {
- SingleLineViewBinder.bind(result.mInflatedSingleLineViewModel,
- result.mInflatedSingleLineViewHolder);
- }
- privateLayout.setSingleLineView(result.mInflatedSingleLineViewHolder);
- }
- }
-
- if (setRepliesAndActions) {
- privateLayout.setInflatedSmartReplyState(result.inflatedSmartReplyState);
- }
-
- if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
- if (result.inflatedPublicView != null) {
- publicLayout.setContractedChild(result.inflatedPublicView);
- remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC,
- result.newPublicView);
- } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC)) {
- remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC,
- result.newPublicView);
- }
- }
-
- if (AsyncGroupHeaderViewInflation.isEnabled()) {
- if ((reInflateFlags & FLAG_GROUP_SUMMARY_HEADER) != 0) {
- if (result.mInflatedGroupHeaderView != null) {
- row.setIsLowPriority(false);
- row.setGroupHeader(/* headerView= */ result.mInflatedGroupHeaderView);
- remoteViewCache.putCachedView(entry, FLAG_GROUP_SUMMARY_HEADER,
- result.mNewGroupHeaderView);
- } else if (remoteViewCache.hasCachedView(entry, FLAG_GROUP_SUMMARY_HEADER)) {
- // Re-inflation case. Only update if it's still cached (i.e. view has not
- // been freed while inflating).
- remoteViewCache.putCachedView(entry, FLAG_GROUP_SUMMARY_HEADER,
- result.mNewGroupHeaderView);
- }
- }
-
- if ((reInflateFlags & FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER) != 0) {
- if (result.mInflatedLowPriorityGroupHeaderView != null) {
- // New view case, set row to low priority
- row.setIsLowPriority(true);
- row.setLowPriorityGroupHeader(
- /* headerView= */ result.mInflatedLowPriorityGroupHeaderView);
- remoteViewCache.putCachedView(entry, FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER,
- result.mNewLowPriorityGroupHeaderView);
- } else if (remoteViewCache.hasCachedView(entry,
- FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER)) {
- // Re-inflation case. Only update if it's still cached (i.e. view has not
- // been freed while inflating).
- remoteViewCache.putCachedView(entry, FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER,
- result.mNewGroupHeaderView);
- }
- }
- }
-
- entry.headsUpStatusBarText = result.headsUpStatusBarText;
- entry.headsUpStatusBarTextPublic = result.headsUpStatusBarTextPublic;
- if (endListener != null) {
- endListener.onAsyncInflationFinished(entry);
- }
- return true;
+ setRepliesAndActions = true;
}
- return false;
+
+ if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) {
+ if (result.inflatedExpandedView != null) {
+ privateLayout.setExpandedChild(result.inflatedExpandedView);
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED,
+ result.newExpandedView);
+ } else if (result.newExpandedView == null) {
+ privateLayout.setExpandedChild(null);
+ remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED);
+ } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)) {
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED,
+ result.newExpandedView);
+ }
+ if (result.newExpandedView != null) {
+ privateLayout.setExpandedInflatedSmartReplies(
+ result.expandedInflatedSmartReplies);
+ } else {
+ privateLayout.setExpandedInflatedSmartReplies(null);
+ }
+ row.setExpandable(result.newExpandedView != null);
+ setRepliesAndActions = true;
+ }
+
+ if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) {
+ if (result.inflatedHeadsUpView != null) {
+ privateLayout.setHeadsUpChild(result.inflatedHeadsUpView);
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP,
+ result.newHeadsUpView);
+ } else if (result.newHeadsUpView == null) {
+ privateLayout.setHeadsUpChild(null);
+ remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP);
+ } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)) {
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP,
+ result.newHeadsUpView);
+ }
+ if (result.newHeadsUpView != null) {
+ privateLayout.setHeadsUpInflatedSmartReplies(
+ result.headsUpInflatedSmartReplies);
+ } else {
+ privateLayout.setHeadsUpInflatedSmartReplies(null);
+ }
+ setRepliesAndActions = true;
+ }
+
+ if (AsyncHybridViewInflation.isEnabled()
+ && (reInflateFlags & FLAG_CONTENT_VIEW_SINGLE_LINE) != 0) {
+ HybridNotificationView viewHolder = result.mInflatedSingleLineViewHolder;
+ SingleLineViewModel viewModel = result.mInflatedSingleLineViewModel;
+ if (viewHolder != null && viewModel != null) {
+ if (viewModel.isConversation()) {
+ SingleLineConversationViewBinder.bind(
+ result.mInflatedSingleLineViewModel,
+ result.mInflatedSingleLineViewHolder
+ );
+ } else {
+ SingleLineViewBinder.bind(result.mInflatedSingleLineViewModel,
+ result.mInflatedSingleLineViewHolder);
+ }
+ privateLayout.setSingleLineView(result.mInflatedSingleLineViewHolder);
+ }
+ }
+
+ if (setRepliesAndActions) {
+ privateLayout.setInflatedSmartReplyState(result.inflatedSmartReplyState);
+ }
+
+ if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
+ if (result.inflatedPublicView != null) {
+ publicLayout.setContractedChild(result.inflatedPublicView);
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC,
+ result.newPublicView);
+ } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC)) {
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC,
+ result.newPublicView);
+ }
+ }
+
+ if (AsyncGroupHeaderViewInflation.isEnabled()) {
+ if ((reInflateFlags & FLAG_GROUP_SUMMARY_HEADER) != 0) {
+ if (result.mInflatedGroupHeaderView != null) {
+ row.setIsLowPriority(false);
+ row.setGroupHeader(/* headerView= */ result.mInflatedGroupHeaderView);
+ remoteViewCache.putCachedView(entry, FLAG_GROUP_SUMMARY_HEADER,
+ result.mNewGroupHeaderView);
+ } else if (remoteViewCache.hasCachedView(entry, FLAG_GROUP_SUMMARY_HEADER)) {
+ // Re-inflation case. Only update if it's still cached (i.e. view has not
+ // been freed while inflating).
+ remoteViewCache.putCachedView(entry, FLAG_GROUP_SUMMARY_HEADER,
+ result.mNewGroupHeaderView);
+ }
+ }
+
+ if ((reInflateFlags & FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER) != 0) {
+ if (result.mInflatedLowPriorityGroupHeaderView != null) {
+ // New view case, set row to low priority
+ row.setIsLowPriority(true);
+ row.setLowPriorityGroupHeader(
+ /* headerView= */ result.mInflatedLowPriorityGroupHeaderView);
+ remoteViewCache.putCachedView(entry, FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER,
+ result.mNewLowPriorityGroupHeaderView);
+ } else if (remoteViewCache.hasCachedView(entry,
+ FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER)) {
+ // Re-inflation case. Only update if it's still cached (i.e. view has not
+ // been freed while inflating).
+ remoteViewCache.putCachedView(entry, FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER,
+ result.mNewGroupHeaderView);
+ }
+ }
+ }
+
+ entry.headsUpStatusBarText = result.headsUpStatusBarText;
+ entry.headsUpStatusBarTextPublic = result.headsUpStatusBarTextPublic;
+ if (endListener != null) {
+ endListener.onAsyncInflationFinished(entry);
+ }
+ return true;
}
private static RemoteViews createExpandedView(Notification.Builder builder,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationViewFlipperFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationViewFlipperFactory.kt
new file mode 100644
index 0000000..0594c12
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationViewFlipperFactory.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import android.widget.ViewFlipper
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag
+import com.android.systemui.statusbar.notification.row.ui.viewbinder.NotificationViewFlipperBinder
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.NotificationViewFlipperViewModel
+import com.android.systemui.statusbar.notification.shared.NotificationViewFlipperPausing
+import javax.inject.Inject
+
+/**
+ * A factory which owns the construction of any ViewFlipper inside of Notifications, and binds it
+ * with a view model. This ensures that ViewFlippers are paused when the keyguard is showing.
+ */
+class NotificationViewFlipperFactory
+@Inject
+constructor(
+ private val viewModel: NotificationViewFlipperViewModel,
+) : NotifRemoteViewsFactory {
+ init {
+ /* check if */ NotificationViewFlipperPausing.isUnexpectedlyInLegacyMode()
+ }
+
+ override fun instantiate(
+ row: ExpandableNotificationRow,
+ @InflationFlag layoutType: Int,
+ parent: View?,
+ name: String,
+ context: Context,
+ attrs: AttributeSet
+ ): View? {
+ return when (name) {
+ ViewFlipper::class.java.name,
+ ViewFlipper::class.java.simpleName ->
+ ViewFlipper(context, attrs).also { viewFlipper ->
+ NotificationViewFlipperBinder.bindWhileAttached(viewFlipper, viewModel)
+ }
+ else -> null
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
index 9445d56..ea3036e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
@@ -31,6 +31,7 @@
import com.android.systemui.res.R;
import com.android.systemui.statusbar.InflationTask;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.util.time.SystemClock;
import javax.inject.Inject;
@@ -46,9 +47,14 @@
private NotificationEntry mEntry;
private boolean mCancelled;
private Throwable mInflateOrigin;
+ private final SystemClock mSystemClock;
+ private final RowInflaterTaskLogger mLogger;
+ private long mInflateStartTimeMs;
@Inject
- public RowInflaterTask() {
+ public RowInflaterTask(SystemClock systemClock, RowInflaterTaskLogger logger) {
+ mSystemClock = systemClock;
+ mLogger = logger;
}
/**
@@ -61,29 +67,49 @@
}
mListener = listener;
AsyncLayoutInflater inflater = com.android.systemui.Flags.notificationRowUserContext()
- ? new AsyncLayoutInflater(context, new RowAsyncLayoutInflater(entry))
+ ? new AsyncLayoutInflater(context, makeRowInflater(entry))
: new AsyncLayoutInflater(context);
mEntry = entry;
entry.setInflationTask(this);
+
+ mLogger.logInflateStart(entry);
+ mInflateStartTimeMs = mSystemClock.elapsedRealtime();
inflater.inflate(R.layout.status_bar_notification_row, parent, this);
}
+ private RowAsyncLayoutInflater makeRowInflater(NotificationEntry entry) {
+ return new RowAsyncLayoutInflater(entry, mSystemClock, mLogger);
+ }
+
@VisibleForTesting
static class RowAsyncLayoutInflater implements AsyncLayoutFactory {
private final NotificationEntry mEntry;
+ private final SystemClock mSystemClock;
+ private final RowInflaterTaskLogger mLogger;
- RowAsyncLayoutInflater(NotificationEntry entry) {
+ RowAsyncLayoutInflater(NotificationEntry entry, SystemClock systemClock,
+ RowInflaterTaskLogger logger) {
mEntry = entry;
+ mSystemClock = systemClock;
+ mLogger = logger;
}
@Nullable
@Override
public View onCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context, @NonNull AttributeSet attrs) {
- if (name.equals(ExpandableNotificationRow.class.getName())) {
- return new ExpandableNotificationRow(context, attrs, mEntry);
+ if (!name.equals(ExpandableNotificationRow.class.getName())) {
+ return null;
}
- return null;
+
+ final long startMs = mSystemClock.elapsedRealtime();
+ final ExpandableNotificationRow row =
+ new ExpandableNotificationRow(context, attrs, mEntry);
+ final long elapsedMs = mSystemClock.elapsedRealtime() - startMs;
+
+ mLogger.logCreatedRow(mEntry, elapsedMs);
+
+ return row;
}
@Nullable
@@ -101,6 +127,9 @@
@Override
public void onInflateFinished(View view, int resid, ViewGroup parent) {
+ final long elapsedMs = mSystemClock.elapsedRealtime() - mInflateStartTimeMs;
+ mLogger.logInflateFinish(mEntry, elapsedMs, mCancelled);
+
if (!mCancelled) {
try {
mEntry.onInflationTaskFinished();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTaskLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTaskLogger.kt
new file mode 100644
index 0000000..94da6be
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTaskLogger.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.NotifInflationLog
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.logKey
+import javax.inject.Inject
+
+class RowInflaterTaskLogger @Inject constructor(@NotifInflationLog private val buffer: LogBuffer) {
+ fun logInflateStart(entry: NotificationEntry) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ { str1 = entry.logKey },
+ { "started row inflation for $str1" }
+ )
+ }
+
+ fun logCreatedRow(entry: NotificationEntry, elapsedMs: Long) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = entry.logKey
+ long1 = elapsedMs
+ },
+ { "created row in $long1 ms for $str1" }
+ )
+ }
+
+ fun logInflateFinish(entry: NotificationEntry, elapsedMs: Long, cancelled: Boolean) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = entry.logKey
+ long1 = elapsedMs
+ bool1 = cancelled
+ },
+ { "finished ${if (bool1) "cancelled " else ""}row inflation in $long1 ms for $str1" }
+ )
+ }
+}
+
+private const val TAG = "RowInflaterTask"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/NotificationViewFlipperBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/NotificationViewFlipperBinder.kt
new file mode 100644
index 0000000..133d3e7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/NotificationViewFlipperBinder.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.ui.viewbinder
+
+import android.widget.ViewFlipper
+import androidx.lifecycle.lifecycleScope
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.NotificationViewFlipperViewModel
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+
+/** Binds a [NotificationViewFlipper] to its [view model][NotificationViewFlipperViewModel]. */
+object NotificationViewFlipperBinder {
+ fun bindWhileAttached(
+ viewFlipper: ViewFlipper,
+ viewModel: NotificationViewFlipperViewModel,
+ ): DisposableHandle {
+ if (viewFlipper.isAutoStart) {
+ // If the ViewFlipper is not set to AutoStart, the pause binding is meaningless
+ return DisposableHandle {}
+ }
+ return viewFlipper.repeatWhenAttached {
+ lifecycleScope.launch { bind(viewFlipper, viewModel) }
+ }
+ }
+
+ suspend fun bind(
+ viewFlipper: ViewFlipper,
+ viewModel: NotificationViewFlipperViewModel,
+ ) = coroutineScope { launch { viewModel.isPaused.collect { viewFlipper.setPaused(it) } } }
+
+ private fun ViewFlipper.setPaused(paused: Boolean) {
+ if (paused) {
+ stopFlipping()
+ } else if (isAutoStart) {
+ startFlipping()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModel.kt
new file mode 100644
index 0000000..7694e58
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModel.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.notification.shared.NotificationViewFlipperPausing
+import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackInteractor
+import com.android.systemui.util.kotlin.FlowDumperImpl
+import javax.inject.Inject
+
+/** A model which represents whether ViewFlippers inside notifications should be paused. */
+@SysUISingleton
+class NotificationViewFlipperViewModel
+@Inject
+constructor(
+ dumpManager: DumpManager,
+ stackInteractor: NotificationStackInteractor,
+) : FlowDumperImpl(dumpManager) {
+ init {
+ /* check if */ NotificationViewFlipperPausing.isUnexpectedlyInLegacyMode()
+ }
+
+ val isPaused = stackInteractor.isShowingOnLockscreen.dumpWhileCollecting("isPaused")
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationViewFlipperPausing.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationViewFlipperPausing.kt
new file mode 100644
index 0000000..cea6a2b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationViewFlipperPausing.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.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 notification view flipper pausing flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object NotificationViewFlipperPausing {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_NOTIFICATION_VIEW_FLIPPER_PAUSING
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.notificationViewFlipperPausing()
+
+ /**
+ * 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)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index f523793..78fc147 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -42,8 +42,10 @@
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.AodToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
import com.android.systemui.keyguard.ui.viewmodel.DozingToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DozingToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.GoneToAodTransitionViewModel
@@ -99,7 +101,9 @@
private val alternateBouncerToGoneTransitionViewModel:
AlternateBouncerToGoneTransitionViewModel,
private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
+ private val aodToOccludedTransitionViewModel: AodToOccludedTransitionViewModel,
private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel,
+ private val dozingToOccludedTransitionViewModel: DozingToOccludedTransitionViewModel,
private val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
private val glanceableHubToLockscreenTransitionViewModel:
GlanceableHubToLockscreenTransitionViewModel,
@@ -155,8 +159,8 @@
.distinctUntilChanged()
.dumpWhileCollecting("isShadeLocked")
- private val shadeCollapseFadeInComplete = MutableStateFlow(false)
- .dumpValue("shadeCollapseFadeInComplete")
+ private val shadeCollapseFadeInComplete =
+ MutableStateFlow(false).dumpValue("shadeCollapseFadeInComplete")
val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
interactor.configurationBasedDimensions
@@ -187,9 +191,8 @@
statesForConstrainedNotifications.contains(it)
},
keyguardTransitionInteractor
- .transitionValue(LOCKSCREEN)
- .onStart { emit(0f) }
- .map { it > 0 }
+ .isInTransitionWhere { from, to -> from == LOCKSCREEN || to == LOCKSCREEN }
+ .onStart { emit(false) }
) { constrainedNotificationState, transitioningToOrFromLockscreen ->
constrainedNotificationState || transitioningToOrFromLockscreen
}
@@ -362,16 +365,16 @@
private val alphaWhenGoneAndShadeState: Flow<Float> =
combineTransform(
- keyguardTransitionInteractor.transitions
- .map { step -> step.to == GONE && step.transitionState == FINISHED }
- .distinctUntilChanged(),
- keyguardInteractor.statusBarState,
- ) { isGoneTransitionFinished, statusBarState ->
- if (isGoneTransitionFinished && statusBarState == SHADE) {
- emit(1f)
+ keyguardTransitionInteractor.transitions
+ .map { step -> step.to == GONE && step.transitionState == FINISHED }
+ .distinctUntilChanged(),
+ keyguardInteractor.statusBarState,
+ ) { isGoneTransitionFinished, statusBarState ->
+ if (isGoneTransitionFinished && statusBarState == SHADE) {
+ emit(1f)
+ }
}
- }
- .dumpWhileCollecting("alphaWhenGoneAndShadeState")
+ .dumpWhileCollecting("alphaWhenGoneAndShadeState")
fun expansionAlpha(viewState: ViewStateAccessor): Flow<Float> {
// All transition view models are mututally exclusive, and safe to merge
@@ -379,7 +382,9 @@
merge(
alternateBouncerToGoneTransitionViewModel.lockscreenAlpha,
aodToLockscreenTransitionViewModel.notificationAlpha,
+ aodToOccludedTransitionViewModel.lockscreenAlpha(viewState),
dozingToLockscreenTransitionViewModel.lockscreenAlpha,
+ dozingToOccludedTransitionViewModel.lockscreenAlpha(viewState),
dreamingToLockscreenTransitionViewModel.lockscreenAlpha,
goneToAodTransitionViewModel.notificationAlpha,
goneToDreamingTransitionViewModel.lockscreenAlpha,
@@ -433,30 +438,35 @@
* alpha sources.
*/
val glanceableHubAlpha: Flow<Float> =
- isOnGlanceableHubWithoutShade.flatMapLatest { isOnGlanceableHubWithoutShade ->
- combineTransform(
- lockscreenToGlanceableHubRunning,
- glanceableHubToLockscreenRunning,
- merge(
- lockscreenToGlanceableHubTransitionViewModel.notificationAlpha,
- glanceableHubToLockscreenTransitionViewModel.notificationAlpha,
- )
- ) { lockscreenToGlanceableHubRunning, glanceableHubToLockscreenRunning, alpha ->
- if (isOnGlanceableHubWithoutShade) {
- // Notifications should not be visible on the glanceable hub.
- // TODO(b/321075734): implement a way to actually set the notifications to gone
- // while on the hub instead of just adjusting alpha
- emit(0f)
- } else if (lockscreenToGlanceableHubRunning || glanceableHubToLockscreenRunning) {
- emit(alpha)
- } else {
- // Not on the hub and no transitions running, return full visibility so we don't
- // block the notifications from showing.
- emit(1f)
+ isOnGlanceableHubWithoutShade
+ .flatMapLatest { isOnGlanceableHubWithoutShade ->
+ combineTransform(
+ lockscreenToGlanceableHubRunning,
+ glanceableHubToLockscreenRunning,
+ merge(
+ lockscreenToGlanceableHubTransitionViewModel.notificationAlpha,
+ glanceableHubToLockscreenTransitionViewModel.notificationAlpha,
+ )
+ ) { lockscreenToGlanceableHubRunning, glanceableHubToLockscreenRunning, alpha ->
+ if (isOnGlanceableHubWithoutShade) {
+ // Notifications should not be visible on the glanceable hub.
+ // TODO(b/321075734): implement a way to actually set the notifications to
+ // gone
+ // while on the hub instead of just adjusting alpha
+ emit(0f)
+ } else if (
+ lockscreenToGlanceableHubRunning || glanceableHubToLockscreenRunning
+ ) {
+ emit(alpha)
+ } else {
+ // Not on the hub and no transitions running, return full visibility so we
+ // don't
+ // block the notifications from showing.
+ emit(1f)
+ }
}
}
- }
- .dumpWhileCollecting("glanceableHubAlpha")
+ .dumpWhileCollecting("glanceableHubAlpha")
/**
* Under certain scenarios, such as swiping up on the lockscreen, the container will need to be
@@ -464,20 +474,20 @@
*/
fun translationY(params: BurnInParameters): Flow<Float> {
return combine(
- aodBurnInViewModel.translationY(params).onStart { emit(0f) },
- isOnLockscreenWithoutShade,
- merge(
- keyguardInteractor.keyguardTranslationY,
- occludedToLockscreenTransitionViewModel.lockscreenTranslationY,
- )
- ) { burnInY, isOnLockscreenWithoutShade, translationY ->
- if (isOnLockscreenWithoutShade) {
- burnInY + translationY
- } else {
- 0f
+ aodBurnInViewModel.translationY(params).onStart { emit(0f) },
+ isOnLockscreenWithoutShade,
+ merge(
+ keyguardInteractor.keyguardTranslationY,
+ occludedToLockscreenTransitionViewModel.lockscreenTranslationY,
+ )
+ ) { burnInY, isOnLockscreenWithoutShade, translationY ->
+ if (isOnLockscreenWithoutShade) {
+ burnInY + translationY
+ } else {
+ 0f
+ }
}
- }
- .dumpWhileCollecting("translationY")
+ .dumpWhileCollecting("translationY")
}
/**
@@ -486,10 +496,10 @@
*/
val translationX: Flow<Float> =
merge(
- lockscreenToGlanceableHubTransitionViewModel.notificationTranslationX,
- glanceableHubToLockscreenTransitionViewModel.notificationTranslationX,
- )
- .dumpWhileCollecting("translationX")
+ lockscreenToGlanceableHubTransitionViewModel.notificationTranslationX,
+ glanceableHubToLockscreenTransitionViewModel.notificationTranslationX,
+ )
+ .dumpWhileCollecting("translationX")
/**
* When on keyguard, there is limited space to display notifications so calculate how many could
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 560d5ba..48d3157 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -315,8 +315,7 @@
mMetricsLogger.count("panel_open", 1);
} else if (!mQsController.getExpanded()
&& !mShadeViewController.isExpandingOrCollapsing()) {
- mQsController.flingQs(0 /* velocity */,
- ShadeViewController.FLING_EXPAND);
+ mShadeController.animateExpandQs();
mMetricsLogger.count("panel_open_qs", 1);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index ee84434..ba89d4a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -95,6 +95,7 @@
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.domain.interactor.StatusBarKeyguardViewManagerInteractor;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.unfold.FoldAodAnimationController;
@@ -351,8 +352,8 @@
private Lazy<WindowManagerLockscreenVisibilityInteractor> mWmLockscreenVisibilityInteractor;
private Lazy<KeyguardSurfaceBehindInteractor> mSurfaceBehindInteractor;
private Lazy<KeyguardDismissActionInteractor> mKeyguardDismissActionInteractor;
-
private final JavaAdapter mJavaAdapter;
+ private StatusBarKeyguardViewManagerInteractor mStatusBarKeyguardViewManagerInteractor;
@Inject
public StatusBarKeyguardViewManager(
@@ -386,7 +387,8 @@
SelectedUserInteractor selectedUserInteractor,
Lazy<KeyguardSurfaceBehindInteractor> surfaceBehindInteractor,
JavaAdapter javaAdapter,
- Lazy<SceneInteractor> sceneInteractorLazy
+ Lazy<SceneInteractor> sceneInteractorLazy,
+ StatusBarKeyguardViewManagerInteractor statusBarKeyguardViewManagerInteractor
) {
mContext = context;
mViewMediatorCallback = callback;
@@ -421,6 +423,7 @@
mSurfaceBehindInteractor = surfaceBehindInteractor;
mJavaAdapter = javaAdapter;
mSceneInteractorLazy = sceneInteractorLazy;
+ mStatusBarKeyguardViewManagerInteractor = statusBarKeyguardViewManagerInteractor;
}
KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@@ -503,6 +506,11 @@
lockscreenVis || animatingSurface
),
this::consumeShowStatusBarKeyguardView);
+
+ mJavaAdapter.alwaysCollectFlow(
+ mStatusBarKeyguardViewManagerInteractor.getKeyguardViewOcclusionState(),
+ (occlusionState) -> setOccluded(
+ occlusionState.getOccluded(), occlusionState.getAnimate()));
}
}
@@ -1453,6 +1461,10 @@
hideAlternateBouncer(false);
executeAfterKeyguardGoneAction();
}
+
+ if (KeyguardWmStateRefactor.isEnabled()) {
+ mKeyguardTransitionInteractor.startDismissKeyguardTransition();
+ }
}
/** Display security message to relevant KeyguardMessageArea. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index f12a09b..82d9fc7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -93,7 +93,7 @@
/**
* @deprecated Don't subclass SystemUIDialog. Please subclass {@link Delegate} and pass it to
- * {@link Factory#create(DialogDelegate)} to create a custom dialog.
+ * {@link Factory#create(Delegate)} to create a custom dialog.
*/
@Deprecated
public SystemUIDialog(Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt
index 0128eb7..80ccd64 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt
@@ -22,6 +22,24 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.onStart
+fun BatteryController.isDevicePluggedIn(): Flow<Boolean> {
+ return conflatedCallbackFlow {
+ val batteryCallback =
+ object : BatteryController.BatteryStateChangeCallback {
+ override fun onBatteryLevelChanged(
+ level: Int,
+ pluggedIn: Boolean,
+ charging: Boolean
+ ) {
+ trySend(pluggedIn)
+ }
+ }
+ addCallback(batteryCallback)
+ awaitClose { removeCallback(batteryCallback) }
+ }
+ .onStart { emit(isPluggedIn) }
+}
+
fun BatteryController.isBatteryPowerSaveEnabled(): Flow<Boolean> {
return conflatedCallbackFlow {
val batteryCallback =
@@ -35,3 +53,35 @@
}
.onStart { emit(isPowerSave) }
}
+
+fun BatteryController.getBatteryLevel(): Flow<Int> {
+ return conflatedCallbackFlow {
+ val batteryCallback =
+ object : BatteryController.BatteryStateChangeCallback {
+ override fun onBatteryLevelChanged(
+ level: Int,
+ pluggedIn: Boolean,
+ charging: Boolean
+ ) {
+ trySend(level)
+ }
+ }
+ addCallback(batteryCallback)
+ awaitClose { removeCallback(batteryCallback) }
+ }
+ .onStart { emit(0) }
+}
+
+fun BatteryController.isExtremePowerSaverEnabled(): Flow<Boolean> {
+ return conflatedCallbackFlow {
+ val batteryCallback =
+ object : BatteryController.BatteryStateChangeCallback {
+ override fun onExtremeBatterySaverChanged(isExtreme: Boolean) {
+ trySend(isExtreme)
+ }
+ }
+ addCallback(batteryCallback)
+ awaitClose { removeCallback(batteryCallback) }
+ }
+ .onStart { emit(isExtremeSaverOn) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java
index 0d6c0f5..10cf082 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java
@@ -25,8 +25,11 @@
import android.os.UserHandle;
import android.provider.Settings;
+import com.android.app.tracing.TraceUtils;
import com.android.systemui.settings.UserTracker;
+import kotlin.Unit;
+
/**
* Used to interact with per-user Settings.Secure and Settings.System settings (but not
* Settings.Global, since those do not vary per-user)
@@ -123,8 +126,16 @@
default void registerContentObserverForUser(
Uri uri, boolean notifyForDescendants, ContentObserver settingsObserver,
int userHandle) {
- getContentResolver().registerContentObserver(
- uri, notifyForDescendants, settingsObserver, getRealUserHandle(userHandle));
+ TraceUtils.trace(
+ () -> {
+ // The limit for trace tags length is 127 chars, which leaves us 90 for Uri.
+ return "USP#registerObserver#[" + uri.toString() + "]";
+ }, () -> {
+ getContentResolver().registerContentObserver(
+ uri, notifyForDescendants, settingsObserver,
+ getRealUserHandle(userHandle));
+ return Unit.INSTANCE;
+ });
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 4045630..23e296d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -2082,6 +2082,9 @@
} else {
row.anim.cancel();
row.anim.setIntValues(progress, newProgress);
+ // The animator can't keep up with the volume changes so haptics need to be
+ // triggered here. This happens when the volume keys are continuously pressed.
+ row.deliverOnProgressChangedHaptics(false, newProgress);
}
row.animTargetProgress = newProgress;
row.anim.setDuration(UPDATE_ANIMATION_DURATION);
@@ -2486,10 +2489,10 @@
if (getActiveRow().equals(mRow)
&& mRow.slider.getVisibility() == VISIBLE
&& mRow.mHapticPlugin != null) {
- mRow.mHapticPlugin.onProgressChanged(seekBar, progress, fromUser);
- if (!fromUser) {
- // Consider a change from program as the volume key being continuously pressed
- mRow.mHapticPlugin.onKeyDown();
+ if (fromUser || mRow.animTargetProgress == progress) {
+ // Deliver user-generated slider changes immediately, or when the animation
+ // completes
+ mRow.deliverOnProgressChangedHaptics(fromUser, progress);
}
}
if (D.BUG) Log.d(TAG, AudioSystem.streamToString(mRow.stream)
@@ -2571,11 +2574,11 @@
/* progressInterpolatorFactor= */ 1f,
/* progressBasedDragMinScale= */ 0f,
/* progressBasedDragMaxScale= */ 0.2f,
- /* additionalVelocityMaxBump= */ 0.15f,
+ /* additionalVelocityMaxBump= */ 0.25f,
/* deltaMillisForDragInterval= */ 0f,
- /* deltaProgressForDragThreshold= */ 0.015f,
- /* numberOfLowTicks= */ 5,
- /* maxVelocityToScale= */ 300f,
+ /* deltaProgressForDragThreshold= */ 0.05f,
+ /* numberOfLowTicks= */ 4,
+ /* maxVelocityToScale= */ 200,
/* velocityAxis= */ MotionEvent.AXIS_Y,
/* upperBookendScale= */ 1f,
/* lowerBookendScale= */ 0.05f,
@@ -2642,6 +2645,14 @@
void removeHaptics() {
slider.setOnTouchListener(null);
}
+
+ void deliverOnProgressChangedHaptics(boolean fromUser, int progress) {
+ mHapticPlugin.onProgressChanged(slider, progress, fromUser);
+ if (!fromUser) {
+ // Consider a change from program as the volume key being continuously pressed
+ mHapticPlugin.onKeyDown();
+ }
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
index 170b32c..2ab5998 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
@@ -16,14 +16,11 @@
package com.android.systemui.volume.panel.component.mediaoutput.domain.interactor
-import android.content.Intent
-import android.provider.Settings
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.media.dialog.MediaOutputDialogFactory
-import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSession
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import javax.inject.Inject
@@ -34,17 +31,8 @@
@Inject
constructor(
private val mediaOutputDialogFactory: MediaOutputDialogFactory,
- private val activityStarter: ActivityStarter,
) {
- fun onDeviceClick(expandable: Expandable) {
- activityStarter.startActivity(
- Intent(Settings.ACTION_BLUETOOTH_SETTINGS),
- true,
- expandable.activityTransitionController(),
- )
- }
-
fun onBarClick(session: MediaDeviceSession, expandable: Expandable) {
when (session) {
is MediaDeviceSession.Active -> {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/DeviceIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/DeviceIconViewModel.kt
index e518ed0..37bf661 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/DeviceIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/DeviceIconViewModel.kt
@@ -26,13 +26,13 @@
val iconColor: Color
val backgroundColor: Color
- class IsPlaying(
+ data class IsPlaying(
override val icon: Icon,
override val iconColor: Color,
override val backgroundColor: Color,
) : DeviceIconViewModel
- class IsNotPlaying(
+ data class IsNotPlaying(
override val icon: Icon,
override val iconColor: Color,
override val backgroundColor: Color,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
index 85d6c9e..37661b5 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
@@ -113,11 +113,6 @@
private fun MediaDeviceSession.isPlaying(): Boolean =
this is MediaDeviceSession.Active && playbackState?.isActive == true
- fun onDeviceClick(expandable: Expandable) {
- actionsInteractor.onDeviceClick(expandable)
- volumePanelViewModel.dismissPanel()
- }
-
fun onBarClick(expandable: Expandable) {
actionsInteractor.onBarClick(mediaDeviceSession.value, expandable)
volumePanelViewModel.dismissPanel()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
index 24a5e80..2e04007 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
@@ -15,14 +15,11 @@
package com.android.systemui;
import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.mock;
import android.os.Looper;
import androidx.test.filters.SmallTest;
-import com.android.systemui.statusbar.policy.FlashlightController;
-
import org.junit.Assert;
import org.junit.Test;
@@ -33,9 +30,9 @@
@Test
public void testClassDependency() {
- FlashlightController f = mock(FlashlightController.class);
- mDependency.injectTestDependency(FlashlightController.class, f);
- Assert.assertEquals(f, Dependency.get(FlashlightController.class));
+ FakeClass f = new FakeClass();
+ mDependency.injectTestDependency(FakeClass.class, f);
+ Assert.assertEquals(f, Dependency.get(FakeClass.class));
}
@Test
@@ -53,4 +50,8 @@
Dependency dependency = initializer.getSysUIComponent().createDependency();
dependency.start();
}
+
+ private static class FakeClass {
+
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
index 3da7261..095c945 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
@@ -22,10 +22,9 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
-import android.app.ActivityManager;
import android.content.Context;
import android.content.ContextWrapper;
import android.hardware.display.DisplayManager;
@@ -75,13 +74,13 @@
private AccessibilityManager mAccessibilityManager;
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private AccessibilityFloatingMenuController mController;
+ @Mock
private AccessibilityButtonTargetsObserver mTargetsObserver;
+ @Mock
private AccessibilityButtonModeObserver mModeObserver;
@Captor
private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardCallbackCaptor;
private KeyguardUpdateMonitorCallback mKeyguardCallback;
- private int mLastButtonMode;
- private String mLastButtonTargets;
@Mock
private SecureSettings mSecureSettings;
@@ -97,10 +96,14 @@
mWindowManager = mContext.getSystemService(WindowManager.class);
mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
- mLastButtonTargets = Settings.Secure.getStringForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, UserHandle.USER_CURRENT);
- mLastButtonMode = Settings.Secure.getIntForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_MODE, UserHandle.USER_CURRENT);
+
+ when(mTargetsObserver.getCurrentAccessibilityButtonTargets())
+ .thenReturn(Settings.Secure.getStringForUser(mContextWrapper.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, UserHandle.USER_CURRENT));
+
+ when(mModeObserver.getCurrentAccessibilityButtonMode())
+ .thenReturn(Settings.Secure.getIntForUser(mContextWrapper.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_MODE, UserHandle.USER_CURRENT));
}
@After
@@ -109,13 +112,6 @@
mController.onAccessibilityButtonTargetsChanged("");
mController = null;
}
-
- Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, mLastButtonTargets,
- UserHandle.USER_CURRENT);
- Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_MODE, mLastButtonMode,
- UserHandle.USER_CURRENT);
}
@Test
@@ -227,9 +223,8 @@
@Test
public void onAccessibilityButtonModeChanged_floatingModeAndHasButtonTargets_showWidget() {
- Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS,
- ActivityManager.getCurrentUser());
+ when(mTargetsObserver.getCurrentAccessibilityButtonTargets())
+ .thenReturn(TEST_A11Y_BTN_TARGETS);
mController = setUpController();
mController.onAccessibilityButtonModeChanged(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
@@ -239,8 +234,8 @@
@Test
public void onAccessibilityButtonModeChanged_floatingModeAndNoButtonTargets_destroyWidget() {
- Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, "", ActivityManager.getCurrentUser());
+ when(mTargetsObserver.getCurrentAccessibilityButtonTargets()).thenReturn("");
+
mController = setUpController();
mController.onAccessibilityButtonModeChanged(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
@@ -250,9 +245,8 @@
@Test
public void onAccessibilityButtonModeChanged_navBarModeAndHasButtonTargets_destroyWidget() {
- Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS,
- ActivityManager.getCurrentUser());
+ when(mTargetsObserver.getCurrentAccessibilityButtonTargets())
+ .thenReturn(TEST_A11Y_BTN_TARGETS);
mController = setUpController();
mController.onAccessibilityButtonModeChanged(ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
@@ -262,8 +256,7 @@
@Test
public void onAccessibilityButtonModeChanged_navBarModeAndNoButtonTargets_destroyWidget() {
- Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, "", ActivityManager.getCurrentUser());
+ when(mTargetsObserver.getCurrentAccessibilityButtonTargets()).thenReturn("");
mController = setUpController();
mController.onAccessibilityButtonModeChanged(ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
@@ -273,9 +266,8 @@
@Test
public void onAccessibilityButtonTargetsChanged_floatingModeAndHasButtonTargets_showWidget() {
- Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU,
- ActivityManager.getCurrentUser());
+ when(mModeObserver.getCurrentAccessibilityButtonMode())
+ .thenReturn(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
mController = setUpController();
mController.onAccessibilityButtonTargetsChanged(TEST_A11Y_BTN_TARGETS);
@@ -285,9 +277,8 @@
@Test
public void onAccessibilityButtonTargetsChanged_floatingModeAndNoButtonTargets_destroyWidget() {
- Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU,
- ActivityManager.getCurrentUser());
+ when(mModeObserver.getCurrentAccessibilityButtonMode())
+ .thenReturn(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
mController = setUpController();
mController.onAccessibilityButtonTargetsChanged("");
@@ -297,9 +288,8 @@
@Test
public void onAccessibilityButtonTargetsChanged_navBarModeAndHasButtonTargets_destroyWidget() {
- Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
- ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, ActivityManager.getCurrentUser());
+ when(mModeObserver.getCurrentAccessibilityButtonMode())
+ .thenReturn(ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
mController = setUpController();
mController.onAccessibilityButtonTargetsChanged(TEST_A11Y_BTN_TARGETS);
@@ -309,9 +299,8 @@
@Test
public void onAccessibilityButtonTargetsChanged_navBarModeAndNoButtonTargets_destroyWidget() {
- Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
- ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, ActivityManager.getCurrentUser());
+ when(mModeObserver.getCurrentAccessibilityButtonMode())
+ .thenReturn(ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
mController = setUpController();
mController.onAccessibilityButtonTargetsChanged("");
@@ -321,9 +310,8 @@
@Test
public void onTargetsChanged_isFloatingViewLayerControllerCreated() {
- Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU,
- UserHandle.USER_CURRENT);
+ when(mModeObserver.getCurrentAccessibilityButtonMode())
+ .thenReturn(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
mController = setUpController();
mController.onAccessibilityButtonTargetsChanged(TEST_A11Y_BTN_TARGETS);
@@ -335,8 +323,6 @@
final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
final FakeDisplayTracker displayTracker = new FakeDisplayTracker(mContext);
- mTargetsObserver = spy(Dependency.get(AccessibilityButtonTargetsObserver.class));
- mModeObserver = spy(Dependency.get(AccessibilityButtonModeObserver.class));
mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
final AccessibilityFloatingMenuController controller =
new AccessibilityFloatingMenuController(mContextWrapper, windowManager,
@@ -348,12 +334,11 @@
}
private void enableAccessibilityFloatingMenuConfig() {
- Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU,
- ActivityManager.getCurrentUser());
- Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS,
- ActivityManager.getCurrentUser());
+ when(mTargetsObserver.getCurrentAccessibilityButtonTargets())
+ .thenReturn(TEST_A11Y_BTN_TARGETS);
+
+ when(mModeObserver.getCurrentAccessibilityButtonMode())
+ .thenReturn(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
}
private void captureKeyguardUpdateMonitorCallback() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
index 915522d..1a6da76 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
@@ -53,9 +53,11 @@
import com.android.systemui.res.R
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
@@ -101,6 +103,8 @@
private lateinit var underTest: CustomizationProvider
private lateinit var testScope: TestScope
+ private val kosmos = testKosmos()
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -185,6 +189,7 @@
},
)
.keyguardInteractor,
+ shadeInteractor = kosmos.shadeInteractor,
lockPatternUtils = lockPatternUtils,
keyguardStateController = keyguardStateController,
userTracker = userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
index 489665c..51828c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
@@ -6,6 +6,7 @@
import android.graphics.Point
import android.graphics.Rect
import android.os.PowerManager
+import android.platform.test.annotations.DisableFlags
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.RemoteAnimationTarget
@@ -15,6 +16,7 @@
import android.view.ViewRootImpl
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardViewController
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController
@@ -130,6 +132,7 @@
* surface, or the user will see the wallpaper briefly as the app animates in.
*/
@Test
+ @DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun noSurfaceAnimation_ifWakeAndUnlocking() {
whenever(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
@@ -320,6 +323,7 @@
* If we are not wake and unlocking, we expect the unlock animation to play normally.
*/
@Test
+ @DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun surfaceAnimation_multipleTargets() {
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
arrayOf(remoteTarget1, remoteTarget2),
@@ -358,6 +362,7 @@
}
@Test
+ @DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun surfaceBehindAlphaOverriddenTo0_ifNotInteractive() {
whenever(powerManager.isInteractive).thenReturn(false)
@@ -389,6 +394,7 @@
}
@Test
+ @DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun surfaceBehindAlphaNotOverriddenTo0_ifInteractive() {
whenever(powerManager.isInteractive).thenReturn(true)
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 0957748..0bd4cbe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -1263,7 +1263,8 @@
mSystemPropertiesHelper,
() -> mock(WindowManagerLockscreenVisibilityManager.class),
mSelectedUserInteractor,
- mKeyguardInteractor);
+ mKeyguardInteractor,
+ mock(WindowManagerOcclusionManager.class));
mViewMediator.start();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
new file mode 100644
index 0000000..7bef01a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.os.PowerManager
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.testKosmos
+import junit.framework.Assert.assertEquals
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.spy
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FromAodTransitionInteractorTest : SysuiTestCase() {
+ private val kosmos =
+ testKosmos().apply {
+ this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+ }
+
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.fromAodTransitionInteractor
+
+ private val powerInteractor = kosmos.powerInteractor
+ private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+
+ @Before
+ fun setup() {
+ underTest.start()
+
+ // Transition to AOD and set the power interactor asleep.
+ powerInteractor.setAsleepForTest()
+ runBlocking {
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ testScope
+ )
+ kosmos.keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.NONE)
+ reset(transitionRepository)
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToLockscreen_onWakeup() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // Under default conditions, we should transition to LOCKSCREEN when waking up.
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToOccluded_onWakeup_whenOccludingActivityOnTop() =
+ testScope.runTest {
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true)
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // Waking with a SHOW_WHEN_LOCKED activity on top should transition to OCCLUDED.
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.AOD,
+ to = KeyguardState.OCCLUDED,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetected_fromAod_nonDismissableKeyguard() =
+ testScope.runTest {
+ powerInteractor.onCameraLaunchGestureDetected()
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // We should head back to GONE since we started there.
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.AOD, to = KeyguardState.OCCLUDED)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToGone_onWakeUp_ifPowerButtonGestureDetected_fromAod_dismissableKeyguard() =
+ testScope.runTest {
+ kosmos.fakeKeyguardRepository.setKeyguardDismissible(true)
+ powerInteractor.onCameraLaunchGestureDetected()
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // We should head back to GONE since we started there.
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.AOD, to = KeyguardState.GONE)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToGone_onWakeUp_ifPowerButtonGestureDetected_fromGone() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.GONE,
+ testScope,
+ )
+ runCurrent()
+
+ // Make sure we're GONE.
+ assertEquals(KeyguardState.GONE, kosmos.keyguardTransitionInteractor.getFinishedState())
+
+ // Get part way to AOD.
+ powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
+ runCurrent()
+
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ testScope = testScope,
+ throughTransitionState = TransitionState.RUNNING,
+ )
+
+ // Detect a power gesture and then wake up.
+ reset(transitionRepository)
+ powerInteractor.onCameraLaunchGestureDetected()
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // We should head back to GONE since we started there.
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.AOD, to = KeyguardState.GONE)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetectedAfterFinishedInAod_fromGone() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.GONE,
+ testScope,
+ )
+ runCurrent()
+
+ // Make sure we're GONE.
+ assertEquals(KeyguardState.GONE, kosmos.keyguardTransitionInteractor.getFinishedState())
+
+ // Get all the way to AOD
+ powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ testScope = testScope,
+ )
+
+ // Detect a power gesture and then wake up.
+ reset(transitionRepository)
+ powerInteractor.onCameraLaunchGestureDetected()
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // We should go to OCCLUDED - we came from GONE, but we finished in AOD, so this is no
+ // longer an insecure camera launch and it would be bad if we unlocked now.
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.AOD, to = KeyguardState.OCCLUDED)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetected_fromLockscreen() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ testScope,
+ )
+ runCurrent()
+
+ // Make sure we're in LOCKSCREEN.
+ assertEquals(
+ KeyguardState.LOCKSCREEN,
+ kosmos.keyguardTransitionInteractor.getFinishedState()
+ )
+
+ // Get part way to AOD.
+ powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
+ runCurrent()
+
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ testScope = testScope,
+ throughTransitionState = TransitionState.RUNNING,
+ )
+
+ // Detect a power gesture and then wake up.
+ reset(transitionRepository)
+ powerInteractor.onCameraLaunchGestureDetected()
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // We should head back to GONE since we started there.
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.AOD, to = KeyguardState.OCCLUDED)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testWakeAndUnlock_transitionsToGone_onlyAfterDismissCallPostWakeup() =
+ testScope.runTest {
+ kosmos.keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // Waking up from wake and unlock should not start any transitions, we'll wait for the
+ // dismiss call.
+ assertThat(transitionRepository).noTransitionsStarted()
+
+ underTest.dismissAod()
+ runCurrent()
+
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.AOD, to = KeyguardState.GONE)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
new file mode 100644
index 0000000..258dbf3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.os.PowerManager
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.testKosmos
+import junit.framework.Assert.assertEquals
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.spy
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FromDozingTransitionInteractorTest : SysuiTestCase() {
+ private val kosmos =
+ testKosmos().apply {
+ this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+ }
+
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.fromDozingTransitionInteractor
+
+ private val powerInteractor = kosmos.powerInteractor
+ private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+
+ @Before
+ fun setup() {
+ underTest.start()
+
+ // Transition to DOZING and set the power interactor asleep.
+ powerInteractor.setAsleepForTest()
+ runBlocking {
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DOZING,
+ testScope
+ )
+ kosmos.keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.NONE)
+ reset(transitionRepository)
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToLockscreen_onWakeup() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // Under default conditions, we should transition to LOCKSCREEN when waking up.
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.DOZING,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToGlanceableHub_onWakeup_ifIdleOnCommunal_noOccludingActivity() =
+ testScope.runTest {
+ kosmos.fakeCommunalRepository.setTransitionState(
+ flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal))
+ )
+ runCurrent()
+
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // Under default conditions, we should transition to LOCKSCREEN when waking up.
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.DOZING,
+ to = KeyguardState.GLANCEABLE_HUB,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToOccluded_onWakeup_whenOccludingActivityOnTop() =
+ testScope.runTest {
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true)
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // Waking with a SHOW_WHEN_LOCKED activity on top should transition to OCCLUDED.
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.DOZING,
+ to = KeyguardState.OCCLUDED,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToOccluded_onWakeup_whenOccludingActivityOnTop_evenIfIdleOnCommunal() =
+ testScope.runTest {
+ kosmos.fakeCommunalRepository.setTransitionState(
+ flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal))
+ )
+ runCurrent()
+
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true)
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // Waking with a SHOW_WHEN_LOCKED activity on top should transition to OCCLUDED.
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.DOZING,
+ to = KeyguardState.OCCLUDED,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetected_fromAod_nonDismissableKeyguard() =
+ testScope.runTest {
+ powerInteractor.onCameraLaunchGestureDetected()
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // We should head back to GONE since we started there.
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.OCCLUDED)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToGone_onWakeUp_ifPowerButtonGestureDetected_fromAod_dismissableKeyguard() =
+ testScope.runTest {
+ kosmos.fakeKeyguardRepository.setKeyguardDismissible(true)
+ powerInteractor.onCameraLaunchGestureDetected()
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // We should head back to GONE since we started there.
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.GONE)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToGone_onWakeUp_ifPowerButtonGestureDetected_fromGone() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.DOZING,
+ to = KeyguardState.GONE,
+ testScope,
+ )
+ runCurrent()
+
+ // Make sure we're GONE.
+ assertEquals(KeyguardState.GONE, kosmos.keyguardTransitionInteractor.getFinishedState())
+
+ // Get part way to AOD.
+ powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
+ runCurrent()
+
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.GONE,
+ to = KeyguardState.DOZING,
+ testScope = testScope,
+ throughTransitionState = TransitionState.RUNNING,
+ )
+
+ // Detect a power gesture and then wake up.
+ reset(transitionRepository)
+ powerInteractor.onCameraLaunchGestureDetected()
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // We should head back to GONE since we started there.
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.GONE)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetectedAfterFinishedInAod_fromGone() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.DOZING,
+ to = KeyguardState.GONE,
+ testScope,
+ )
+ runCurrent()
+
+ // Make sure we're GONE.
+ assertEquals(KeyguardState.GONE, kosmos.keyguardTransitionInteractor.getFinishedState())
+
+ // Get all the way to AOD
+ powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.GONE,
+ to = KeyguardState.DOZING,
+ testScope = testScope,
+ )
+
+ // Detect a power gesture and then wake up.
+ reset(transitionRepository)
+ powerInteractor.onCameraLaunchGestureDetected()
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // We should go to OCCLUDED - we came from GONE, but we finished in AOD, so this is no
+ // longer an insecure camera launch and it would be bad if we unlocked now.
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.OCCLUDED)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetected_fromLockscreen() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.DOZING,
+ to = KeyguardState.LOCKSCREEN,
+ testScope,
+ )
+ runCurrent()
+
+ // Make sure we're in LOCKSCREEN.
+ assertEquals(
+ KeyguardState.LOCKSCREEN,
+ kosmos.keyguardTransitionInteractor.getFinishedState()
+ )
+
+ // Get part way to AOD.
+ powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
+ runCurrent()
+
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DOZING,
+ testScope = testScope,
+ throughTransitionState = TransitionState.RUNNING,
+ )
+
+ // Detect a power gesture and then wake up.
+ reset(transitionRepository)
+ powerInteractor.onCameraLaunchGestureDetected()
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // We should head back to GONE since we started there.
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.OCCLUDED)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
new file mode 100644
index 0000000..f534ba5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.testKosmos
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.spy
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FromDreamingTransitionInteractorTest : SysuiTestCase() {
+ private val kosmos =
+ testKosmos().apply {
+ this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+ }
+
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.fromDreamingTransitionInteractor
+
+ private val powerInteractor = kosmos.powerInteractor
+ private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+
+ @Before
+ fun setup() {
+ underTest.start()
+
+ // Transition to DOZING and set the power interactor asleep.
+ powerInteractor.setAsleepForTest()
+ runBlocking {
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DREAMING,
+ testScope
+ )
+ kosmos.keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.NONE)
+ reset(transitionRepository)
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToOccluded_ifDreamEnds_occludingActivityOnTop() =
+ testScope.runTest {
+ kosmos.fakeKeyguardRepository.setDreaming(true)
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DREAMING,
+ testScope,
+ )
+ runCurrent()
+
+ reset(transitionRepository)
+
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = true)
+ kosmos.fakeKeyguardRepository.setDreaming(false)
+ runCurrent()
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.OCCLUDED,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testDoesNotTransitionToOccluded_occludingActivityOnTop_whileStillDreaming() =
+ testScope.runTest {
+ kosmos.fakeKeyguardRepository.setDreaming(true)
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DREAMING,
+ testScope,
+ )
+ runCurrent()
+
+ reset(transitionRepository)
+
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = true)
+ runCurrent()
+
+ assertThat(transitionRepository).noTransitionsStarted()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionsToLockscreen_whenOccludingActivityEnds() =
+ testScope.runTest {
+ kosmos.fakeKeyguardRepository.setDreaming(true)
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = true)
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DREAMING,
+ testScope,
+ )
+ runCurrent()
+
+ reset(transitionRepository)
+
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = false)
+ runCurrent()
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
index 6aebe36..c3e24d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
@@ -16,6 +16,8 @@
package com.android.systemui.keyguard.domain.interactor
+import android.app.ActivityManager
+import android.app.WindowConfiguration
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -26,6 +28,7 @@
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -42,6 +45,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
@@ -171,4 +175,49 @@
assertThatRepository(transitionRepository).noTransitionsStarted()
}
+
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionsToOccluded_whenShowWhenLockedActivityOnTop() =
+ testScope.runTest {
+ underTest.start()
+ runCurrent()
+
+ reset(transitionRepository)
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(
+ true,
+ ActivityManager.RunningTaskInfo().apply {
+ topActivityType = WindowConfiguration.ACTIVITY_TYPE_STANDARD
+ }
+ )
+ runCurrent()
+
+ assertThatRepository(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionsToDream_whenDreamActivityOnTop() =
+ testScope.runTest {
+ underTest.start()
+ runCurrent()
+
+ reset(transitionRepository)
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(
+ true,
+ ActivityManager.RunningTaskInfo().apply {
+ topActivityType = WindowConfiguration.ACTIVITY_TYPE_DREAM
+ }
+ )
+ runCurrent()
+
+ assertThatRepository(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DREAMING,
+ )
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
new file mode 100644
index 0000000..d3c4848
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.testKosmos
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.spy
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FromOccludedTransitionInteractorTest : SysuiTestCase() {
+ private val kosmos =
+ testKosmos().apply {
+ this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+ }
+
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.fromOccludedTransitionInteractor
+
+ private val powerInteractor = kosmos.powerInteractor
+ private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+
+ @Before
+ fun setup() {
+ underTest.start()
+
+ // Transition to OCCLUDED and set up PowerInteractor and the occlusion repository.
+ powerInteractor.setAwakeForTest()
+ runBlocking {
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = true)
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ testScope
+ )
+ reset(transitionRepository)
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testShowWhenLockedActivity_noLongerOnTop_transitionsToLockscreen() =
+ testScope.runTest {
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = false)
+ runCurrent()
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testShowWhenLockedActivity_noLongerOnTop_transitionsToGlanceableHub_ifIdleOnCommunal() =
+ testScope.runTest {
+ kosmos.fakeCommunalRepository.setTransitionState(
+ flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal))
+ )
+ runCurrent()
+
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = false)
+ runCurrent()
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.GLANCEABLE_HUB,
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
index f33a5c9..7ee8963 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
@@ -16,14 +16,23 @@
package com.android.systemui.keyguard.domain.interactor
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
+import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.android.systemui.user.domain.interactor.selectedUserInteractor
@@ -31,20 +40,27 @@
import junit.framework.Assert.assertTrue
import junit.framework.Assert.fail
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.spy
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class FromPrimaryBouncerTransitionInteractorTest : SysuiTestCase() {
- val kosmos = testKosmos()
+ val kosmos =
+ testKosmos().apply {
+ this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+ }
val underTest = kosmos.fromPrimaryBouncerTransitionInteractor
val testScope = kosmos.testScope
val selectedUserInteractor = kosmos.selectedUserInteractor
val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+ val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
@Test
fun testSurfaceBehindVisibility() =
@@ -193,4 +209,85 @@
fail("surfaceBehindModel was unexpectedly null.")
}
}
+
+ @Test
+ @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testReturnToLockscreen_whenBouncerHides() =
+ testScope.runTest {
+ underTest.start()
+ bouncerRepository.setPrimaryShow(true)
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ testScope
+ )
+
+ reset(transitionRepository)
+
+ bouncerRepository.setPrimaryShow(false)
+ runCurrent()
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.LOCKSCREEN
+ )
+ }
+
+ @Test
+ @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testReturnToGlanceableHub_whenBouncerHides_ifIdleOnCommunal() =
+ testScope.runTest {
+ underTest.start()
+ kosmos.fakeCommunalRepository.setTransitionState(
+ flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal))
+ )
+ bouncerRepository.setPrimaryShow(true)
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ testScope
+ )
+
+ reset(transitionRepository)
+
+ bouncerRepository.setPrimaryShow(false)
+ runCurrent()
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GLANCEABLE_HUB
+ )
+ }
+
+ @Test
+ @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToOccluded_bouncerHide_occludingActivityOnTop() =
+ testScope.runTest {
+ underTest.start()
+ bouncerRepository.setPrimaryShow(true)
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ testScope
+ )
+
+ reset(transitionRepository)
+
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true)
+ runCurrent()
+
+ // Shouldn't transition to OCCLUDED until the bouncer hides.
+ assertThat(transitionRepository).noTransitionsStarted()
+
+ bouncerRepository.setPrimaryShow(false)
+ runCurrent()
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.OCCLUDED
+ )
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt
new file mode 100644
index 0000000..8a77ed2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
+import kotlin.test.Test
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardOcclusionInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.keyguardOcclusionInteractor
+ private val powerInteractor = kosmos.powerInteractor
+ private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+
+ @Test
+ fun testTransitionFromPowerGesture_whileGoingToSleep_isTrue() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ testScope = testScope,
+ throughTransitionState = TransitionState.RUNNING
+ )
+
+ powerInteractor.onCameraLaunchGestureDetected()
+ runCurrent()
+
+ assertTrue(underTest.shouldTransitionFromPowerButtonGesture())
+ }
+
+ @Test
+ fun testTransitionFromPowerGesture_whileAsleep_isTrue() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ testScope = testScope,
+ )
+
+ powerInteractor.onCameraLaunchGestureDetected()
+ runCurrent()
+
+ assertTrue(underTest.shouldTransitionFromPowerButtonGesture())
+ }
+
+ @Test
+ fun testTransitionFromPowerGesture_whileWaking_isFalse() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ testScope = testScope,
+ )
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ testScope = testScope,
+ throughTransitionState = TransitionState.RUNNING
+ )
+
+ powerInteractor.onCameraLaunchGestureDetected()
+ runCurrent()
+
+ assertFalse(underTest.shouldTransitionFromPowerButtonGesture())
+ }
+
+ @Test
+ fun testTransitionFromPowerGesture_whileAwake_isFalse() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ testScope = testScope,
+ )
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ testScope = testScope,
+ )
+
+ powerInteractor.onCameraLaunchGestureDetected()
+ runCurrent()
+
+ assertFalse(underTest.shouldTransitionFromPowerButtonGesture())
+ }
+
+ @Test
+ fun testShowWhenLockedActivityLaunchedFromPowerGesture_notTrueSecondTime() =
+ testScope.runTest {
+ val values by collectValues(underTest.showWhenLockedActivityLaunchedFromPowerGesture)
+ powerInteractor.setAsleepForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ testScope = testScope,
+ )
+
+ powerInteractor.onCameraLaunchGestureDetected()
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true)
+ runCurrent()
+
+ assertThat(values)
+ .containsExactly(
+ false,
+ true,
+ )
+
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(false)
+ runCurrent()
+
+ assertThat(values)
+ .containsExactly(
+ false,
+ true,
+ false,
+ )
+
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true)
+ runCurrent()
+
+ assertThat(values)
+ .containsExactly(
+ false,
+ true,
+ // Power button gesture was not triggered a second time, so this should remain
+ // false.
+ false,
+ )
+ }
+
+ @Test
+ fun testShowWhenLockedActivityLaunchedFromPowerGesture_falseIfReturningToGone() =
+ testScope.runTest {
+ val values by collectValues(underTest.showWhenLockedActivityLaunchedFromPowerGesture)
+ powerInteractor.setAwakeForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ testScope = testScope,
+ )
+
+ powerInteractor.setAsleepForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ testScope = testScope,
+ throughTransitionState = TransitionState.RUNNING
+ )
+
+ powerInteractor.onCameraLaunchGestureDetected()
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true)
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.GONE,
+ testScope = testScope,
+ )
+
+ assertThat(values)
+ .containsExactly(
+ false,
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index d2a8444..45b2a42 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -46,7 +46,9 @@
import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
@@ -242,6 +244,8 @@
private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
private lateinit var userTracker: UserTracker
+ private val kosmos = testKosmos()
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -311,6 +315,7 @@
featureFlags = featureFlags,
)
.keyguardInteractor,
+ shadeInteractor = kosmos.shadeInteractor,
lockPatternUtils = lockPatternUtils,
keyguardStateController = keyguardStateController,
userTracker = userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index c65a9ef..95606ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -21,8 +21,8 @@
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
+import com.android.systemui.Flags
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
-import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
import com.android.systemui.communal.domain.interactor.communalInteractor
@@ -40,7 +40,6 @@
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
-import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
@@ -48,7 +47,6 @@
import com.android.systemui.shade.data.repository.fakeShadeRepository
import com.android.systemui.statusbar.commandQueue
import com.android.systemui.testKosmos
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -92,30 +90,26 @@
private var commandQueue = kosmos.fakeCommandQueue
private val shadeRepository = kosmos.fakeShadeRepository
private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
- private val transitionInteractor = kosmos.keyguardTransitionInteractor
private lateinit var featureFlags: FakeFeatureFlags
// Used to verify transition requests for test output
@Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
- @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
private val fromLockscreenTransitionInteractor = kosmos.fromLockscreenTransitionInteractor
- private lateinit var fromDreamingTransitionInteractor: FromDreamingTransitionInteractor
- private lateinit var fromDozingTransitionInteractor: FromDozingTransitionInteractor
- private lateinit var fromOccludedTransitionInteractor: FromOccludedTransitionInteractor
- private lateinit var fromGoneTransitionInteractor: FromGoneTransitionInteractor
- private lateinit var fromAodTransitionInteractor: FromAodTransitionInteractor
- private lateinit var fromAlternateBouncerTransitionInteractor:
- FromAlternateBouncerTransitionInteractor
+ private val fromDreamingTransitionInteractor = kosmos.fromDreamingTransitionInteractor
+ private val fromDozingTransitionInteractor = kosmos.fromDozingTransitionInteractor
+ private val fromOccludedTransitionInteractor = kosmos.fromOccludedTransitionInteractor
+ private val fromGoneTransitionInteractor = kosmos.fromGoneTransitionInteractor
+ private val fromAodTransitionInteractor = kosmos.fromAodTransitionInteractor
+ private val fromAlternateBouncerTransitionInteractor =
+ kosmos.fromAlternateBouncerTransitionInteractor
private val fromPrimaryBouncerTransitionInteractor =
kosmos.fromPrimaryBouncerTransitionInteractor
- private lateinit var fromDreamingLockscreenHostedTransitionInteractor:
- FromDreamingLockscreenHostedTransitionInteractor
- private lateinit var fromGlanceableHubTransitionInteractor:
- FromGlanceableHubTransitionInteractor
+ private val fromDreamingLockscreenHostedTransitionInteractor =
+ kosmos.fromDreamingLockscreenHostedTransitionInteractor
+ private val fromGlanceableHubTransitionInteractor = kosmos.fromGlanceableHubTransitionInteractor
private val powerInteractor = kosmos.powerInteractor
- private val keyguardInteractor = kosmos.keyguardInteractor
private val communalInteractor = kosmos.communalInteractor
@Before
@@ -125,122 +119,21 @@
whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN)
mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
+ mSetFlagsRule.disableFlags(
+ Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
+ )
featureFlags = FakeFeatureFlags()
- val glanceableHubTransitions =
- GlanceableHubTransitions(
- bgDispatcher = kosmos.testDispatcher,
- transitionInteractor = transitionInteractor,
- transitionRepository = transitionRepository,
- communalInteractor = communalInteractor
- )
-
fromLockscreenTransitionInteractor.start()
fromPrimaryBouncerTransitionInteractor.start()
-
- fromDreamingTransitionInteractor =
- FromDreamingTransitionInteractor(
- scope = testScope,
- bgDispatcher = kosmos.testDispatcher,
- mainDispatcher = kosmos.testDispatcher,
- keyguardInteractor = keyguardInteractor,
- transitionRepository = transitionRepository,
- transitionInteractor = transitionInteractor,
- glanceableHubTransitions = glanceableHubTransitions,
- )
- .apply { start() }
-
- fromDreamingLockscreenHostedTransitionInteractor =
- FromDreamingLockscreenHostedTransitionInteractor(
- scope = testScope,
- bgDispatcher = kosmos.testDispatcher,
- mainDispatcher = kosmos.testDispatcher,
- keyguardInteractor = keyguardInteractor,
- transitionRepository = transitionRepository,
- transitionInteractor = transitionInteractor,
- )
- .apply { start() }
-
- fromAodTransitionInteractor =
- FromAodTransitionInteractor(
- scope = testScope,
- bgDispatcher = kosmos.testDispatcher,
- mainDispatcher = kosmos.testDispatcher,
- keyguardInteractor = keyguardInteractor,
- transitionRepository = transitionRepository,
- transitionInteractor = transitionInteractor,
- powerInteractor = powerInteractor,
- )
- .apply { start() }
-
- fromGoneTransitionInteractor =
- FromGoneTransitionInteractor(
- scope = testScope,
- bgDispatcher = kosmos.testDispatcher,
- mainDispatcher = kosmos.testDispatcher,
- keyguardInteractor = keyguardInteractor,
- transitionRepository = transitionRepository,
- transitionInteractor = transitionInteractor,
- powerInteractor = powerInteractor,
- communalInteractor = communalInteractor,
- )
- .apply { start() }
-
- fromDozingTransitionInteractor =
- FromDozingTransitionInteractor(
- scope = testScope,
- bgDispatcher = kosmos.testDispatcher,
- mainDispatcher = kosmos.testDispatcher,
- keyguardInteractor = keyguardInteractor,
- transitionRepository = transitionRepository,
- transitionInteractor = transitionInteractor,
- powerInteractor = powerInteractor,
- communalInteractor = communalInteractor,
- )
- .apply { start() }
-
- fromOccludedTransitionInteractor =
- FromOccludedTransitionInteractor(
- scope = testScope,
- bgDispatcher = kosmos.testDispatcher,
- mainDispatcher = kosmos.testDispatcher,
- keyguardInteractor = keyguardInteractor,
- transitionRepository = transitionRepository,
- transitionInteractor = transitionInteractor,
- powerInteractor = powerInteractor,
- communalInteractor = communalInteractor,
- )
- .apply { start() }
-
- fromAlternateBouncerTransitionInteractor =
- FromAlternateBouncerTransitionInteractor(
- scope = testScope,
- bgDispatcher = kosmos.testDispatcher,
- mainDispatcher = kosmos.testDispatcher,
- keyguardInteractor = keyguardInteractor,
- transitionRepository = transitionRepository,
- transitionInteractor = transitionInteractor,
- communalInteractor = communalInteractor,
- powerInteractor = powerInteractor,
- )
- .apply { start() }
-
- fromGlanceableHubTransitionInteractor =
- FromGlanceableHubTransitionInteractor(
- scope = testScope,
- bgDispatcher = kosmos.testDispatcher,
- mainDispatcher = kosmos.testDispatcher,
- glanceableHubTransitions = glanceableHubTransitions,
- keyguardInteractor = keyguardInteractor,
- transitionRepository = transitionRepository,
- transitionInteractor = transitionInteractor,
- powerInteractor = powerInteractor,
- )
- .apply { start() }
-
- mSetFlagsRule.disableFlags(
- FLAG_KEYGUARD_WM_STATE_REFACTOR,
- )
+ fromDreamingTransitionInteractor.start()
+ fromDreamingLockscreenHostedTransitionInteractor.start()
+ fromAodTransitionInteractor.start()
+ fromGoneTransitionInteractor.start()
+ fromDozingTransitionInteractor.start()
+ fromOccludedTransitionInteractor.start()
+ fromAlternateBouncerTransitionInteractor.start()
+ fromGlanceableHubTransitionInteractor.start()
}
@Test
@@ -257,7 +150,9 @@
.startedTransition(
to = KeyguardState.PRIMARY_BOUNCER,
from = KeyguardState.LOCKSCREEN,
- ownerName = "FromLockscreenTransitionInteractor",
+ ownerName =
+ "FromLockscreenTransitionInteractor" +
+ "(#listenForLockscreenToPrimaryBouncer)",
animatorAssertion = { it.isNotNull() }
)
@@ -282,7 +177,7 @@
.startedTransition(
to = KeyguardState.DOZING,
from = KeyguardState.OCCLUDED,
- ownerName = "FromOccludedTransitionInteractor",
+ ownerName = "FromOccludedTransitionInteractor(Sleep transition triggered)",
animatorAssertion = { it.isNotNull() }
)
@@ -307,7 +202,7 @@
.startedTransition(
to = KeyguardState.AOD,
from = KeyguardState.OCCLUDED,
- ownerName = "FromOccludedTransitionInteractor",
+ ownerName = "FromOccludedTransitionInteractor(Sleep transition triggered)",
animatorAssertion = { it.isNotNull() }
)
@@ -389,7 +284,7 @@
.startedTransition(
to = KeyguardState.DOZING,
from = KeyguardState.LOCKSCREEN,
- ownerName = "FromLockscreenTransitionInteractor",
+ ownerName = "FromLockscreenTransitionInteractor(Sleep transition triggered)",
animatorAssertion = { it.isNotNull() }
)
@@ -414,7 +309,7 @@
.startedTransition(
to = KeyguardState.AOD,
from = KeyguardState.LOCKSCREEN,
- ownerName = "FromLockscreenTransitionInteractor",
+ ownerName = "FromLockscreenTransitionInteractor(Sleep transition triggered)",
animatorAssertion = { it.isNotNull() }
)
@@ -703,6 +598,32 @@
coroutineContext.cancelChildren()
}
+ /** This handles security method NONE and screen off with lock timeout */
+ @Test
+ fun dreamingToGoneWithKeyguardNotShowing() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to DREAMING
+ keyguardRepository.setDreamingWithOverlay(true)
+ runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DREAMING)
+ runCurrent()
+
+ // WHEN the device wakes up without a keyguard
+ keyguardRepository.setKeyguardShowing(false)
+ keyguardRepository.setKeyguardDismissible(true)
+ keyguardRepository.setDreamingWithOverlay(false)
+ advanceTimeBy(60L)
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.GONE,
+ from = KeyguardState.DREAMING,
+ ownerName = "FromDreamingTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
+
+ coroutineContext.cancelChildren()
+ }
+
@Test
fun dozingToGlanceableHub() =
testScope.runTest {
@@ -752,7 +673,7 @@
.startedTransition(
to = KeyguardState.DOZING,
from = KeyguardState.GONE,
- ownerName = "FromGoneTransitionInteractor",
+ ownerName = "FromGoneTransitionInteractor(Sleep transition triggered)",
animatorAssertion = { it.isNotNull() }
)
@@ -777,7 +698,7 @@
.startedTransition(
to = KeyguardState.AOD,
from = KeyguardState.GONE,
- ownerName = "FromGoneTransitionInteractor",
+ ownerName = "FromGoneTransitionInteractor(Sleep transition triggered)",
animatorAssertion = { it.isNotNull() }
)
@@ -1044,12 +965,14 @@
@Test
fun primaryBouncerToAod() =
testScope.runTest {
+ // GIVEN aod available
+ keyguardRepository.setAodAvailable(true)
+ runCurrent()
+
// GIVEN a prior transition has run to PRIMARY_BOUNCER
bouncerRepository.setPrimaryShow(true)
runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.PRIMARY_BOUNCER)
- // GIVEN aod available and starting to sleep
- keyguardRepository.setAodAvailable(true)
powerInteractor.setAsleepForTest()
// WHEN the primaryBouncer stops showing
@@ -1059,7 +982,8 @@
// THEN a transition to AOD should occur
assertThat(transitionRepository)
.startedTransition(
- ownerName = "FromPrimaryBouncerTransitionInteractor",
+ ownerName =
+ "FromPrimaryBouncerTransitionInteractor" + "(Sleep transition triggered)",
from = KeyguardState.PRIMARY_BOUNCER,
to = KeyguardState.AOD,
animatorAssertion = { it.isNotNull() },
@@ -1086,7 +1010,8 @@
// THEN a transition to DOZING should occur
assertThat(transitionRepository)
.startedTransition(
- ownerName = "FromPrimaryBouncerTransitionInteractor",
+ ownerName =
+ "FromPrimaryBouncerTransitionInteractor" + "(Sleep transition triggered)",
from = KeyguardState.PRIMARY_BOUNCER,
to = KeyguardState.DOZING,
animatorAssertion = { it.isNotNull() },
@@ -1616,7 +1541,9 @@
// THEN a transition from LOCKSCREEN => PRIMARY_BOUNCER should occur
assertThat(transitionRepository)
.startedTransition(
- ownerName = "FromLockscreenTransitionInteractor",
+ ownerName =
+ "FromLockscreenTransitionInteractor" +
+ "(#listenForLockscreenToPrimaryBouncerDragging)",
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.PRIMARY_BOUNCER,
animatorAssertion = { it.isNull() }, // dragging should be manually animated
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 2ec2fe3..7290863 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -54,6 +54,7 @@
import com.android.systemui.res.R
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -218,6 +219,7 @@
quickAffordanceInteractor =
KeyguardQuickAffordanceInteractor(
keyguardInteractor = keyguardInteractor,
+ shadeInteractor = kosmos.shadeInteractor,
lockPatternUtils = lockPatternUtils,
keyguardStateController = keyguardStateController,
userTracker = userTracker,
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 1f14afa..bcec6109 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
@@ -280,6 +280,7 @@
quickAffordanceInteractor =
KeyguardQuickAffordanceInteractor(
keyguardInteractor = keyguardInteractor,
+ shadeInteractor = shadeInteractor,
lockPatternUtils = lockPatternUtils,
keyguardStateController = keyguardStateController,
userTracker = userTracker,
@@ -643,7 +644,7 @@
val testConfig =
TestConfig(
- isVisible = true,
+ isVisible = false,
isClickable = false,
icon = mock(),
canShowWhileLocked = false,
@@ -673,7 +674,7 @@
val testConfig =
TestConfig(
- isVisible = true,
+ isVisible = false,
isClickable = false,
icon = mock(),
canShowWhileLocked = false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt
index dbfab64..bda0e1e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt
@@ -21,32 +21,25 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createToken
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createToken
+import com.android.systemui.mediaprojection.taskswitcher.activityTaskManagerTasksRepository
+import com.android.systemui.mediaprojection.taskswitcher.fakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherKosmos
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
-@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidTestingRunner::class)
@SmallTest
class ActivityTaskManagerTasksRepositoryTest : SysuiTestCase() {
- private val fakeActivityTaskManager = FakeActivityTaskManager()
-
- private val dispatcher = UnconfinedTestDispatcher()
- private val testScope = TestScope(dispatcher)
-
- private val repo =
- ActivityTaskManagerTasksRepository(
- activityTaskManager = fakeActivityTaskManager.activityTaskManager,
- applicationScope = testScope.backgroundScope,
- backgroundDispatcher = dispatcher
- )
+ private val kosmos = taskSwitcherKosmos()
+ private val fakeActivityTaskManager = kosmos.fakeActivityTaskManager
+ private val testScope = kosmos.testScope
+ private val repo = kosmos.activityTaskManagerTasksRepository
@Test
fun launchRecentTask_taskIsMovedToForeground() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
index fdd434a..6043ede 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
@@ -17,50 +17,35 @@
package com.android.systemui.mediaprojection.taskswitcher.data.repository
import android.os.Binder
-import android.os.Handler
import android.testing.AndroidTestingRunner
import android.view.ContentRecordingSession
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createToken
import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createToken
+import com.android.systemui.mediaprojection.taskswitcher.fakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.fakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.mediaProjectionManagerRepository
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherKosmos
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
-@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidTestingRunner::class)
@SmallTest
class MediaProjectionManagerRepositoryTest : SysuiTestCase() {
- private val dispatcher = UnconfinedTestDispatcher()
- private val testScope = TestScope(dispatcher)
+ private val kosmos = taskSwitcherKosmos()
+ private val testScope = kosmos.testScope
- private val fakeMediaProjectionManager = FakeMediaProjectionManager()
- private val fakeActivityTaskManager = FakeActivityTaskManager()
+ private val fakeMediaProjectionManager = kosmos.fakeMediaProjectionManager
+ private val fakeActivityTaskManager = kosmos.fakeActivityTaskManager
- private val tasksRepo =
- ActivityTaskManagerTasksRepository(
- activityTaskManager = fakeActivityTaskManager.activityTaskManager,
- applicationScope = testScope.backgroundScope,
- backgroundDispatcher = dispatcher
- )
-
- private val repo =
- MediaProjectionManagerRepository(
- mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
- handler = Handler.getMain(),
- applicationScope = testScope.backgroundScope,
- tasksRepository = tasksRepo,
- backgroundDispatcher = dispatcher,
- mediaProjectionServiceHelper = fakeMediaProjectionManager.helper
- )
+ private val repo = kosmos.mediaProjectionManagerRepository
@Test
fun switchProjectedTask_stateIsUpdatedWithNewTask() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
index dfb688b..33e65f26 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
@@ -17,55 +17,33 @@
package com.android.systemui.mediaprojection.taskswitcher.domain.interactor
import android.content.Intent
-import android.os.Handler
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager.Companion.createSingleTaskSession
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.FakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.FakeMediaProjectionManager.Companion.createSingleTaskSession
import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState
+import com.android.systemui.mediaprojection.taskswitcher.fakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.fakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherInteractor
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherKosmos
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
-@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidTestingRunner::class)
@SmallTest
class TaskSwitchInteractorTest : SysuiTestCase() {
- private val dispatcher = UnconfinedTestDispatcher()
- private val testScope = TestScope(dispatcher)
-
- private val fakeActivityTaskManager = FakeActivityTaskManager()
- private val fakeMediaProjectionManager = FakeMediaProjectionManager()
-
- private val tasksRepo =
- ActivityTaskManagerTasksRepository(
- activityTaskManager = fakeActivityTaskManager.activityTaskManager,
- applicationScope = testScope.backgroundScope,
- backgroundDispatcher = dispatcher
- )
-
- private val mediaRepo =
- MediaProjectionManagerRepository(
- mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
- handler = Handler.getMain(),
- applicationScope = testScope.backgroundScope,
- tasksRepository = tasksRepo,
- backgroundDispatcher = dispatcher,
- mediaProjectionServiceHelper = fakeMediaProjectionManager.helper,
- )
-
- private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
+ private val kosmos = taskSwitcherKosmos()
+ private val testScope = kosmos.testScope
+ private val fakeActivityTaskManager = kosmos.fakeActivityTaskManager
+ private val fakeMediaProjectionManager = kosmos.fakeMediaProjectionManager
+ private val interactor = kosmos.taskSwitcherInteractor
@Test
fun taskSwitchChanges_notProjecting_foregroundTaskChange_emitsNotProjectingTask() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt
index c4e9393..9382c58 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt
@@ -18,26 +18,22 @@
import android.app.Notification
import android.app.NotificationManager
-import android.os.Handler
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
-import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
-import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.FakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.fakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.fakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherKosmos
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherViewModel
import com.android.systemui.res.R
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertEquals
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -46,39 +42,16 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidTestingRunner::class)
@SmallTest
class TaskSwitcherNotificationCoordinatorTest : SysuiTestCase() {
private val notificationManager = mock<NotificationManager>()
-
- private val dispatcher = UnconfinedTestDispatcher()
- private val testScope = TestScope(dispatcher)
-
- private val fakeActivityTaskManager = FakeActivityTaskManager()
- private val fakeMediaProjectionManager = FakeMediaProjectionManager()
-
- private val tasksRepo =
- ActivityTaskManagerTasksRepository(
- activityTaskManager = fakeActivityTaskManager.activityTaskManager,
- applicationScope = testScope.backgroundScope,
- backgroundDispatcher = dispatcher
- )
-
- private val mediaRepo =
- MediaProjectionManagerRepository(
- mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
- handler = Handler.getMain(),
- applicationScope = testScope.backgroundScope,
- tasksRepository = tasksRepo,
- backgroundDispatcher = dispatcher,
- mediaProjectionServiceHelper = fakeMediaProjectionManager.helper,
- )
-
- private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
- private val viewModel =
- TaskSwitcherNotificationViewModel(interactor, backgroundDispatcher = dispatcher)
+ private val kosmos = taskSwitcherKosmos()
+ private val testScope = kosmos.testScope
+ private val fakeActivityTaskManager = kosmos.fakeActivityTaskManager
+ private val fakeMediaProjectionManager = kosmos.fakeMediaProjectionManager
+ private val viewModel = kosmos.taskSwitcherViewModel
private lateinit var coordinator: TaskSwitcherNotificationCoordinator
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
index 5dadf21..a468953 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
@@ -17,60 +17,35 @@
package com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel
import android.content.Intent
-import android.os.Handler
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager.Companion.createDisplaySession
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager.Companion.createSingleTaskSession
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
-import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.FakeMediaProjectionManager.Companion.createDisplaySession
+import com.android.systemui.mediaprojection.taskswitcher.FakeMediaProjectionManager.Companion.createSingleTaskSession
+import com.android.systemui.mediaprojection.taskswitcher.fakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.fakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherKosmos
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherViewModel
import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState
+import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel.Companion.NOTIFICATION_MAX_SHOW_DURATION
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
-@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidTestingRunner::class)
@SmallTest
class TaskSwitcherNotificationViewModelTest : SysuiTestCase() {
- private val dispatcher = UnconfinedTestDispatcher()
- private val testScope = TestScope(dispatcher)
-
- private val fakeActivityTaskManager = FakeActivityTaskManager()
- private val fakeMediaProjectionManager = FakeMediaProjectionManager()
-
- private val tasksRepo =
- ActivityTaskManagerTasksRepository(
- activityTaskManager = fakeActivityTaskManager.activityTaskManager,
- applicationScope = testScope.backgroundScope,
- backgroundDispatcher = dispatcher
- )
-
- private val mediaRepo =
- MediaProjectionManagerRepository(
- mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
- handler = Handler.getMain(),
- applicationScope = testScope.backgroundScope,
- tasksRepository = tasksRepo,
- backgroundDispatcher = dispatcher,
- mediaProjectionServiceHelper = fakeMediaProjectionManager.helper,
- )
-
- private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
-
- private val viewModel =
- TaskSwitcherNotificationViewModel(interactor, backgroundDispatcher = dispatcher)
+ private val kosmos = taskSwitcherKosmos()
+ private val testScope = kosmos.testScope
+ private val fakeActivityTaskManager = kosmos.fakeActivityTaskManager
+ private val fakeMediaProjectionManager = kosmos.fakeMediaProjectionManager
+ private val viewModel = kosmos.taskSwitcherViewModel
@Test
fun uiState_notProjecting_emitsNotShowing() =
@@ -138,6 +113,41 @@
}
@Test
+ fun uiState_taskChanged_beforeDelayLimit_stillEmitsShowing() =
+ testScope.runTest {
+ val projectedTask = createTask(taskId = 1)
+ val foregroundTask = createTask(taskId = 2)
+ val uiState by collectLastValue(viewModel.uiState)
+
+ fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session = createSingleTaskSession(projectedTask.token.asBinder())
+ )
+ fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+ testScheduler.advanceTimeBy(NOTIFICATION_MAX_SHOW_DURATION - 1.milliseconds)
+ assertThat(uiState)
+ .isEqualTo(TaskSwitcherNotificationUiState.Showing(projectedTask, foregroundTask))
+ }
+
+ @Test
+ fun uiState_taskChanged_afterDelayLimit_emitsNotShowing() =
+ testScope.runTest {
+ val projectedTask = createTask(taskId = 1)
+ val foregroundTask = createTask(taskId = 2)
+ val uiState by collectLastValue(viewModel.uiState)
+
+ fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session = createSingleTaskSession(projectedTask.token.asBinder())
+ )
+ fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+ testScheduler.advanceTimeBy(NOTIFICATION_MAX_SHOW_DURATION)
+ assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+ }
+
+ @Test
fun uiState_projectingTask_foregroundTaskChanged_thenTaskSwitched_emitsNotShowing() =
testScope.runTest {
val projectedTask = createTask(taskId = 1)
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 52859cd..d405df7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
@@ -43,13 +43,11 @@
import android.content.res.Configuration;
import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper.RunWithLooper;
import android.util.SparseArray;
import androidx.test.filters.SmallTest;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
-import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.model.SysUiState;
@@ -61,7 +59,9 @@
import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.util.time.FakeSystemClock;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.pip.Pip;
@@ -76,7 +76,6 @@
/** atest NavigationBarControllerTest */
@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
@SmallTest
public class NavigationBarControllerImplTest extends SysuiTestCase {
@@ -88,6 +87,8 @@
private StaticMockitoSession mMockitoSession;
private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
+ private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
+
@Mock
private CommandQueue mCommandQueue;
@Mock
@@ -104,7 +105,7 @@
mock(NavigationModeController.class),
mock(SysUiState.class),
mCommandQueue,
- Dependency.get(Dependency.MAIN_HANDLER),
+ mExecutor,
mock(ConfigurationController.class),
mock(NavBarHelper.class),
mTaskbarDelegate,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
index 7b285ab..ada93db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
@@ -33,6 +33,7 @@
import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
import com.android.systemui.mediaprojection.SessionCreationSource
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialogDelegate
import com.android.systemui.model.SysUiState
import com.android.systemui.qs.tiles.RecordIssueTile
import com.android.systemui.res.R
@@ -41,18 +42,18 @@
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.CountDownLatch
-import java.util.concurrent.Executor
import java.util.concurrent.TimeUnit
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
@@ -74,12 +75,16 @@
@Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var userFileManager: UserFileManager
@Mock private lateinit var sharedPreferences: SharedPreferences
+ @Mock private lateinit var screenCaptureDisabledDialogDelegate:
+ ScreenCaptureDisabledDialogDelegate
+ @Mock private lateinit var screenCaptureDisabledDialog: SystemUIDialog
@Mock private lateinit var sysuiState: SysUiState
@Mock private lateinit var systemUIDialogManager: SystemUIDialogManager
@Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
- @Mock private lateinit var bgExecutor: Executor
- @Mock private lateinit var mainExecutor: Executor
+ private val systemClock = FakeSystemClock()
+ private val bgExecutor = FakeExecutor(systemClock)
+ private val mainExecutor = FakeExecutor(systemClock)
@Mock private lateinit var mDialogTransitionAnimator: DialogTransitionAnimator
private lateinit var dialog: SystemUIDialog
@@ -92,6 +97,8 @@
whenever(dprLazy.get()).thenReturn(devicePolicyResolver)
whenever(sysuiState.setFlag(anyInt(), anyBoolean())).thenReturn(sysuiState)
whenever(userContextProvider.userContext).thenReturn(mContext)
+ whenever(screenCaptureDisabledDialogDelegate.createDialog())
+ .thenReturn(screenCaptureDisabledDialog)
whenever(
userFileManager.getSharedPreferences(
eq(RecordIssueTile.TILE_SPEC),
@@ -124,6 +131,7 @@
dprLazy,
mediaProjectionMetricsLogger,
userFileManager,
+ screenCaptureDisabledDialogDelegate,
) {
latch.countDown()
}
@@ -163,13 +171,8 @@
val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch)
screenRecordSwitch.isChecked = true
- val bgCaptor = ArgumentCaptor.forClass(Runnable::class.java)
- verify(bgExecutor).execute(bgCaptor.capture())
- bgCaptor.value.run()
-
- val mainCaptor = ArgumentCaptor.forClass(Runnable::class.java)
- verify(mainExecutor).execute(mainCaptor.capture())
- mainCaptor.value.run()
+ bgExecutor.runAllReady()
+ mainExecutor.runAllReady()
verify(mediaProjectionMetricsLogger, never())
.notifyProjectionInitiated(
@@ -192,13 +195,8 @@
val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch)
screenRecordSwitch.isChecked = true
- val bgCaptor = ArgumentCaptor.forClass(Runnable::class.java)
- verify(bgExecutor).execute(bgCaptor.capture())
- bgCaptor.value.run()
-
- val mainCaptor = ArgumentCaptor.forClass(Runnable::class.java)
- verify(mainExecutor).execute(mainCaptor.capture())
- mainCaptor.value.run()
+ bgExecutor.runAllReady()
+ mainExecutor.runAllReady()
verify(mediaProjectionMetricsLogger)
.notifyProjectionInitiated(
@@ -219,9 +217,7 @@
val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch)
screenRecordSwitch.isChecked = true
- val bgCaptor = ArgumentCaptor.forClass(Runnable::class.java)
- verify(bgExecutor).execute(bgCaptor.capture())
- bgCaptor.value.run()
+ bgExecutor.runAllReady()
verify(mediaProjectionMetricsLogger)
.notifyProjectionInitiated(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
index 6cbe8c9..b3df12ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
@@ -17,13 +17,11 @@
package com.android.systemui.screenrecord;
import static android.os.Process.myUid;
-
import static com.google.common.truth.Truth.assertThat;
-
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
-
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -48,10 +46,9 @@
import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
import com.android.systemui.mediaprojection.SessionCreationSource;
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
-import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialogDelegate;
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.DialogDelegate;
import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -84,8 +81,6 @@
@Mock
private BroadcastDispatcher mBroadcastDispatcher;
@Mock
- private UserContextProvider mUserContextProvider;
- @Mock
private ScreenCaptureDevicePolicyResolver mDevicePolicyResolver;
@Mock
private DialogTransitionAnimator mDialogTransitionAnimator;
@@ -96,6 +91,22 @@
@Mock
private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
+ @Mock
+ private ScreenCaptureDisabledDialogDelegate mScreenCaptureDisabledDialogDelegate;
+ @Mock
+ private SystemUIDialog mScreenCaptureDisabledDialog;
+ @Mock
+ private ScreenRecordDialogDelegate.Factory mScreenRecordDialogFactory;
+ @Mock
+ private ScreenRecordDialogDelegate mScreenRecordDialogDelegate;
+ @Mock
+ private ScreenRecordPermissionDialogDelegate.Factory
+ mScreenRecordPermissionDialogDelegateFactory;
+ @Mock
+ private ScreenRecordPermissionDialogDelegate mScreenRecordPermissionDialogDelegate;
+ @Mock
+ private SystemUIDialog mScreenRecordSystemUIDialog;
+
private FakeFeatureFlags mFeatureFlags;
private RecordingController mController;
private TestSystemUIDialogFactory mDialogFactory;
@@ -108,8 +119,6 @@
Context spiedContext = spy(mContext);
when(spiedContext.getUserId()).thenReturn(TEST_USER_ID);
- when(mUserContextProvider.getUserContext()).thenReturn(spiedContext);
-
mDialogFactory = new TestSystemUIDialogFactory(
mContext,
Dependency.get(SystemUIDialogManager.class),
@@ -119,16 +128,26 @@
);
mFeatureFlags = new FakeFeatureFlags();
+ when(mScreenCaptureDisabledDialogDelegate.createDialog())
+ .thenReturn(mScreenCaptureDisabledDialog);
+ when(mScreenRecordDialogFactory.create(any(), any()))
+ .thenReturn(mScreenRecordDialogDelegate);
+ when(mScreenRecordDialogDelegate.createDialog()).thenReturn(mScreenRecordSystemUIDialog);
+ when(mScreenRecordPermissionDialogDelegateFactory.create(any(), any(), anyInt(), any()))
+ .thenReturn(mScreenRecordPermissionDialogDelegate);
+ when(mScreenRecordPermissionDialogDelegate.createDialog())
+ .thenReturn(mScreenRecordSystemUIDialog);
mController = new RecordingController(
mMainExecutor,
mBroadcastDispatcher,
- mContext,
mFeatureFlags,
- mUserContextProvider,
() -> mDevicePolicyResolver,
mUserTracker,
mMediaProjectionMetricsLogger,
- mDialogFactory);
+ mScreenCaptureDisabledDialogDelegate,
+ mScreenRecordDialogFactory,
+ mScreenRecordPermissionDialogDelegateFactory
+ );
mController.addCallback(mCallback);
}
@@ -242,8 +261,8 @@
mActivityStarter,
/* onStartRecordingClicked= */ null);
- assertThat(dialog).isSameInstanceAs(mDialogFactory.mLastCreatedDialog);
- assertThat(mDialogFactory.mLastDelegate)
+ assertThat(dialog).isSameInstanceAs(mScreenRecordSystemUIDialog);
+ assertThat(mScreenRecordPermissionDialogDelegate)
.isInstanceOf(ScreenRecordPermissionDialogDelegate.class);
}
@@ -255,7 +274,7 @@
Dialog dialog = mController.createScreenRecordDialog(mContext, mFeatureFlags,
mDialogTransitionAnimator, mActivityStarter, /* onStartRecordingClicked= */ null);
- assertThat(dialog).isInstanceOf(ScreenRecordDialog.class);
+ assertThat(dialog).isEqualTo(mScreenRecordSystemUIDialog);
}
@Test
@@ -267,7 +286,7 @@
Dialog dialog = mController.createScreenRecordDialog(mContext, mFeatureFlags,
mDialogTransitionAnimator, mActivityStarter, /* onStartRecordingClicked= */ null);
- assertThat(dialog).isInstanceOf(ScreenCaptureDisabledDialog.class);
+ assertThat(dialog).isEqualTo(mScreenCaptureDisabledDialog);
}
@Test
@@ -284,8 +303,8 @@
mActivityStarter,
/* onStartRecordingClicked= */ null);
- assertThat(dialog).isSameInstanceAs(mDialogFactory.mLastCreatedDialog);
- assertThat(mDialogFactory.mLastDelegate)
+ assertThat(dialog).isSameInstanceAs(mScreenRecordSystemUIDialog);
+ assertThat(mScreenRecordPermissionDialogDelegate)
.isInstanceOf(ScreenRecordPermissionDialogDelegate.class);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
index 90ced92..6e48074 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
@@ -39,6 +39,7 @@
import com.android.systemui.settings.UserContextProvider
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
@@ -58,6 +59,7 @@
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class ScreenRecordPermissionDialogDelegateTest : SysuiTestCase() {
+ //@Mock private lateinit var dialogFactory: SystemUIDialog.Factory
@Mock private lateinit var starter: ActivityStarter
@Mock private lateinit var controller: RecordingController
@Mock private lateinit var userContextProvider: UserContextProvider
@@ -71,14 +73,17 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
+ whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
+
val systemUIDialogFactory =
- SystemUIDialog.Factory(
- context,
- Dependency.get(SystemUIDialogManager::class.java),
- Dependency.get(SysUiState::class.java),
- Dependency.get(BroadcastDispatcher::class.java),
- Dependency.get(DialogTransitionAnimator::class.java),
- )
+ SystemUIDialog.Factory(
+ context,
+ Dependency.get(SystemUIDialogManager::class.java),
+ Dependency.get(SysUiState::class.java),
+ Dependency.get(BroadcastDispatcher::class.java),
+ Dependency.get(DialogTransitionAnimator::class.java),
+ )
+
val delegate =
ScreenRecordPermissionDialogDelegate(
UserHandle.of(0),
@@ -88,11 +93,9 @@
userContextProvider,
onStartRecordingClicked,
mediaProjectionMetricsLogger,
- systemUIDialogFactory
+ systemUIDialogFactory,
)
dialog = delegate.createDialog()
- delegate.onCreate(dialog, savedInstanceState = null)
- whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
}
@After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt
index 2f911fff..92c2404 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt
@@ -22,8 +22,10 @@
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import java.lang.IllegalStateException
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -31,12 +33,14 @@
import org.mockito.Mockito.verify
@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
class ScreenshotSoundControllerTest : SysuiTestCase() {
private val soundProvider = mock<ScreenshotSoundProvider>()
private val mediaPlayer = mock<MediaPlayer>()
private val bgDispatcher = UnconfinedTestDispatcher()
private val scope = TestScope(bgDispatcher)
+
@Before
fun setup() {
whenever(soundProvider.getScreenshotSound()).thenReturn(mediaPlayer)
@@ -45,52 +49,59 @@
@Test
fun init_soundLoading() {
createController()
- bgDispatcher.scheduler.runCurrent()
+ scope.advanceUntilIdle()
verify(soundProvider).getScreenshotSound()
}
@Test
- fun init_soundLoadingException_playAndReleaseDoNotThrow() = runTest {
- whenever(soundProvider.getScreenshotSound()).thenThrow(IllegalStateException())
+ fun init_soundLoadingException_playAndReleaseDoNotThrow() =
+ scope.runTest {
+ whenever(soundProvider.getScreenshotSound()).thenThrow(IllegalStateException())
- val controller = createController()
+ val controller = createController()
- controller.playCameraSound().await()
- controller.releaseScreenshotSound().await()
+ controller.playScreenshotSound()
+ advanceUntilIdle()
- verify(mediaPlayer, never()).start()
- verify(mediaPlayer, never()).release()
- }
+ verify(mediaPlayer, never()).start()
+ verify(mediaPlayer, never()).release()
+ }
@Test
- fun playCameraSound_soundLoadingSuccessful_mediaPlayerPlays() = runTest {
- val controller = createController()
+ fun playCameraSound_soundLoadingSuccessful_mediaPlayerPlays() =
+ scope.runTest {
+ val controller = createController()
- controller.playCameraSound().await()
+ controller.playScreenshotSound()
+ advanceUntilIdle()
- verify(mediaPlayer).start()
- }
+ verify(mediaPlayer).start()
+ }
@Test
- fun playCameraSound_illegalStateException_doesNotThrow() = runTest {
- whenever(mediaPlayer.start()).thenThrow(IllegalStateException())
+ fun playCameraSound_illegalStateException_doesNotThrow() =
+ scope.runTest {
+ whenever(mediaPlayer.start()).thenThrow(IllegalStateException())
- val controller = createController()
- controller.playCameraSound().await()
+ val controller = createController()
+ controller.playScreenshotSound()
+ advanceUntilIdle()
- verify(mediaPlayer).start()
- verify(mediaPlayer).release()
- }
+ verify(mediaPlayer).start()
+ verify(mediaPlayer).release()
+ }
@Test
- fun playCameraSound_soundLoadingSuccessful_mediaPlayerReleases() = runTest {
- val controller = createController()
+ fun playCameraSound_soundLoadingSuccessful_mediaPlayerReleases() =
+ scope.runTest {
+ val controller = createController()
- controller.releaseScreenshotSound().await()
+ controller.releaseScreenshotSound()
+ advanceUntilIdle()
- verify(mediaPlayer).release()
- }
+ verify(mediaPlayer).release()
+ }
private fun createController() =
ScreenshotSoundControllerImpl(soundProvider, scope, bgDispatcher)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index fd7b139..acbf997 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -287,7 +287,7 @@
@Mock protected KeyguardMediaController mKeyguardMediaController;
@Mock protected NavigationModeController mNavigationModeController;
@Mock protected NavigationBarController mNavigationBarController;
- @Mock protected QuickSettingsController mQsController;
+ @Mock protected QuickSettingsControllerImpl mQsController;
@Mock protected ShadeHeaderController mShadeHeaderController;
@Mock protected ContentResolver mContentResolver;
@Mock protected TapAgainViewController mTapAgainViewController;
@@ -380,7 +380,7 @@
protected final ShadeExpansionStateManager mShadeExpansionStateManager =
new ShadeExpansionStateManager();
- protected QuickSettingsController mQuickSettingsController;
+ protected QuickSettingsControllerImpl mQuickSettingsController;
@Mock protected Lazy<NotificationPanelViewController> mNotificationPanelViewControllerLazy;
protected FragmentHostManager.FragmentListener mFragmentListener;
@@ -794,7 +794,7 @@
when(mNotificationPanelViewControllerLazy.get())
.thenReturn(mNotificationPanelViewController);
- mQuickSettingsController = new QuickSettingsController(
+ mQuickSettingsController = new QuickSettingsControllerImpl(
mNotificationPanelViewControllerLazy,
mView,
mQsFrameTranslateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 960fd59..617b25d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -112,7 +112,7 @@
@Mock private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController
@Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
@Mock private lateinit var statusBarWindowStateController: StatusBarWindowStateController
- @Mock private lateinit var quickSettingsController: QuickSettingsController
+ @Mock private lateinit var quickSettingsController: QuickSettingsControllerImpl
@Mock
private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController
@Mock private lateinit var lockIconViewController: LockIconViewController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
index 0b49a95..4809a47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
@@ -109,7 +109,7 @@
import kotlinx.coroutines.test.TestScope;
-public class QuickSettingsControllerBaseTest extends SysuiTestCase {
+public class QuickSettingsControllerImplBaseTest extends SysuiTestCase {
protected static final float QS_FRAME_START_X = 0f;
protected static final int QS_FRAME_WIDTH = 1000;
protected static final int QS_FRAME_TOP = 0;
@@ -119,7 +119,7 @@
protected static final int DEFAULT_MIN_HEIGHT_SPLIT_SHADE = DEFAULT_HEIGHT;
protected static final int DEFAULT_MIN_HEIGHT = 300;
- protected QuickSettingsController mQsController;
+ protected QuickSettingsControllerImpl mQsController;
protected KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
protected TestScope mTestScope = mKosmos.getTestScope();
@@ -304,7 +304,7 @@
mMainHandler = new Handler(Looper.getMainLooper());
- mQsController = new QuickSettingsController(
+ mQsController = new QuickSettingsControllerImpl(
mPanelViewControllerLazy,
mPanelView,
mQsFrameTranslateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java
index 997e0e2..b16f412 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java
@@ -41,8 +41,8 @@
import androidx.test.filters.SmallTest;
-import com.android.systemui.res.R;
import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.res.R;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -53,7 +53,7 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class QuickSettingsControllerTest extends QuickSettingsControllerBaseTest {
+public class QuickSettingsControllerImplTest extends QuickSettingsControllerImplBaseTest {
@Test
public void testCloseQsSideEffects() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt
similarity index 96%
rename from packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerWithCoroutinesTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt
index cc4a063..2c453a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt
@@ -27,7 +27,7 @@
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
-class QuickSettingsControllerWithCoroutinesTest : QuickSettingsControllerBaseTest() {
+class QuickSettingsControllerImplWithCoroutinesTest : QuickSettingsControllerImplBaseTest() {
@Test
fun isExpansionEnabled_dozing_false() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
index b3fc25c..24195fe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
@@ -16,8 +16,10 @@
package com.android.systemui.statusbar.notification.icon.ui.viewmodel
+import android.platform.test.annotations.DisableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
import com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION
import com.android.systemui.SysUITestComponent
import com.android.systemui.SysUITestModule
@@ -238,6 +240,7 @@
}
@Test
+ @DisableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun animationsEnabled_isTrue_whenKeyguardIsShowing() =
testComponent.runTest {
keyguardTransitionRepository.sendTransitionStep(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index e78081f..fb49499f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -89,6 +89,8 @@
import com.android.systemui.statusbar.policy.SmartReplyConstants;
import com.android.systemui.statusbar.policy.SmartReplyStateInflater;
import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent;
+import com.android.systemui.util.time.SystemClock;
+import com.android.systemui.util.time.SystemClockImpl;
import com.android.systemui.wmshell.BubblesManager;
import com.android.systemui.wmshell.BubblesTestActivity;
@@ -136,6 +138,8 @@
public final Runnable mFutureDismissalRunnable;
private @InflationFlag int mDefaultInflationFlags;
private final FakeFeatureFlags mFeatureFlags;
+ private final SystemClock mSystemClock;
+ private final RowInflaterTaskLogger mRowInflaterTaskLogger;
public NotificationTestHelper(
Context context,
@@ -199,6 +203,9 @@
mFutureDismissalRunnable = mock(Runnable.class);
when(mOnUserInteractionCallback.registerFutureDismissal(any(), anyInt()))
.thenReturn(mFutureDismissalRunnable);
+
+ mSystemClock = new SystemClockImpl();
+ mRowInflaterTaskLogger = mock(RowInflaterTaskLogger.class);
}
public void setDefaultInflationFlags(@InflationFlag int defaultInflationFlags) {
@@ -572,7 +579,8 @@
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
if (com.android.systemui.Flags.notificationRowUserContext()) {
- inflater.setFactory2(new RowInflaterTask.RowAsyncLayoutInflater(entry));
+ inflater.setFactory2(new RowInflaterTask.RowAsyncLayoutInflater(entry, mSystemClock,
+ mRowInflaterTaskLogger));
}
mRow = (ExpandableNotificationRow) inflater.inflate(
R.layout.status_bar_notification_row,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelTest.kt
new file mode 100644
index 0000000..f88bd7e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelTest.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.row.ui.viewmodel
+
+import android.platform.test.annotations.EnableFlags
+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.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.power.shared.model.WakefulnessState
+import com.android.systemui.statusbar.notification.shared.NotificationViewFlipperPausing
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(NotificationViewFlipperPausing.FLAG_NAME)
+class NotificationViewFlipperViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ val underTest
+ get() = kosmos.notificationViewFlipperViewModel
+
+ @Test
+ fun testIsPaused_falseWhenViewingShade() =
+ kosmos.testScope.runTest {
+ val isPaused by collectLastValue(underTest.isPaused)
+
+ // WHEN shade is open
+ kosmos.fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ runCurrent()
+
+ // THEN view flippers should NOT be paused
+ assertThat(isPaused).isFalse()
+ }
+
+ @Test
+ fun testIsPaused_trueWhenViewingKeyguard() =
+ kosmos.testScope.runTest {
+ val isPaused by collectLastValue(underTest.isPaused)
+
+ // WHEN on keyguard
+ kosmos.fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+ runCurrent()
+
+ // THEN view flippers should be paused
+ assertThat(isPaused).isTrue()
+ }
+
+ @Test
+ fun testIsPaused_trueWhenStartingToSleep() =
+ kosmos.testScope.runTest {
+ val isPaused by collectLastValue(underTest.isPaused)
+
+ // WHEN shade is open
+ kosmos.fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ // AND device is starting to go to sleep
+ kosmos.fakePowerRepository.updateWakefulness(WakefulnessState.STARTING_TO_SLEEP)
+ runCurrent()
+
+ // THEN view flippers should be paused
+ assertThat(isPaused).isTrue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index f050857..562aa6a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -95,6 +95,7 @@
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.domain.interactor.StatusBarKeyguardViewManagerInteractor;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
@@ -226,7 +227,8 @@
mSelectedUserInteractor,
() -> mock(KeyguardSurfaceBehindInteractor.class),
mock(JavaAdapter.class),
- () -> mock(SceneInteractor.class)) {
+ () -> mock(SceneInteractor.class),
+ mock(StatusBarKeyguardViewManagerInteractor.class)) {
@Override
public ViewRootImpl getViewRootImpl() {
return mViewRootImpl;
@@ -736,7 +738,8 @@
mSelectedUserInteractor,
() -> mock(KeyguardSurfaceBehindInteractor.class),
mock(JavaAdapter.class),
- () -> mock(SceneInteractor.class)) {
+ () -> mock(SceneInteractor.class),
+ mock(StatusBarKeyguardViewManagerInteractor.class)) {
@Override
public ViewRootImpl getViewRootImpl() {
return mViewRootImpl;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index 1dafcc4..b0404a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -15,13 +15,14 @@
package com.android.systemui.statusbar.phone;
import static android.view.Display.DEFAULT_DISPLAY;
+
import static com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.BUBBLE;
import static com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK;
import static com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE;
+
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorKosmos.kt
index c3f677e..d5411ad 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorKosmos.kt
@@ -21,6 +21,7 @@
import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.plugins.activityStarter
@@ -40,5 +41,6 @@
context = mockedContext,
activityStarter = activityStarter,
powerInteractor = powerInteractor,
+ keyguardTransitionInteractor = keyguardTransitionInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
index a9a2d91..dcbd577 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.data.repository
import android.annotation.FloatRange
+import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
@@ -65,55 +66,79 @@
}
/**
- * Sends STARTED, RUNNING, and FINISHED TransitionSteps between [from] and [to], calling
- * [runCurrent] after each step.
+ * Sends TransitionSteps between [from] and [to], calling [runCurrent] after each step.
+ *
+ * By default, sends steps through FINISHED (STARTED, RUNNING, FINISHED) but can be halted part
+ * way using [throughTransitionState].
*/
suspend fun sendTransitionSteps(
from: KeyguardState,
to: KeyguardState,
testScope: TestScope,
+ throughTransitionState: TransitionState = TransitionState.FINISHED,
) {
- sendTransitionSteps(from, to, testScope.testScheduler)
+ sendTransitionSteps(from, to, testScope.testScheduler, throughTransitionState)
}
/**
- * Sends STARTED, RUNNING, and FINISHED TransitionSteps between [from] and [to], calling
- * [runCurrent] after each step.
+ * Sends TransitionSteps between [from] and [to], calling [runCurrent] after each step.
+ *
+ * By default, sends steps through FINISHED (STARTED, RUNNING, FINISHED) but can be halted part
+ * way using [throughTransitionState].
*/
suspend fun sendTransitionSteps(
from: KeyguardState,
to: KeyguardState,
testScheduler: TestCoroutineScheduler,
+ throughTransitionState: TransitionState = TransitionState.FINISHED,
) {
sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.STARTED,
- from = from,
- to = to,
- value = 0f,
- )
+ step =
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = from,
+ to = to,
+ value = 0f,
+ )
)
testScheduler.runCurrent()
- sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.RUNNING,
- from = from,
- to = to,
- value = 0.5f
+ if (
+ throughTransitionState == TransitionState.RUNNING ||
+ throughTransitionState == TransitionState.FINISHED
+ ) {
+ sendTransitionStep(
+ step =
+ TransitionStep(
+ transitionState = TransitionState.RUNNING,
+ from = from,
+ to = to,
+ value = 0.5f
+ )
)
- )
- testScheduler.runCurrent()
+ testScheduler.runCurrent()
+ }
- sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.FINISHED,
- from = from,
- to = to,
- value = 1f,
+ if (throughTransitionState == TransitionState.FINISHED) {
+ sendTransitionStep(
+ step =
+ TransitionStep(
+ transitionState = TransitionState.FINISHED,
+ from = from,
+ to = to,
+ value = 1f,
+ )
)
+ testScheduler.runCurrent()
+ }
+ }
+
+ suspend fun sendTransitionStep(step: TransitionStep, validateStep: Boolean = true) {
+ this.sendTransitionStep(
+ step = step,
+ validateStep = validateStep,
+ ownerName = step.ownerName
)
- testScheduler.runCurrent()
}
/**
@@ -132,7 +157,22 @@
* If you're testing something involving transitions themselves and are sure you want to send
* only a FINISHED step, override [validateStep].
*/
- suspend fun sendTransitionStep(step: TransitionStep, validateStep: Boolean = true) {
+ suspend fun sendTransitionStep(
+ from: KeyguardState = KeyguardState.OFF,
+ to: KeyguardState = KeyguardState.OFF,
+ value: Float = 0f,
+ transitionState: TransitionState = TransitionState.FINISHED,
+ ownerName: String = "",
+ step: TransitionStep =
+ TransitionStep(
+ from = from,
+ to = to,
+ value = value,
+ transitionState = transitionState,
+ ownerName = ownerName
+ ),
+ validateStep: Boolean = true
+ ) {
_transitions.replayCache.last().let { lastStep ->
if (
validateStep &&
@@ -159,7 +199,9 @@
step: TransitionStep,
validateStep: Boolean = true
): Job {
- return coroutineScope.launch { sendTransitionStep(step, validateStep) }
+ return coroutineScope.launch {
+ sendTransitionStep(step = step, validateStep = validateStep)
+ }
}
suspend fun sendTransitionSteps(
@@ -168,12 +210,13 @@
validateStep: Boolean = true
) {
steps.forEach {
- sendTransitionStep(it, validateStep = validateStep)
+ sendTransitionStep(step = it, validateStep = validateStep)
testScope.testScheduler.runCurrent()
}
}
override fun startTransition(info: TransitionInfo): UUID? {
+ Log.i("TEST", "Start transition: ", Exception())
return if (info.animator == null) UUID.randomUUID() else null
}
diff --git a/core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardOcclusionRepositoryKosmos.kt
similarity index 62%
copy from core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl
copy to packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardOcclusionRepositoryKosmos.kt
index 838e41e..4c8bf90 100644
--- a/core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardOcclusionRepositoryKosmos.kt
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2024, The Android Open Source Project
+/*
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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,
@@ -14,11 +14,8 @@
* limitations under the License.
*/
-package android.hardware;
+package com.android.systemui.keyguard.data.repository
-/** @hide */
-parcelable CameraPrivacyAllowlistEntry {
- String packageName;
- boolean isMandatory;
-}
+import com.android.systemui.kosmos.Kosmos
+val Kosmos.keyguardOcclusionRepository by Kosmos.Fixture { KeyguardOcclusionRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorKosmos.kt
new file mode 100644
index 0000000..530cbed
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorKosmos.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
+
+val Kosmos.fromAlternateBouncerTransitionInteractor by
+ Kosmos.Fixture {
+ FromAlternateBouncerTransitionInteractor(
+ transitionRepository = keyguardTransitionRepository,
+ transitionInteractor = keyguardTransitionInteractor,
+ scope = applicationCoroutineScope,
+ bgDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
+ keyguardInteractor = keyguardInteractor,
+ communalInteractor = communalInteractor,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
index 2477415..bbe37c1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
@@ -18,19 +18,21 @@
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
-import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
val Kosmos.fromAodTransitionInteractor by
Kosmos.Fixture {
FromAodTransitionInteractor(
transitionRepository = fakeKeyguardTransitionRepository,
transitionInteractor = keyguardTransitionInteractor,
- scope = testScope,
+ scope = applicationCoroutineScope,
bgDispatcher = testDispatcher,
mainDispatcher = testDispatcher,
keyguardInteractor = keyguardInteractor,
powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
new file mode 100644
index 0000000..23dcd96
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
+
+var Kosmos.fromDozingTransitionInteractor by
+ Kosmos.Fixture {
+ FromDozingTransitionInteractor(
+ transitionRepository = keyguardTransitionRepository,
+ transitionInteractor = keyguardTransitionInteractor,
+ scope = applicationCoroutineScope,
+ bgDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
+ keyguardInteractor = keyguardInteractor,
+ communalInteractor = communalInteractor,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractorKosmos.kt
new file mode 100644
index 0000000..f7a9d59
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractorKosmos.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
+
+var Kosmos.fromDreamingLockscreenHostedTransitionInteractor by
+ Kosmos.Fixture {
+ FromDreamingLockscreenHostedTransitionInteractor(
+ transitionRepository = keyguardTransitionRepository,
+ transitionInteractor = keyguardTransitionInteractor,
+ scope = applicationCoroutineScope,
+ bgDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
+ keyguardInteractor = keyguardInteractor,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
new file mode 100644
index 0000000..135644c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
+
+var Kosmos.fromDreamingTransitionInteractor by
+ Kosmos.Fixture {
+ FromDreamingTransitionInteractor(
+ transitionRepository = keyguardTransitionRepository,
+ transitionInteractor = keyguardTransitionInteractor,
+ scope = applicationCoroutineScope,
+ bgDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
+ keyguardInteractor = keyguardInteractor,
+ glanceableHubTransitions = glanceableHubTransitions,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt
new file mode 100644
index 0000000..1695327
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
+
+var Kosmos.fromGlanceableHubTransitionInteractor by
+ Kosmos.Fixture {
+ FromGlanceableHubTransitionInteractor(
+ transitionRepository = keyguardTransitionRepository,
+ transitionInteractor = keyguardTransitionInteractor,
+ scope = applicationCoroutineScope,
+ bgDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
+ keyguardInteractor = keyguardInteractor,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ glanceableHubTransitions = glanceableHubTransitions,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt
index 25fc67a..604d9e4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt
@@ -17,11 +17,13 @@
package com.android.systemui.keyguard.domain.interactor
import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
val Kosmos.fromGoneTransitionInteractor by
Kosmos.Fixture {
@@ -34,5 +36,7 @@
keyguardInteractor = keyguardInteractor,
powerInteractor = powerInteractor,
communalInteractor = communalInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ biometricSettingsRepository = biometricSettingsRepository,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
index 3b52676..162fd90 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
@@ -23,6 +23,7 @@
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
var Kosmos.fromLockscreenTransitionInteractor by
Kosmos.Fixture {
@@ -38,5 +39,6 @@
powerInteractor = powerInteractor,
glanceableHubTransitions = glanceableHubTransitions,
swipeToDismissInteractor = swipeToDismissInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorKosmos.kt
new file mode 100644
index 0000000..fc740a1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorKosmos.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
+
+val Kosmos.fromOccludedTransitionInteractor by
+ Kosmos.Fixture {
+ FromOccludedTransitionInteractor(
+ transitionRepository = keyguardTransitionRepository,
+ transitionInteractor = keyguardTransitionInteractor,
+ scope = applicationCoroutineScope,
+ bgDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
+ keyguardInteractor = keyguardInteractor,
+ powerInteractor = powerInteractor,
+ communalInteractor = communalInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
index 6b76449..98babff 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
@@ -24,6 +24,7 @@
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
import com.android.systemui.user.domain.interactor.selectedUserInteractor
var Kosmos.fromPrimaryBouncerTransitionInteractor by
@@ -40,5 +41,6 @@
keyguardSecurityModel = keyguardSecurityModel,
selectedUserInteractor = selectedUserInteractor,
powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
index 6df7493..6cc1e8e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
@@ -16,19 +16,21 @@
package com.android.systemui.keyguard.domain.interactor
+import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
-import dagger.Lazy
val Kosmos.keyguardTransitionInteractor: KeyguardTransitionInteractor by
Kosmos.Fixture {
KeyguardTransitionInteractor(
scope = applicationCoroutineScope,
repository = keyguardTransitionRepository,
- fromLockscreenTransitionInteractor = Lazy { fromLockscreenTransitionInteractor },
- fromPrimaryBouncerTransitionInteractor =
- Lazy { fromPrimaryBouncerTransitionInteractor },
- fromAodTransitionInteractor = Lazy { fromAodTransitionInteractor },
+ keyguardRepository = keyguardRepository,
+ fromLockscreenTransitionInteractor = { fromLockscreenTransitionInteractor },
+ fromPrimaryBouncerTransitionInteractor = { fromPrimaryBouncerTransitionInteractor },
+ fromAodTransitionInteractor = { fromAodTransitionInteractor },
+ fromAlternateBouncerTransitionInteractor = { fromAlternateBouncerTransitionInteractor },
+ fromDozingTransitionInteractor = { fromDozingTransitionInteractor },
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModelKosmos.kt
similarity index 62%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModelKosmos.kt
index ad8ccb0..8162520 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModelKosmos.kt
@@ -14,12 +14,16 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package com.android.systemui.keyguard.ui.viewmodel
+import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.media.mediaOutputDialogFactory
-import com.android.systemui.plugins.activityStarter
-import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputActionsInteractor
+import kotlinx.coroutines.ExperimentalCoroutinesApi
-val Kosmos.mediaOutputActionsInteractor by
- Kosmos.Fixture { MediaOutputActionsInteractor(mediaOutputDialogFactory, activityStarter) }
+@ExperimentalCoroutinesApi
+val Kosmos.dozingToOccludedTransitionViewModel by
+ Kosmos.Fixture {
+ DozingToOccludedTransitionViewModel(
+ animationFlow = keyguardTransitionAnimationFlow,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index c2300a1e..75e3ac2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
@file:OptIn(ExperimentalCoroutinesApi::class)
package com.android.systemui.keyguard.ui.viewmodel
@@ -41,8 +40,10 @@
alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel,
aodToGoneTransitionViewModel = aodToGoneTransitionViewModel,
aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
+ aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel,
dozingToGoneTransitionViewModel = dozingToGoneTransitionViewModel,
dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel,
+ dozingToOccludedTransitionViewModel = dozingToOccludedTransitionViewModel,
dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel,
glanceableHubToLockscreenTransitionViewModel = glanceableHubToLockscreenTransitionViewModel,
goneToAodTransitionViewModel = goneToAodTransitionViewModel,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeActivityTaskManager.kt
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeActivityTaskManager.kt
index 920e5ee..41d2d60 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeActivityTaskManager.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.mediaprojection.taskswitcher.data.repository
+package com.android.systemui.mediaprojection.taskswitcher
import android.app.ActivityManager.RunningTaskInfo
import android.app.IActivityTaskManager
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeMediaProjectionManager.kt
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeMediaProjectionManager.kt
index 28393e8..2b6032c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeMediaProjectionManager.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.mediaprojection.taskswitcher.data.repository
+package com.android.systemui.mediaprojection.taskswitcher
import android.media.projection.MediaProjectionInfo
import android.media.projection.MediaProjectionManager
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/TaskSwitcherKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/TaskSwitcherKosmos.kt
new file mode 100644
index 0000000..d344b75
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/TaskSwitcherKosmos.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.taskswitcher
+
+import android.os.Handler
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
+import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
+import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+
+val Kosmos.fakeActivityTaskManager by Kosmos.Fixture { FakeActivityTaskManager() }
+
+val Kosmos.fakeMediaProjectionManager by Kosmos.Fixture { FakeMediaProjectionManager() }
+
+val Kosmos.activityTaskManagerTasksRepository by
+ Kosmos.Fixture {
+ ActivityTaskManagerTasksRepository(
+ activityTaskManager = fakeActivityTaskManager.activityTaskManager,
+ applicationScope = applicationCoroutineScope,
+ backgroundDispatcher = testDispatcher
+ )
+ }
+
+val Kosmos.mediaProjectionManagerRepository by
+ Kosmos.Fixture {
+ MediaProjectionManagerRepository(
+ mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
+ handler = Handler.getMain(),
+ applicationScope = applicationCoroutineScope,
+ tasksRepository = activityTaskManagerTasksRepository,
+ backgroundDispatcher = testDispatcher,
+ mediaProjectionServiceHelper = fakeMediaProjectionManager.helper,
+ )
+ }
+
+val Kosmos.taskSwitcherInteractor by
+ Kosmos.Fixture {
+ TaskSwitchInteractor(mediaProjectionManagerRepository, activityTaskManagerTasksRepository)
+ }
+
+val Kosmos.taskSwitcherViewModel by
+ Kosmos.Fixture { TaskSwitcherNotificationViewModel(taskSwitcherInteractor, testDispatcher) }
+
+@OptIn(ExperimentalCoroutinesApi::class)
+fun taskSwitcherKosmos() = Kosmos().apply { testDispatcher = UnconfinedTestDispatcher() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/battery/BatterySaverTileKosmos.kt
similarity index 62%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/battery/BatterySaverTileKosmos.kt
index ad8ccb0..a2d1d93 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/battery/BatterySaverTileKosmos.kt
@@ -14,12 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package com.android.systemui.qs.tiles.impl.battery
+import com.android.systemui.battery.BatterySaverModule
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.media.mediaOutputDialogFactory
-import com.android.systemui.plugins.activityStarter
-import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputActionsInteractor
+import com.android.systemui.qs.qsEventLogger
-val Kosmos.mediaOutputActionsInteractor by
- Kosmos.Fixture { MediaOutputActionsInteractor(mediaOutputDialogFactory, activityStarter) }
+val Kosmos.qsBatterySaverTileConfig by
+ Kosmos.Fixture { BatterySaverModule.provideBatterySaverTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardOcclusionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardOcclusionInteractorKosmos.kt
new file mode 100644
index 0000000..d793740
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardOcclusionInteractorKosmos.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardOcclusionInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.powerInteractor
+
+val Kosmos.keyguardOcclusionInteractor by
+ Kosmos.Fixture {
+ KeyguardOcclusionInteractor(
+ scope = testScope.backgroundScope,
+ repository = keyguardOcclusionRepository,
+ powerInteractor = powerInteractor,
+ transitionInteractor = keyguardTransitionInteractor,
+ keyguardInteractor = keyguardInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardViewOcclusionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardViewOcclusionInteractorKosmos.kt
new file mode 100644
index 0000000..9e34fe8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardViewOcclusionInteractorKosmos.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.domain.interactor
+
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.power.domain.interactor.powerInteractor
+
+val Kosmos.statusBarKeyguardViewManagerInteractor by
+ Kosmos.Fixture {
+ StatusBarKeyguardViewManagerInteractor(
+ keyguardTransitionInteractor = this.keyguardTransitionInteractor,
+ keyguardOcclusionInteractor = this.keyguardOcclusionInteractor,
+ powerInteractor = this.powerInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelKosmos.kt
new file mode 100644
index 0000000..7ffa262
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.ui.viewmodel
+
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackInteractor
+
+val Kosmos.notificationViewFlipperViewModel by Fixture {
+ NotificationViewFlipperViewModel(
+ dumpManager = dumpManager,
+ stackInteractor = notificationStackInteractor,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
index 106e85c..c013664 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
@@ -23,7 +23,9 @@
import com.android.systemui.keyguard.ui.viewmodel.alternateBouncerToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.aodToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.aodToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.dozingToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.dozingToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.dreamingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.goneToAodTransitionViewModel
@@ -57,7 +59,9 @@
communalInteractor = communalInteractor,
alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel,
aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
+ aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel,
dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel,
+ dozingToOccludedTransitionViewModel = dozingToOccludedTransitionViewModel,
dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel,
goneToAodTransitionViewModel = goneToAodTransitionViewModel,
goneToDozingTransitionViewModel = goneToDozingTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java
index 5ae033c..d798b3b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java
@@ -30,6 +30,8 @@
private boolean mIsAodPowerSave = false;
private boolean mWirelessCharging;
private boolean mPowerSaveMode = false;
+ private boolean mIsPluggedIn = false;
+ private boolean mIsExtremePowerSave = false;
private final List<BatteryStateChangeCallback> mCallbacks = new ArrayList<>();
@@ -64,8 +66,35 @@
}
@Override
+ public boolean isExtremeSaverOn() {
+ return mIsExtremePowerSave;
+ }
+
+ /**
+ * Note: this does not affect the regular power saver. Triggers all callbacks, only on change.
+ */
+ public void setExtremeSaverOn(Boolean extremePowerSave) {
+ if (extremePowerSave == mIsExtremePowerSave) return;
+
+ mIsExtremePowerSave = extremePowerSave;
+ for (BatteryStateChangeCallback callback: mCallbacks) {
+ callback.onExtremeBatterySaverChanged(extremePowerSave);
+ }
+ }
+
+ @Override
public boolean isPluggedIn() {
- return false;
+ return mIsPluggedIn;
+ }
+
+ /**
+ * Notifies all registered callbacks
+ */
+ public void setPluggedIn(boolean pluggedIn) {
+ mIsPluggedIn = pluggedIn;
+ for (BatteryStateChangeCallback cb : mCallbacks) {
+ cb.onBatteryLevelChanged(0, pluggedIn, false);
+ }
}
@Override
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
index 3f20df3..a3b1a0e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
@@ -25,7 +25,6 @@
import com.android.systemui.kosmos.testCase
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.mediaOutputDialogFactory
-import com.android.systemui.plugins.activityStarter
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
@@ -43,7 +42,7 @@
Kosmos.Fixture { FakeLocalMediaRepositoryFactory { localMediaRepository } }
val Kosmos.mediaOutputActionsInteractor by
- Kosmos.Fixture { MediaOutputActionsInteractor(mediaOutputDialogFactory, activityStarter) }
+ Kosmos.Fixture { MediaOutputActionsInteractor(mediaOutputDialogFactory) }
val Kosmos.mediaControllerRepository by Kosmos.Fixture { FakeMediaControllerRepository() }
val Kosmos.mediaOutputInteractor by
Kosmos.Fixture {
diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
index cc94090..9057d16 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
+++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
@@ -42,7 +42,6 @@
public static final String KEYBOARD_PATHS = "keyboard_paths";
public static final String GRAPHICS_NATIVE_CLASSES = "graphics_native_classes";
- public static final String VALUE_N_A = "**n/a**";
public static final String LIBANDROID_RUNTIME_NAME = "android_runtime";
private static String sInitialDir = new File("").getAbsolutePath();
@@ -130,8 +129,6 @@
}
setProperty(CORE_NATIVE_CLASSES, jniClasses);
setProperty(GRAPHICS_NATIVE_CLASSES, "");
- setProperty(ICU_DATA_PATH, VALUE_N_A);
- setProperty(KEYBOARD_PATHS, VALUE_N_A);
RavenwoodUtils.loadJniLibrary(LIBANDROID_RUNTIME_NAME);
}
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index a754ba5..997f3af 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -59,6 +59,13 @@
}
flag {
+ name: "enable_magnification_one_finger_panning_gesture"
+ namespace: "accessibility"
+ description: "Whether to allow easy-mode (one finger panning gesture) for magnification"
+ bug: "282039824"
+}
+
+flag {
name: "fix_drag_pointer_when_ending_drag"
namespace: "accessibility"
description: "Send the correct pointer id when transitioning from dragging to delegating states."
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index 279bd72..6d1ab9f 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -167,7 +167,7 @@
})
public @interface OverscrollState {}
- @VisibleForTesting boolean mIsSinglePanningEnabled;
+ @VisibleForTesting final OneFingerPanningSettingsProvider mOneFingerPanningSettingsProvider;
private final FullScreenMagnificationVibrationHelper mFullScreenMagnificationVibrationHelper;
@@ -201,7 +201,11 @@
displayId,
fullScreenMagnificationVibrationHelper,
/* magnificationLogger= */ null,
- ViewConfiguration.get(context));
+ ViewConfiguration.get(context),
+ new OneFingerPanningSettingsProvider(
+ context,
+ Flags.enableMagnificationOneFingerPanningGesture()
+ ));
}
/** Constructor for tests. */
@@ -218,7 +222,9 @@
int displayId,
FullScreenMagnificationVibrationHelper fullScreenMagnificationVibrationHelper,
MagnificationLogger magnificationLogger,
- ViewConfiguration viewConfiguration) {
+ ViewConfiguration viewConfiguration,
+ OneFingerPanningSettingsProvider oneFingerPanningSettingsProvider
+ ) {
super(displayId, detectSingleFingerTripleTap, detectTwoFingerTripleTap,
detectShortcutTrigger, trace, callback);
if (DEBUG_ALL) {
@@ -301,9 +307,7 @@
mPanningScalingState = new PanningScalingState(context);
mSinglePanningState = new SinglePanningState(context);
mFullScreenMagnificationVibrationHelper = fullScreenMagnificationVibrationHelper;
- setSinglePanningEnabled(
- context.getResources()
- .getBoolean(R.bool.config_enable_a11y_magnification_single_panning));
+ mOneFingerPanningSettingsProvider = oneFingerPanningSettingsProvider;
mOverscrollHandler = new OverscrollHandler();
mIsWatch = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
@@ -317,11 +321,6 @@
transitionTo(mDetectingState);
}
- @VisibleForTesting
- void setSinglePanningEnabled(boolean isEnabled) {
- mIsSinglePanningEnabled = isEnabled;
- }
-
@Override
void onMotionEventInternal(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
if (event.getActionMasked() == ACTION_DOWN) {
@@ -361,6 +360,7 @@
Slog.i(mLogTag, "onDestroy(); delayed = "
+ MotionEventInfo.toString(mDetectingState.mDelayedEventQueue));
}
+ mOneFingerPanningSettingsProvider.unregister();
if (mScreenStateReceiver != null) {
mScreenStateReceiver.unregister();
@@ -524,7 +524,7 @@
&& event.getPointerCount() == 2 // includes the pointer currently being released
&& mPreviousState == mViewportDraggingState) {
// if feature flag is enabled, currently only true on watches
- if (mIsSinglePanningEnabled) {
+ if (mOneFingerPanningSettingsProvider.isOneFingerPanningEnabled()) {
mOverscrollHandler.setScaleAndCenterToEdgeIfNeeded();
mOverscrollHandler.clearEdgeState();
}
@@ -532,7 +532,7 @@
} else if (action == ACTION_UP || action == ACTION_CANCEL) {
onPanningFinished(event);
// if feature flag is enabled, currently only true on watches
- if (mIsSinglePanningEnabled) {
+ if (mOneFingerPanningSettingsProvider.isOneFingerPanningEnabled()) {
mOverscrollHandler.setScaleAndCenterToEdgeIfNeeded();
mOverscrollHandler.clearEdgeState();
}
@@ -611,7 +611,7 @@
onPan(second);
mFullScreenMagnificationController.offsetMagnifiedRegion(mDisplayId, distanceX,
distanceY, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
- if (mIsSinglePanningEnabled) {
+ if (mOneFingerPanningSettingsProvider.isOneFingerPanningEnabled()) {
mOverscrollHandler.onScrollStateChanged(first, second);
}
return /* event consumed: */ true;
@@ -1000,7 +1000,7 @@
&& event.getPointerCount() == 2) {
transitionToViewportDraggingStateAndClear(event);
} else if (isActivated() && event.getPointerCount() == 2) {
- if (mIsSinglePanningEnabled
+ if (mOneFingerPanningSettingsProvider.isOneFingerPanningEnabled()
&& overscrollState(event, mFirstPointerDownLocation)
== OVERSCROLL_VERTICAL_EDGE) {
transitionToDelegatingStateAndClear();
@@ -1008,7 +1008,7 @@
//Primary pointer is swiping, so transit to PanningScalingState
transitToPanningScalingStateAndClear();
}
- } else if (mIsSinglePanningEnabled
+ } else if (mOneFingerPanningSettingsProvider.isOneFingerPanningEnabled()
&& isActivated()
&& event.getPointerCount() == 1) {
if (overscrollState(event, mFirstPointerDownLocation)
@@ -1255,7 +1255,7 @@
if (isMultiTapTriggered(2 /* taps */) && event.getPointerCount() == 1) {
transitionToViewportDraggingStateAndClear(event);
} else if (isActivated() && event.getPointerCount() == 2) {
- if (mIsSinglePanningEnabled
+ if (mOneFingerPanningSettingsProvider.isOneFingerPanningEnabled()
&& overscrollState(event, mFirstPointerDownLocation)
== OVERSCROLL_VERTICAL_EDGE) {
transitionToDelegatingStateAndClear();
@@ -1263,7 +1263,7 @@
//Primary pointer is swiping, so transit to PanningScalingState
transitToPanningScalingStateAndClear();
}
- } else if (mIsSinglePanningEnabled
+ } else if (mOneFingerPanningSettingsProvider.isOneFingerPanningEnabled()
&& isActivated()
&& event.getPointerCount() == 1) {
if (overscrollState(event, mFirstPointerDownLocation)
@@ -1633,7 +1633,8 @@
+ ", mPreviousState=" + State.nameOf(mPreviousState)
+ ", mMagnificationController=" + mFullScreenMagnificationController
+ ", mDisplayId=" + mDisplayId
- + ", mIsSinglePanningEnabled=" + mIsSinglePanningEnabled
+ + ", mIsSinglePanningEnabled="
+ + mOneFingerPanningSettingsProvider.isOneFingerPanningEnabled()
+ ", mOverscrollHandler=" + mOverscrollHandler
+ '}';
}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/OneFingerPanningSettingsProvider.java b/services/accessibility/java/com/android/server/accessibility/magnification/OneFingerPanningSettingsProvider.java
new file mode 100644
index 0000000..3cdaf98
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/OneFingerPanningSettingsProvider.java
@@ -0,0 +1,104 @@
+/*
+ * 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.server.accessibility.magnification;
+
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.provider.Settings;
+
+import androidx.annotation.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Provider for secure settings {@link Settings.Secure.ACCESSIBILITY_SINGLE_FINGER_PANNING_ENABLED}.
+ */
+public class OneFingerPanningSettingsProvider {
+
+ @VisibleForTesting
+ static final String KEY = Settings.Secure.ACCESSIBILITY_SINGLE_FINGER_PANNING_ENABLED;
+ private static final Uri URI = Settings.Secure.getUriFor(KEY);
+ private AtomicBoolean mCached = new AtomicBoolean();
+ @VisibleForTesting
+ ContentObserver mObserver;
+ @VisibleForTesting
+ ContentResolver mContentResolver;
+
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface State {
+ int OFF = 0;
+ int ON = 1;
+ }
+
+ public OneFingerPanningSettingsProvider(
+ Context context,
+ boolean featureFlagEnabled
+ ) {
+ var defaultValue = isOneFingerPanningEnabledDefault(context);
+ if (featureFlagEnabled) {
+ mContentResolver = context.getContentResolver();
+ mObserver = new ContentObserver(context.getMainThreadHandler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ mCached.set(isOneFingerPanningEnabledInSetting(context, defaultValue));
+ }
+ };
+ mCached.set(isOneFingerPanningEnabledInSetting(context, defaultValue));
+ mContentResolver.registerContentObserver(URI, false, mObserver);
+ } else {
+ mCached.set(defaultValue);
+ }
+ }
+
+ /** Returns whether one finger panning is enabled.. */
+ public boolean isOneFingerPanningEnabled() {
+ return mCached.get();
+ }
+
+ /** Unregister content observer for listening to secure settings. */
+ public void unregister() {
+ if (mContentResolver != null) {
+ mContentResolver.unregisterContentObserver(mObserver);
+ }
+ mContentResolver = null;
+ }
+
+ private boolean isOneFingerPanningEnabledInSetting(Context context, boolean defaultValue) {
+ return State.ON == Settings.Secure.getIntForUser(
+ mContentResolver,
+ KEY,
+ (defaultValue ? State.ON : State.OFF),
+ context.getUserId());
+ }
+
+ @VisibleForTesting
+ static boolean isOneFingerPanningEnabledDefault(Context context) {
+ boolean oneFingerPanningDefaultValue;
+ try {
+ oneFingerPanningDefaultValue = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_enable_a11y_magnification_single_panning);
+ } catch (Resources.NotFoundException e) {
+ oneFingerPanningDefaultValue = false;
+ }
+ return oneFingerPanningDefaultValue;
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/AssociationStore.java b/services/companion/java/com/android/server/companion/AssociationStore.java
deleted file mode 100644
index 01905bb..0000000
--- a/services/companion/java/com/android/server/companion/AssociationStore.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.companion;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.UserIdInt;
-import android.companion.AssociationInfo;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * Interface for a store of {@link AssociationInfo}-s.
- */
-public interface AssociationStore {
-
- @IntDef(prefix = { "CHANGE_TYPE_" }, value = {
- CHANGE_TYPE_ADDED,
- CHANGE_TYPE_REMOVED,
- CHANGE_TYPE_UPDATED_ADDRESS_CHANGED,
- CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED,
- })
- @Retention(RetentionPolicy.SOURCE)
- @interface ChangeType {}
-
- int CHANGE_TYPE_ADDED = 0;
- int CHANGE_TYPE_REMOVED = 1;
- int CHANGE_TYPE_UPDATED_ADDRESS_CHANGED = 2;
- int CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED = 3;
-
- /** Listener for any changes to {@link AssociationInfo}-s. */
- interface OnChangeListener {
- default void onAssociationChanged(
- @ChangeType int changeType, AssociationInfo association) {
- switch (changeType) {
- case CHANGE_TYPE_ADDED:
- onAssociationAdded(association);
- break;
-
- case CHANGE_TYPE_REMOVED:
- onAssociationRemoved(association);
- break;
-
- case CHANGE_TYPE_UPDATED_ADDRESS_CHANGED:
- onAssociationUpdated(association, true);
- break;
-
- case CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED:
- onAssociationUpdated(association, false);
- break;
- }
- }
-
- default void onAssociationAdded(AssociationInfo association) {}
-
- default void onAssociationRemoved(AssociationInfo association) {}
-
- default void onAssociationUpdated(AssociationInfo association, boolean addressChanged) {}
- }
-
- /**
- * @return all CDM associations.
- */
- @NonNull
- Collection<AssociationInfo> getAssociations();
-
- /**
- * @return a {@link List} of associations that belong to the user.
- */
- @NonNull
- List<AssociationInfo> getAssociationsForUser(@UserIdInt int userId);
-
- /**
- * @return a {@link List} of association that belong to the package.
- */
- @NonNull
- List<AssociationInfo> getAssociationsForPackage(
- @UserIdInt int userId, @NonNull String packageName);
-
- /**
- * @return an association with the given address that belong to the given package if such an
- * association exists, otherwise {@code null}.
- */
- @Nullable
- AssociationInfo getAssociationsForPackageWithAddress(
- @UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress);
-
- /**
- * @return an association with the given id if such an association exists, otherwise
- * {@code null}.
- */
- @Nullable
- AssociationInfo getAssociationById(int id);
-
- /**
- * @return all associations with the given MAc address.
- */
- @NonNull
- List<AssociationInfo> getAssociationsByAddress(@NonNull String macAddress);
-
- /** Register a {@link OnChangeListener} */
- void registerListener(@NonNull OnChangeListener listener);
-
- /** Un-register a previously registered {@link OnChangeListener} */
- void unregisterListener(@NonNull OnChangeListener listener);
-
- /** @hide */
- static String changeTypeToString(@ChangeType int changeType) {
- switch (changeType) {
- case CHANGE_TYPE_ADDED:
- return "ASSOCIATION_ADDED";
-
- case CHANGE_TYPE_REMOVED:
- return "ASSOCIATION_REMOVED";
-
- case CHANGE_TYPE_UPDATED_ADDRESS_CHANGED:
- return "ASSOCIATION_UPDATED";
-
- case CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED:
- return "ASSOCIATION_UPDATED_ADDRESS_UNCHANGED";
-
- default:
- return "Unknown (" + changeType + ")";
- }
- }
-}
diff --git a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
index e4cc1f8..f2409fb 100644
--- a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
+++ b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
@@ -34,6 +34,9 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.CollectionUtils;
+import com.android.server.companion.association.AssociationDiskStore;
+import com.android.server.companion.association.AssociationRequestsProcessor;
+import com.android.server.companion.association.AssociationStore;
import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
import java.nio.ByteBuffer;
@@ -54,9 +57,9 @@
@NonNull
private final PackageManagerInternal mPackageManager;
@NonNull
- private final AssociationStoreImpl mAssociationStore;
+ private final AssociationStore mAssociationStore;
@NonNull
- private final PersistentDataStore mPersistentStore;
+ private final AssociationDiskStore mPersistentStore;
@NonNull
private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
@NonNull
@@ -71,8 +74,8 @@
new PerUserAssociationSet();
BackupRestoreProcessor(@NonNull CompanionDeviceManagerService service,
- @NonNull AssociationStoreImpl associationStore,
- @NonNull PersistentDataStore persistentStore,
+ @NonNull AssociationStore associationStore,
+ @NonNull AssociationDiskStore persistentStore,
@NonNull SystemDataTransferRequestStore systemDataTransferRequestStore,
@NonNull AssociationRequestsProcessor associationRequestsProcessor) {
mService = service;
diff --git a/services/companion/java/com/android/server/companion/CompanionApplicationController.java b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
index 559ebbc..c801489 100644
--- a/services/companion/java/com/android/server/companion/CompanionApplicationController.java
+++ b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
@@ -37,6 +37,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.infra.PerUser;
+import com.android.server.companion.association.AssociationStore;
import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
import com.android.server.companion.presence.ObservableUuid;
import com.android.server.companion.presence.ObservableUuidStore;
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 17ba073..e4a1048 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -37,7 +37,9 @@
import static com.android.internal.util.CollectionUtils.any;
import static com.android.internal.util.Preconditions.checkState;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
-import static com.android.server.companion.AssociationStore.CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED;
+import static com.android.server.companion.association.AssociationStore.CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED;
+import static com.android.server.companion.utils.AssociationUtils.getFirstAssociationIdForUser;
+import static com.android.server.companion.utils.AssociationUtils.getLastAssociationIdForUser;
import static com.android.server.companion.utils.PackageUtils.isRestrictedSettingsAllowed;
import static com.android.server.companion.utils.PackageUtils.enforceUsesCompanionDeviceFeature;
import static com.android.server.companion.utils.PackageUtils.getPackageInfo;
@@ -117,6 +119,11 @@
import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
+import com.android.server.companion.association.AssociationDiskStore;
+import com.android.server.companion.association.AssociationRequestsProcessor;
+import com.android.server.companion.association.AssociationRevokeProcessor;
+import com.android.server.companion.association.AssociationStore;
+import com.android.server.companion.association.InactiveAssociationsRemovalService;
import com.android.server.companion.datatransfer.SystemDataTransferProcessor;
import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
import com.android.server.companion.datatransfer.contextsync.CrossDeviceCall;
@@ -147,8 +154,6 @@
static final String TAG = "CDM_CompanionDeviceManagerService";
static final boolean DEBUG = false;
- /** Range of Association IDs allocated for a user. */
- private static final int ASSOCIATIONS_IDS_PER_USER_RANGE = 100000;
private static final long PAIR_WITHOUT_PROMPT_WINDOW_MS = 10 * 60 * 1000; // 10 min
private static final String PREF_FILE_NAME = "companion_device_preferences.xml";
@@ -160,10 +165,10 @@
private static final int MAX_CN_LENGTH = 500;
private final ActivityManager mActivityManager;
- private PersistentDataStore mPersistentStore;
+ private AssociationDiskStore mAssociationDiskStore;
private final PersistUserStateHandler mUserPersistenceHandler;
- private final AssociationStoreImpl mAssociationStore;
+ private final AssociationStore mAssociationStore;
private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
private AssociationRequestsProcessor mAssociationRequestsProcessor;
private SystemDataTransferProcessor mSystemDataTransferProcessor;
@@ -178,7 +183,7 @@
private final IAppOpsService mAppOpsManager;
private final PowerWhitelistManager mPowerWhitelistManager;
private final UserManager mUserManager;
- final PackageManagerInternal mPackageManagerInternal;
+ public final PackageManagerInternal mPackageManagerInternal;
private final PowerManagerInternal mPowerManagerInternal;
/**
@@ -210,7 +215,7 @@
mUserManager = context.getSystemService(UserManager.class);
mUserPersistenceHandler = new PersistUserStateHandler();
- mAssociationStore = new AssociationStoreImpl();
+ mAssociationStore = new AssociationStore();
mSystemDataTransferRequestStore = new SystemDataTransferRequestStore();
mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
@@ -221,11 +226,11 @@
public void onStart() {
final Context context = getContext();
- mPersistentStore = new PersistentDataStore();
+ mAssociationDiskStore = new AssociationDiskStore();
mAssociationRequestsProcessor = new AssociationRequestsProcessor(
/* cdmService */ this, mAssociationStore);
mBackupRestoreProcessor = new BackupRestoreProcessor(
- /* cdmService */ this, mAssociationStore, mPersistentStore,
+ /* cdmService */ this, mAssociationStore, mAssociationDiskStore,
mSystemDataTransferRequestStore, mAssociationRequestsProcessor);
mObservableUuidStore.getObservableUuidsForUser(getContext().getUserId());
@@ -264,10 +269,13 @@
void loadAssociationsFromDisk() {
final Set<AssociationInfo> allAssociations = new ArraySet<>();
synchronized (mPreviouslyUsedIds) {
+ List<Integer> userIds = new ArrayList<>();
+ for (UserInfo user : mUserManager.getAliveUsers()) {
+ userIds.add(user.id);
+ }
// The data is stored in DE directories, so we can read the data for all users now
// (which would not be possible if the data was stored to CE directories).
- mPersistentStore.readStateForUsers(
- mUserManager.getAliveUsers(), allAssociations, mPreviouslyUsedIds);
+ mAssociationDiskStore.readStateForUsers(userIds, allAssociations, mPreviouslyUsedIds);
}
final Set<AssociationInfo> activeAssociations =
@@ -291,7 +299,7 @@
}
}
- mAssociationStore.setAssociations(activeAssociations);
+ mAssociationStore.setAssociationsToCache(activeAssociations);
// IMPORTANT: only do this AFTER mAssociationStore.setAssociations(), because
// persistStateForUser() queries AssociationStore.
@@ -582,7 +590,7 @@
final Map<String, Set<Integer>> usedIdsForUser = getPreviouslyUsedIdsForUser(userId);
- mPersistentStore.persistStateForUser(userId, allAssociations, usedIdsForUser);
+ mAssociationDiskStore.persistStateForUser(userId, allAssociations, usedIdsForUser);
}
private void notifyListeners(
@@ -646,7 +654,8 @@
final List<AssociationInfo> associationsForPackage =
mAssociationStore.getAssociationsForPackage(userId, packageName);
for (AssociationInfo association : associationsForPackage) {
- updateSpecialAccessPermissionForAssociatedPackage(association);
+ updateSpecialAccessPermissionForAssociatedPackage(association.getUserId(),
+ association.getPackageName());
}
mCompanionAppController.onPackagesChanged(userId);
@@ -692,7 +701,7 @@
}
}
- class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub {
+ public class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub {
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
@@ -1338,7 +1347,10 @@
return usedIdsForPackage;
}
- int getNewAssociationIdForPackage(@UserIdInt int userId, @NonNull String packageName) {
+ /**
+ * Get a new association id for the package.
+ */
+ public int getNewAssociationIdForPackage(@UserIdInt int userId, @NonNull String packageName) {
synchronized (mPreviouslyUsedIds) {
// First: collect all IDs currently in use for this user's Associations.
final SparseBooleanArray usedIds = new SparseBooleanArray();
@@ -1383,9 +1395,12 @@
}
}
- void updateSpecialAccessPermissionForAssociatedPackage(AssociationInfo association) {
+ /**
+ * Update special access for the association's package
+ */
+ public void updateSpecialAccessPermissionForAssociatedPackage(int userId, String packageName) {
final PackageInfo packageInfo =
- getPackageInfo(getContext(), association.getUserId(), association.getPackageName());
+ getPackageInfo(getContext(), userId, packageName);
Binder.withCleanCallingIdentity(() -> updateSpecialAccessPermissionAsSystem(packageInfo));
}
@@ -1539,15 +1554,6 @@
}
};
- static int getFirstAssociationIdForUser(@UserIdInt int userId) {
- // We want the IDs to start from 1, not 0.
- return userId * ASSOCIATIONS_IDS_PER_USER_RANGE + 1;
- }
-
- static int getLastAssociationIdForUser(@UserIdInt int userId) {
- return (userId + 1) * ASSOCIATIONS_IDS_PER_USER_RANGE;
- }
-
private static Map<String, Set<Integer>> deepUnmodifiableCopy(Map<String, Set<Integer>> orig) {
final Map<String, Set<Integer>> copy = new HashMap<>();
@@ -1671,11 +1677,17 @@
}
}
- void postPersistUserState(@UserIdInt int userId) {
+ /**
+ * Persist associations
+ */
+ public void postPersistUserState(@UserIdInt int userId) {
mUserPersistenceHandler.postPersistUserState(userId);
}
- static class PerUserAssociationSet extends PerUser<Set<AssociationInfo>> {
+ /**
+ * Set to store associations
+ */
+ public static class PerUserAssociationSet extends PerUser<Set<AssociationInfo>> {
@Override
protected @NonNull Set<AssociationInfo> create(int userId) {
return new ArraySet<>();
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index 74b4cab..16877dc 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -32,6 +32,9 @@
import android.util.Base64;
import android.util.proto.ProtoOutputStream;
+import com.android.server.companion.association.AssociationRequestsProcessor;
+import com.android.server.companion.association.AssociationRevokeProcessor;
+import com.android.server.companion.association.AssociationStore;
import com.android.server.companion.datatransfer.SystemDataTransferProcessor;
import com.android.server.companion.datatransfer.contextsync.BitmapUtils;
import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncController;
@@ -47,7 +50,7 @@
private final CompanionDeviceManagerService mService;
private final AssociationRevokeProcessor mRevokeProcessor;
- private final AssociationStoreImpl mAssociationStore;
+ private final AssociationStore mAssociationStore;
private final CompanionDevicePresenceMonitor mDevicePresenceMonitor;
private final CompanionTransportManager mTransportManager;
@@ -56,7 +59,7 @@
private final BackupRestoreProcessor mBackupRestoreProcessor;
CompanionDeviceShellCommand(CompanionDeviceManagerService service,
- AssociationStoreImpl associationStore,
+ AssociationStore associationStore,
CompanionDevicePresenceMonitor devicePresenceMonitor,
CompanionTransportManager transportManager,
SystemDataTransferProcessor systemDataTransferProcessor,
diff --git a/services/companion/java/com/android/server/companion/PersistentDataStore.java b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
similarity index 92%
rename from services/companion/java/com/android/server/companion/PersistentDataStore.java
rename to services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
index 7527efb..75cb120 100644
--- a/services/companion/java/com/android/server/companion/PersistentDataStore.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.companion;
+package com.android.server.companion.association;
import static com.android.internal.util.CollectionUtils.forEach;
import static com.android.internal.util.XmlUtils.readBooleanAttribute;
@@ -25,8 +25,8 @@
import static com.android.internal.util.XmlUtils.writeIntAttribute;
import static com.android.internal.util.XmlUtils.writeLongAttribute;
import static com.android.internal.util.XmlUtils.writeStringAttribute;
-import static com.android.server.companion.CompanionDeviceManagerService.getFirstAssociationIdForUser;
-import static com.android.server.companion.CompanionDeviceManagerService.getLastAssociationIdForUser;
+import static com.android.server.companion.utils.AssociationUtils.getFirstAssociationIdForUser;
+import static com.android.server.companion.utils.AssociationUtils.getLastAssociationIdForUser;
import static com.android.server.companion.utils.DataStoreUtils.createStorageFileForUser;
import static com.android.server.companion.utils.DataStoreUtils.fileToByteArray;
import static com.android.server.companion.utils.DataStoreUtils.isEndOfTag;
@@ -38,12 +38,10 @@
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.companion.AssociationInfo;
-import android.content.pm.UserInfo;
import android.net.MacAddress;
import android.os.Environment;
import android.util.ArrayMap;
import android.util.AtomicFile;
-import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.util.Xml;
@@ -51,7 +49,6 @@
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
-import com.android.server.companion.utils.DataStoreUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -71,6 +68,8 @@
import java.util.concurrent.ConcurrentMap;
/**
+ * IMPORTANT: This class should NOT be directly used except {@link AssociationStore}
+ *
* The class responsible for persisting Association records and other related information (such as
* previously used IDs) to a disk, and reading the data back from the disk.
*
@@ -107,8 +106,6 @@
* Since Android T the data is stored to "companion_device_manager.xml" file in
* {@link Environment#getDataSystemDeDirectory(int) /data/system_de/}.
*
- * See {@link DataStoreUtils#getBaseStorageFileForUser(int, String)}
- *
* <p>
* Since Android T the data is stored using the v1 schema.
*
@@ -161,9 +158,8 @@
* }</pre>
*/
@SuppressLint("LongLogTag")
-final class PersistentDataStore {
- private static final String TAG = "CompanionDevice_PersistentDataStore";
- private static final boolean DEBUG = CompanionDeviceManagerService.DEBUG;
+public final class AssociationDiskStore {
+ private static final String TAG = "CompanionDevice_AssociationDiskStore";
private static final int CURRENT_PERSISTENCE_VERSION = 1;
@@ -200,11 +196,13 @@
private final @NonNull ConcurrentMap<Integer, AtomicFile> mUserIdToStorageFile =
new ConcurrentHashMap<>();
- void readStateForUsers(@NonNull List<UserInfo> users,
+ /**
+ * Read all associations for given users
+ */
+ public void readStateForUsers(@NonNull List<Integer> userIds,
@NonNull Set<AssociationInfo> allAssociationsOut,
@NonNull SparseArray<Map<String, Set<Integer>>> previouslyUsedIdsPerUserOut) {
- for (UserInfo user : users) {
- final int userId = user.id;
+ for (int userId : userIds) {
// Previously used IDs are stored in the "out" collection per-user.
final Map<String, Set<Integer>> previouslyUsedIds = new ArrayMap<>();
@@ -247,12 +245,11 @@
* @param associationsOut a container to read the {@link AssociationInfo}s "into".
* @param previouslyUsedIdsPerPackageOut a container to read the used IDs "into".
*/
- void readStateForUser(@UserIdInt int userId,
+ private void readStateForUser(@UserIdInt int userId,
@NonNull Collection<AssociationInfo> associationsOut,
@NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackageOut) {
Slog.i(TAG, "Reading associations for user " + userId + " from disk");
final AtomicFile file = getStorageFileForUser(userId);
- if (DEBUG) Log.d(TAG, " > File=" + file.getBaseFile().getPath());
// getStorageFileForUser() ALWAYS returns the SAME OBJECT, which allows us to synchronize
// accesses to the file on the file system using this AtomicFile object.
@@ -261,12 +258,8 @@
final AtomicFile readFrom;
final String rootTag;
if (!file.getBaseFile().exists()) {
- if (DEBUG) Log.d(TAG, " > File does not exist -> Try to read legacy file");
-
legacyBaseFile = getBaseLegacyStorageFileForUser(userId);
- if (DEBUG) Log.d(TAG, " > Legacy file=" + legacyBaseFile.getPath());
if (!legacyBaseFile.exists()) {
- if (DEBUG) Log.d(TAG, " > Legacy file does not exist -> Abort");
return;
}
@@ -277,27 +270,16 @@
rootTag = XML_TAG_STATE;
}
- if (DEBUG) Log.d(TAG, " > Reading associations...");
final int version = readStateFromFileLocked(userId, readFrom, rootTag,
associationsOut, previouslyUsedIdsPerPackageOut);
- if (DEBUG) {
- Log.d(TAG, " > Done reading: " + associationsOut);
- if (version < CURRENT_PERSISTENCE_VERSION) {
- Log.d(TAG, " > File used old format: v." + version + " -> Re-write");
- }
- }
if (legacyBaseFile != null || version < CURRENT_PERSISTENCE_VERSION) {
// The data is either in the legacy file or in the legacy format, or both.
// Save the data to right file in using the current format.
- if (DEBUG) {
- Log.d(TAG, " > Writing the data to " + file.getBaseFile().getPath());
- }
persistStateToFileLocked(file, associationsOut, previouslyUsedIdsPerPackageOut);
if (legacyBaseFile != null) {
// We saved the data to the right file, can delete the old file now.
- if (DEBUG) Log.d(TAG, " > Deleting legacy file");
legacyBaseFile.delete();
}
}
@@ -314,14 +296,12 @@
* @param associations a set of user's associations.
* @param previouslyUsedIdsPerPackage a set previously used Association IDs for the user.
*/
- void persistStateForUser(@UserIdInt int userId,
+ public void persistStateForUser(@UserIdInt int userId,
@NonNull Collection<AssociationInfo> associations,
@NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackage) {
Slog.i(TAG, "Writing associations for user " + userId + " to disk");
- if (DEBUG) Slog.d(TAG, " > " + associations);
final AtomicFile file = getStorageFileForUser(userId);
- if (DEBUG) Log.d(TAG, " > File=" + file.getBaseFile().getPath());
// getStorageFileForUser() ALWAYS returns the SAME OBJECT, which allows us to synchronize
// accesses to the file on the file system using this AtomicFile object.
synchronized (file) {
@@ -404,7 +384,10 @@
u -> createStorageFileForUser(userId, FILE_NAME));
}
- byte[] getBackupPayload(@UserIdInt int userId) {
+ /**
+ * Get associations backup payload from disk
+ */
+ public byte[] getBackupPayload(@UserIdInt int userId) {
Slog.i(TAG, "Fetching stored state data for user " + userId + " from disk");
final AtomicFile file = getStorageFileForUser(userId);
@@ -413,7 +396,10 @@
}
}
- void readStateFromPayload(byte[] payload, @UserIdInt int userId,
+ /**
+ * Convert payload to a set of associations
+ */
+ public void readStateFromPayload(byte[] payload, @UserIdInt int userId,
@NonNull Set<AssociationInfo> associationsOut,
@NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackageOut) {
try (ByteArrayInputStream in = new ByteArrayInputStream(payload)) {
@@ -615,7 +601,7 @@
macAddress, displayName, profile, null, selfManaged, notify,
revoked, pending, timeApproved, lastTimeConnected, systemDataSyncFlags);
} catch (Exception e) {
- if (DEBUG) Log.w(TAG, "Could not create AssociationInfo", e);
+ Slog.e(TAG, "Could not create AssociationInfo", e);
}
return associationInfo;
}
diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
similarity index 89%
rename from services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
rename to services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
index 1dab40e..29ec7c2 100644
--- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.companion;
+package com.android.server.companion.association;
import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
import static android.app.PendingIntent.FLAG_IMMUTABLE;
@@ -24,7 +24,6 @@
import static android.content.ComponentName.createRelative;
import static android.content.pm.PackageManager.FEATURE_WATCH;
-import static com.android.server.companion.CompanionDeviceManagerService.DEBUG;
import static com.android.server.companion.utils.MetricUtils.logCreateAssociation;
import static com.android.server.companion.utils.PackageUtils.enforceUsesCompanionDeviceFeature;
import static com.android.server.companion.utils.PermissionsUtils.enforcePermissionForCreatingAssociation;
@@ -59,6 +58,7 @@
import android.util.Slog;
import com.android.internal.R;
+import com.android.server.companion.CompanionDeviceManagerService;
import com.android.server.companion.utils.PackageUtils;
import java.util.List;
@@ -107,7 +107,7 @@
* ResultReceiver, MacAddress)
*/
@SuppressLint("LongLogTag")
-class AssociationRequestsProcessor {
+public class AssociationRequestsProcessor {
private static final String TAG = "CDM_AssociationRequestsProcessor";
// AssociationRequestsProcessor <-> UI
@@ -130,12 +130,12 @@
private final @NonNull Context mContext;
private final @NonNull CompanionDeviceManagerService mService;
private final @NonNull PackageManagerInternal mPackageManager;
- private final @NonNull AssociationStoreImpl mAssociationStore;
+ private final @NonNull AssociationStore mAssociationStore;
@NonNull
private final ComponentName mCompanionDeviceActivity;
- AssociationRequestsProcessor(@NonNull CompanionDeviceManagerService service,
- @NonNull AssociationStoreImpl associationStore) {
+ public AssociationRequestsProcessor(@NonNull CompanionDeviceManagerService service,
+ @NonNull AssociationStore associationStore) {
mContext = service.getContext();
mService = service;
mPackageManager = service.mPackageManagerInternal;
@@ -149,7 +149,7 @@
* Handle incoming {@link AssociationRequest}s, sent via
* {@link android.companion.ICompanionDeviceManager#associate(AssociationRequest, IAssociationRequestCallback, String, int)}
*/
- void processNewAssociationRequest(@NonNull AssociationRequest request,
+ public void processNewAssociationRequest(@NonNull AssociationRequest request,
@NonNull String packageName, @UserIdInt int userId,
@NonNull IAssociationRequestCallback callback) {
requireNonNull(request, "Request MUST NOT be null");
@@ -161,11 +161,8 @@
requireNonNull(callback, "Callback MUST NOT be null");
final int packageUid = mPackageManager.getPackageUid(packageName, 0, userId);
- if (DEBUG) {
- Slog.d(TAG, "processNewAssociationRequest() "
- + "request=" + request + ", "
- + "package=u" + userId + "/" + packageName + " (uid=" + packageUid + ")");
- }
+ Slog.d(TAG, "processNewAssociationRequest() " + "request=" + request + ", " + "package=u"
+ + userId + "/" + packageName + " (uid=" + packageUid + ")");
// 1. Enforce permissions and other requirements.
enforcePermissionForCreatingAssociation(mContext, request, packageUid);
@@ -223,7 +220,7 @@
/**
* Process another AssociationRequest in CompanionDeviceActivity to cancel current dialog.
*/
- PendingIntent buildAssociationCancellationIntent(@NonNull String packageName,
+ public PendingIntent buildAssociationCancellationIntent(@NonNull String packageName,
@UserIdInt int userId) {
requireNonNull(packageName, "Package name MUST NOT be null");
@@ -248,13 +245,6 @@
final int userId = request.getUserId();
final int packageUid = mPackageManager.getPackageUid(packageName, 0, userId);
- if (DEBUG) {
- Slog.d(TAG, "processAssociationRequestApproval()\n"
- + " package=u" + userId + "/" + packageName + " (uid=" + packageUid + ")\n"
- + " request=" + request + "\n"
- + " macAddress=" + macAddress + "\n");
- }
-
// 1. Need to check permissions again in case something changed, since we first received
// this request.
try {
@@ -288,6 +278,9 @@
}
}
+ /**
+ * Create an association.
+ */
public void createAssociation(@UserIdInt int userId, @NonNull String packageName,
@Nullable MacAddress macAddress, @Nullable CharSequence displayName,
@Nullable String deviceProfile, @Nullable AssociatedDevice associatedDevice,
@@ -309,6 +302,9 @@
// that there are other devices with the same profile, so the role holder won't be removed.
}
+ /**
+ * Grant a role if specified and add an association to store.
+ */
public void maybeGrantRoleAndStoreAssociation(@NonNull AssociationInfo association,
@Nullable IAssociationRequestCallback callback,
@Nullable ResultReceiver resultReceiver) {
@@ -331,6 +327,9 @@
});
}
+ /**
+ * Enable system data sync.
+ */
public void enableSystemDataSync(int associationId, int flags) {
AssociationInfo association = mAssociationStore.getAssociationById(associationId);
AssociationInfo updated = (new AssociationInfo.Builder(association))
@@ -338,6 +337,9 @@
mAssociationStore.updateAssociation(updated);
}
+ /**
+ * Disable system data sync.
+ */
public void disableSystemDataSync(int associationId, int flags) {
AssociationInfo association = mAssociationStore.getAssociationById(associationId);
AssociationInfo updated = (new AssociationInfo.Builder(association))
@@ -350,7 +352,8 @@
mAssociationStore.addAssociation(association);
- mService.updateSpecialAccessPermissionForAssociatedPackage(association);
+ mService.updateSpecialAccessPermissionForAssociatedPackage(association.getUserId(),
+ association.getPackageName());
logCreateAssociation(association.getDeviceProfile());
}
@@ -431,38 +434,37 @@
private final ResultReceiver mOnRequestConfirmationReceiver =
new ResultReceiver(Handler.getMain()) {
- @Override
- protected void onReceiveResult(int resultCode, Bundle data) {
- if (DEBUG) {
- Slog.d(TAG, "mOnRequestConfirmationReceiver.onReceiveResult() "
- + "code=" + resultCode + ", " + "data=" + data);
- }
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle data) {
+ if (resultCode != RESULT_CODE_ASSOCIATION_APPROVED) {
+ Slog.w(TAG, "Unknown result code:" + resultCode);
+ return;
+ }
- if (resultCode != RESULT_CODE_ASSOCIATION_APPROVED) {
- Slog.w(TAG, "Unknown result code:" + resultCode);
- return;
- }
+ final AssociationRequest request = data.getParcelable(EXTRA_ASSOCIATION_REQUEST,
+ android.companion.AssociationRequest.class);
+ final IAssociationRequestCallback callback = IAssociationRequestCallback.Stub
+ .asInterface(data.getBinder(EXTRA_APPLICATION_CALLBACK));
+ final ResultReceiver resultReceiver = data.getParcelable(EXTRA_RESULT_RECEIVER,
+ android.os.ResultReceiver.class);
- final AssociationRequest request = data.getParcelable(EXTRA_ASSOCIATION_REQUEST, android.companion.AssociationRequest.class);
- final IAssociationRequestCallback callback = IAssociationRequestCallback.Stub
- .asInterface(data.getBinder(EXTRA_APPLICATION_CALLBACK));
- final ResultReceiver resultReceiver = data.getParcelable(EXTRA_RESULT_RECEIVER, android.os.ResultReceiver.class);
+ requireNonNull(request);
+ requireNonNull(callback);
+ requireNonNull(resultReceiver);
- requireNonNull(request);
- requireNonNull(callback);
- requireNonNull(resultReceiver);
+ final MacAddress macAddress;
+ if (request.isSelfManaged()) {
+ macAddress = null;
+ } else {
+ macAddress = data.getParcelable(EXTRA_MAC_ADDRESS,
+ android.net.MacAddress.class);
+ requireNonNull(macAddress);
+ }
- final MacAddress macAddress;
- if (request.isSelfManaged()) {
- macAddress = null;
- } else {
- macAddress = data.getParcelable(EXTRA_MAC_ADDRESS, android.net.MacAddress.class);
- requireNonNull(macAddress);
- }
-
- processAssociationRequestApproval(request, callback, resultReceiver, macAddress);
- }
- };
+ processAssociationRequestApproval(request, callback, resultReceiver,
+ macAddress);
+ }
+ };
private boolean mayAssociateWithoutPrompt(@NonNull String packageName, @UserIdInt int userId) {
// Throttle frequent associations
diff --git a/services/companion/java/com/android/server/companion/AssociationRevokeProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRevokeProcessor.java
similarity index 95%
rename from services/companion/java/com/android/server/companion/AssociationRevokeProcessor.java
rename to services/companion/java/com/android/server/companion/association/AssociationRevokeProcessor.java
index 10963ea..490be0d 100644
--- a/services/companion/java/com/android/server/companion/AssociationRevokeProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRevokeProcessor.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.companion;
+package com.android.server.companion.association;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
@@ -38,6 +38,8 @@
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.server.companion.CompanionApplicationController;
+import com.android.server.companion.CompanionDeviceManagerService;
import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
@@ -55,7 +57,7 @@
private static final boolean DEBUG = false;
private final @NonNull Context mContext;
private final @NonNull CompanionDeviceManagerService mService;
- private final @NonNull AssociationStoreImpl mAssociationStore;
+ private final @NonNull AssociationStore mAssociationStore;
private final @NonNull PackageManagerInternal mPackageManagerInternal;
private final @NonNull CompanionDevicePresenceMonitor mDevicePresenceMonitor;
private final @NonNull SystemDataTransferRequestStore mSystemDataTransferRequestStore;
@@ -90,8 +92,8 @@
@GuardedBy("mRevokedAssociationsPendingRoleHolderRemoval")
private final Map<Integer, String> mUidsPendingRoleHolderRemoval = new HashMap<>();
- AssociationRevokeProcessor(@NonNull CompanionDeviceManagerService service,
- @NonNull AssociationStoreImpl associationStore,
+ public AssociationRevokeProcessor(@NonNull CompanionDeviceManagerService service,
+ @NonNull AssociationStore associationStore,
@NonNull PackageManagerInternal packageManager,
@NonNull CompanionDevicePresenceMonitor devicePresenceMonitor,
@NonNull CompanionApplicationController applicationController,
@@ -108,8 +110,11 @@
mSystemDataTransferRequestStore = systemDataTransferRequestStore;
}
+ /**
+ * Disassociate an association
+ */
// TODO: also revoke notification access
- void disassociateInternal(int associationId) {
+ public void disassociateInternal(int associationId) {
final AssociationInfo association = mAssociationStore.getAssociationById(associationId);
final int userId = association.getUserId();
final String packageName = association.getPackageName();
@@ -168,7 +173,7 @@
* {@code RoleManager.removeRoleHolderAsUser()} will kill the application's process,
* which would lead to the poor UX, hence need to try later.
*/
- boolean maybeRemoveRoleHolderForAssociation(@NonNull AssociationInfo association) {
+ public boolean maybeRemoveRoleHolderForAssociation(@NonNull AssociationInfo association) {
if (DEBUG) Log.d(TAG, "maybeRemoveRoleHolderForAssociation() association=" + association);
final String deviceProfile = association.getDeviceProfile();
@@ -208,15 +213,6 @@
return true;
}
- @SuppressLint("MissingPermission")
- private int getPackageProcessImportance(@UserIdInt int userId, @NonNull String packageName) {
- return Binder.withCleanCallingIdentity(() -> {
- final int uid =
- mPackageManagerInternal.getPackageUid(packageName, /* flags */0, userId);
- return mActivityManager.getUidImportance(uid);
- });
- }
-
/**
* Set revoked flag for active association and add the revoked association and the uid into
* the caches.
@@ -225,7 +221,7 @@
* @see #mUidsPendingRoleHolderRemoval
* @see OnPackageVisibilityChangeListener
*/
- void addToPendingRoleHolderRemoval(@NonNull AssociationInfo association) {
+ public void addToPendingRoleHolderRemoval(@NonNull AssociationInfo association) {
// First: set revoked flag
association = (new AssociationInfo.Builder(association)).setRevoked(true).build();
final String packageName = association.getPackageName();
@@ -247,6 +243,28 @@
}
/**
+ * @return a copy of the revoked associations set (safeguarding against
+ * {@code ConcurrentModificationException}-s).
+ */
+ @NonNull
+ public Set<AssociationInfo> getPendingRoleHolderRemovalAssociationsForUser(
+ @UserIdInt int userId) {
+ synchronized (mRevokedAssociationsPendingRoleHolderRemoval) {
+ // Return a copy.
+ return new ArraySet<>(mRevokedAssociationsPendingRoleHolderRemoval.forUser(userId));
+ }
+ }
+
+ @SuppressLint("MissingPermission")
+ private int getPackageProcessImportance(@UserIdInt int userId, @NonNull String packageName) {
+ return Binder.withCleanCallingIdentity(() -> {
+ final int uid =
+ mPackageManagerInternal.getPackageUid(packageName, /* flags */0, userId);
+ return mActivityManager.getUidImportance(uid);
+ });
+ }
+
+ /**
* Remove the revoked association from the cache and also remove the uid from the map if
* there are other associations with the same package still pending for role holder removal.
*
@@ -279,18 +297,6 @@
}
}
- /**
- * @return a copy of the revoked associations set (safeguarding against
- * {@code ConcurrentModificationException}-s).
- */
- @NonNull Set<AssociationInfo> getPendingRoleHolderRemovalAssociationsForUser(
- @UserIdInt int userId) {
- synchronized (mRevokedAssociationsPendingRoleHolderRemoval) {
- // Return a copy.
- return new ArraySet<>(mRevokedAssociationsPendingRoleHolderRemoval.forUser(userId));
- }
- }
-
private String getPackageNameByUid(int uid) {
synchronized (mRevokedAssociationsPendingRoleHolderRemoval) {
return mUidsPendingRoleHolderRemoval.get(uid);
diff --git a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java b/services/companion/java/com/android/server/companion/association/AssociationStore.java
similarity index 68%
rename from services/companion/java/com/android/server/companion/AssociationStoreImpl.java
rename to services/companion/java/com/android/server/companion/association/AssociationStore.java
index 8c6ad3b..2f94bde 100644
--- a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationStore.java
@@ -14,15 +14,15 @@
* limitations under the License.
*/
-package com.android.server.companion;
+package com.android.server.companion.association;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.companion.AssociationInfo;
import android.net.MacAddress;
-import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
@@ -30,6 +30,8 @@
import com.android.internal.util.CollectionUtils;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -40,24 +42,69 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
-import java.util.StringJoiner;
/**
- * Implementation of the {@link AssociationStore}, with addition of the methods for modification.
- * <ul>
- * <li> {@link #addAssociation(AssociationInfo)}
- * <li> {@link #removeAssociation(int)}
- * <li> {@link #updateAssociation(AssociationInfo)}
- * </ul>
- *
- * The class has package-private access level, and instances of the class should only be created by
- * the {@link CompanionDeviceManagerService}.
- * Other system component (both inside and outside if the com.android.server.companion package)
- * should use public {@link AssociationStore} interface.
+ * Association store for CRUD.
*/
@SuppressLint("LongLogTag")
-class AssociationStoreImpl implements AssociationStore {
- private static final boolean DEBUG = false;
+public class AssociationStore {
+
+ @IntDef(prefix = { "CHANGE_TYPE_" }, value = {
+ CHANGE_TYPE_ADDED,
+ CHANGE_TYPE_REMOVED,
+ CHANGE_TYPE_UPDATED_ADDRESS_CHANGED,
+ CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ChangeType {}
+
+ public static final int CHANGE_TYPE_ADDED = 0;
+ public static final int CHANGE_TYPE_REMOVED = 1;
+ public static final int CHANGE_TYPE_UPDATED_ADDRESS_CHANGED = 2;
+ public static final int CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED = 3;
+
+ /** Listener for any changes to associations. */
+ public interface OnChangeListener {
+ /**
+ * Called when there are association changes.
+ */
+ default void onAssociationChanged(
+ @AssociationStore.ChangeType int changeType, AssociationInfo association) {
+ switch (changeType) {
+ case CHANGE_TYPE_ADDED:
+ onAssociationAdded(association);
+ break;
+
+ case CHANGE_TYPE_REMOVED:
+ onAssociationRemoved(association);
+ break;
+
+ case CHANGE_TYPE_UPDATED_ADDRESS_CHANGED:
+ onAssociationUpdated(association, true);
+ break;
+
+ case CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED:
+ onAssociationUpdated(association, false);
+ break;
+ }
+ }
+
+ /**
+ * Called when an association is added.
+ */
+ default void onAssociationAdded(AssociationInfo association) {}
+
+ /**
+ * Called when an association is removed.
+ */
+ default void onAssociationRemoved(AssociationInfo association) {}
+
+ /**
+ * Called when an association is updated.
+ */
+ default void onAssociationUpdated(AssociationInfo association, boolean addressChanged) {}
+ }
+
private static final String TAG = "CDM_AssociationStore";
private final Object mLock = new Object();
@@ -72,17 +119,17 @@
@GuardedBy("mListeners")
private final Set<OnChangeListener> mListeners = new LinkedHashSet<>();
- void addAssociation(@NonNull AssociationInfo association) {
+ /**
+ * Add an association.
+ */
+ public void addAssociation(@NonNull AssociationInfo association) {
+ Slog.i(TAG, "Adding new association=" + association);
+
// Validity check first.
checkNotRevoked(association);
final int id = association.getId();
- if (DEBUG) {
- Log.i(TAG, "addAssociation() " + association.toShortString());
- Log.d(TAG, " association=" + association);
- }
-
synchronized (mLock) {
if (mIdMap.containsKey(id)) {
Slog.e(TAG, "Association with id " + id + " already exists.");
@@ -96,34 +143,34 @@
}
invalidateCacheForUserLocked(association.getUserId());
+
+ Slog.i(TAG, "Done adding new association.");
}
broadcastChange(CHANGE_TYPE_ADDED, association);
}
- void updateAssociation(@NonNull AssociationInfo updated) {
+ /**
+ * Update an association.
+ */
+ public void updateAssociation(@NonNull AssociationInfo updated) {
+ Slog.i(TAG, "Updating new association=" + updated);
// Validity check first.
checkNotRevoked(updated);
final int id = updated.getId();
- if (DEBUG) {
- Log.i(TAG, "updateAssociation() " + updated.toShortString());
- Log.d(TAG, " updated=" + updated);
- }
-
final AssociationInfo current;
final boolean macAddressChanged;
synchronized (mLock) {
current = mIdMap.get(id);
if (current == null) {
- if (DEBUG) Log.w(TAG, "Association with id " + id + " does not exist.");
+ Slog.w(TAG, "Can't update association. It does not exist.");
return;
}
- if (DEBUG) Log.d(TAG, " current=" + current);
if (current.equals(updated)) {
- if (DEBUG) Log.w(TAG, " No changes.");
+ Slog.w(TAG, "Association is the same.");
return;
}
@@ -144,6 +191,7 @@
mAddressMap.computeIfAbsent(updatedAddress, it -> new HashSet<>()).add(id);
}
}
+ Slog.i(TAG, "Done updating association.");
}
final int changeType = macAddressChanged ? CHANGE_TYPE_UPDATED_ADDRESS_CHANGED
@@ -151,21 +199,19 @@
broadcastChange(changeType, updated);
}
- void removeAssociation(int id) {
- if (DEBUG) Log.i(TAG, "removeAssociation() id=" + id);
+ /**
+ * Remove an association
+ */
+ public void removeAssociation(int id) {
+ Slog.i(TAG, "Removing association id=" + id);
final AssociationInfo association;
synchronized (mLock) {
association = mIdMap.remove(id);
if (association == null) {
- if (DEBUG) Log.w(TAG, "Association with id " + id + " is not stored.");
+ Slog.w(TAG, "Can't remove association. It does not exist.");
return;
- } else {
- if (DEBUG) {
- Log.i(TAG, "removed " + association.toShortString());
- Log.d(TAG, " association=" + association);
- }
}
final MacAddress macAddress = association.getDeviceMacAddress();
@@ -174,6 +220,8 @@
}
invalidateCacheForUserLocked(association.getUserId());
+
+ Slog.i(TAG, "Done removing association.");
}
broadcastChange(CHANGE_TYPE_REMOVED, association);
@@ -195,12 +243,18 @@
}
}
+ /**
+ * Get associations for the user.
+ */
public @NonNull List<AssociationInfo> getAssociationsForUser(@UserIdInt int userId) {
synchronized (mLock) {
return getAssociationsForUserLocked(userId);
}
}
+ /**
+ * Get associations for the package
+ */
public @NonNull List<AssociationInfo> getAssociationsForPackage(
@UserIdInt int userId, @NonNull String packageName) {
final List<AssociationInfo> associationsForUser = getAssociationsForUser(userId);
@@ -210,6 +264,9 @@
return Collections.unmodifiableList(associationsForPackage);
}
+ /**
+ * Get associations by mac address for the package.
+ */
public @Nullable AssociationInfo getAssociationsForPackageWithAddress(
@UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress) {
final List<AssociationInfo> associations = getAssociationsByAddress(macAddress);
@@ -217,13 +274,20 @@
it -> it.belongsToPackage(userId, packageName));
}
+ /**
+ * Get association by id.
+ */
public @Nullable AssociationInfo getAssociationById(int id) {
synchronized (mLock) {
return mIdMap.get(id);
}
}
- public @NonNull List<AssociationInfo> getAssociationsByAddress(@NonNull String macAddress) {
+ /**
+ * Get associations by mac address.
+ */
+ @NonNull
+ public List<AssociationInfo> getAssociationsByAddress(@NonNull String macAddress) {
final MacAddress address = MacAddress.fromString(macAddress);
synchronized (mLock) {
@@ -240,7 +304,8 @@
}
@GuardedBy("mLock")
- private @NonNull List<AssociationInfo> getAssociationsForUserLocked(@UserIdInt int userId) {
+ @NonNull
+ private List<AssociationInfo> getAssociationsForUserLocked(@UserIdInt int userId) {
final List<AssociationInfo> cached = mCachedPerUser.get(userId);
if (cached != null) {
return cached;
@@ -262,12 +327,18 @@
mCachedPerUser.delete(userId);
}
+ /**
+ * Register a listener for association changes.
+ */
public void registerListener(@NonNull OnChangeListener listener) {
synchronized (mListeners) {
mListeners.add(listener);
}
}
+ /**
+ * Unregister a listener previously registered for association changes.
+ */
public void unregisterListener(@NonNull OnChangeListener listener) {
synchronized (mListeners) {
mListeners.remove(listener);
@@ -297,43 +368,30 @@
}
}
- void setAssociations(Collection<AssociationInfo> allAssociations) {
+ /**
+ * Set associations to cache. It will clear the existing cache.
+ */
+ public void setAssociationsToCache(Collection<AssociationInfo> associations) {
// Validity check first.
- allAssociations.forEach(AssociationStoreImpl::checkNotRevoked);
+ associations.forEach(AssociationStore::checkNotRevoked);
- if (DEBUG) {
- Log.i(TAG, "setAssociations() n=" + allAssociations.size());
- final StringJoiner stringJoiner = new StringJoiner(", ");
- allAssociations.forEach(assoc -> stringJoiner.add(assoc.toShortString()));
- Log.v(TAG, " associations=" + stringJoiner);
- }
synchronized (mLock) {
- setAssociationsLocked(allAssociations);
- }
- }
+ mIdMap.clear();
+ mAddressMap.clear();
+ mCachedPerUser.clear();
- @GuardedBy("mLock")
- private void setAssociationsLocked(Collection<AssociationInfo> associations) {
- clearLocked();
+ for (AssociationInfo association : associations) {
+ final int id = association.getId();
+ mIdMap.put(id, association);
- for (AssociationInfo association : associations) {
- final int id = association.getId();
- mIdMap.put(id, association);
-
- final MacAddress address = association.getDeviceMacAddress();
- if (address != null) {
- mAddressMap.computeIfAbsent(address, it -> new HashSet<>()).add(id);
+ final MacAddress address = association.getDeviceMacAddress();
+ if (address != null) {
+ mAddressMap.computeIfAbsent(address, it -> new HashSet<>()).add(id);
+ }
}
}
}
- @GuardedBy("mLock")
- private void clearLocked() {
- mIdMap.clear();
- mAddressMap.clear();
- mCachedPerUser.clear();
- }
-
private static void checkNotRevoked(@NonNull AssociationInfo association) {
if (association.isRevoked()) {
throw new IllegalArgumentException(
diff --git a/services/companion/java/com/android/server/companion/InactiveAssociationsRemovalService.java b/services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java
similarity index 84%
rename from services/companion/java/com/android/server/companion/InactiveAssociationsRemovalService.java
rename to services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java
index aac628c..894c49a 100644
--- a/services/companion/java/com/android/server/companion/InactiveAssociationsRemovalService.java
+++ b/services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java
@@ -14,9 +14,7 @@
* limitations under the License.
*/
-package com.android.server.companion;
-
-import static com.android.server.companion.CompanionDeviceManagerService.TAG;
+package com.android.server.companion.association;
import static java.util.concurrent.TimeUnit.DAYS;
@@ -29,13 +27,17 @@
import android.util.Slog;
import com.android.server.LocalServices;
+import com.android.server.companion.CompanionDeviceManagerServiceInternal;
/**
- * A Job Service responsible for clean up the Association.
+ * A Job Service responsible for clean up idle self-managed associations.
+ *
* The job will be executed only if the device is charging and in idle mode due to the application
- * will be killed if association/role are revoked.
+ * will be killed if association/role are revoked. See {@link AssociationRevokeProcessor}
*/
public class InactiveAssociationsRemovalService extends JobService {
+
+ private static final String TAG = "CDM_InactiveAssociationsRemovalService";
private static final String JOB_NAMESPACE = "companion";
private static final int JOB_ID = 1;
private static final long ONE_DAY_INTERVAL = DAYS.toMillis(1);
@@ -60,7 +62,10 @@
return false;
}
- static void schedule(Context context) {
+ /**
+ * Schedule this job.
+ */
+ public static void schedule(Context context) {
Slog.i(TAG, "Scheduling the Association Removal job");
final JobScheduler jobScheduler =
context.getSystemService(JobScheduler.class).forNamespace(JOB_NAMESPACE);
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
index 74236a4..a08e0da 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -52,8 +52,8 @@
import android.util.Slog;
import com.android.internal.R;
-import com.android.server.companion.AssociationStore;
import com.android.server.companion.CompanionDeviceManagerService;
+import com.android.server.companion.association.AssociationStore;
import com.android.server.companion.transport.CompanionTransportManager;
import com.android.server.companion.utils.PackageUtils;
import com.android.server.companion.utils.PermissionsUtils;
diff --git a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
index 2899c05..99466a9 100644
--- a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
+++ b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
@@ -59,8 +59,8 @@
import android.util.Log;
import android.util.Slog;
-import com.android.server.companion.AssociationStore;
-import com.android.server.companion.AssociationStore.ChangeType;
+import com.android.server.companion.association.AssociationStore;
+import com.android.server.companion.association.AssociationStore.ChangeType;
import java.util.ArrayList;
import java.util.Arrays;
diff --git a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
index 0287f62..4da3f9b 100644
--- a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
+++ b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
@@ -39,7 +39,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
-import com.android.server.companion.AssociationStore;
+import com.android.server.companion.association.AssociationStore;
import java.util.Arrays;
import java.util.Collections;
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
index 3da9693..37bbb93 100644
--- a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
+++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
@@ -44,7 +44,7 @@
import android.util.SparseBooleanArray;
import com.android.internal.annotations.GuardedBy;
-import com.android.server.companion.AssociationStore;
+import com.android.server.companion.association.AssociationStore;
import java.io.PrintWriter;
import java.util.HashSet;
diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
index 3861f99..6dd14ac 100644
--- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
+++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
@@ -32,7 +32,7 @@
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
-import com.android.server.companion.AssociationStore;
+import com.android.server.companion.association.AssociationStore;
import java.io.FileDescriptor;
import java.io.IOException;
diff --git a/services/companion/java/com/android/server/companion/utils/AssociationUtils.java b/services/companion/java/com/android/server/companion/utils/AssociationUtils.java
new file mode 100644
index 0000000..e4d9641
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/utils/AssociationUtils.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.utils;
+
+import android.annotation.UserIdInt;
+
+public final class AssociationUtils {
+
+ /** Range of Association IDs allocated for a user. */
+ private static final int ASSOCIATIONS_IDS_PER_USER_RANGE = 100000;
+
+ /**
+ * Get the left boundary of the association id range for the user.
+ */
+ public static int getFirstAssociationIdForUser(@UserIdInt int userId) {
+ // We want the IDs to start from 1, not 0.
+ return userId * ASSOCIATIONS_IDS_PER_USER_RANGE + 1;
+ }
+
+ /**
+ * Get the right boundary of the association id range for the user.
+ */
+ public static int getLastAssociationIdForUser(@UserIdInt int userId) {
+ return (userId + 1) * ASSOCIATIONS_IDS_PER_USER_RANGE;
+ }
+
+ private AssociationUtils() {}
+}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 4443f90..b1672ed 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -210,7 +210,7 @@
mActivityListener.onTopActivityChanged(displayId, topActivity,
UserHandle.USER_NULL);
} catch (RemoteException e) {
- Slog.w(TAG, "Unable to call mActivityListener", e);
+ Slog.w(TAG, "Unable to call mActivityListener for display: " + displayId, e);
}
}
@@ -220,7 +220,7 @@
try {
mActivityListener.onTopActivityChanged(displayId, topActivity, userId);
} catch (RemoteException e) {
- Slog.w(TAG, "Unable to call mActivityListener", e);
+ Slog.w(TAG, "Unable to call mActivityListener for display: " + displayId, e);
}
}
@@ -229,7 +229,7 @@
try {
mActivityListener.onDisplayEmpty(displayId);
} catch (RemoteException e) {
- Slog.w(TAG, "Unable to call mActivityListener", e);
+ Slog.w(TAG, "Unable to call mActivityListener for display: " + displayId, e);
}
}
};
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index e1d7be1..1a3ef73 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -264,8 +264,8 @@
final ArrayMap<String, ArraySet<String>> mAllowIgnoreLocationSettings = new ArrayMap<>();
// These are the packages that are allow-listed to be able to access camera when
- // the camera privacy state is for driver assistance apps only.
- final ArrayMap<String, Boolean> mAllowlistCameraPrivacy = new ArrayMap<>();
+ // the camera privacy state is enabled.
+ final ArraySet<String> mAllowlistCameraPrivacy = new ArraySet<>();
// These are the action strings of broadcasts which are whitelisted to
// be delivered anonymously even to apps which target O+.
@@ -489,7 +489,7 @@
return mAllowedAssociations;
}
- public ArrayMap<String, Boolean> getCameraPrivacyAllowlist() {
+ public ArraySet<String> getCameraPrivacyAllowlist() {
return mAllowlistCameraPrivacy;
}
@@ -1076,13 +1076,11 @@
case "camera-privacy-allowlisted-app" : {
if (allowOverrideAppRestrictions) {
String pkgname = parser.getAttributeValue(null, "package");
- boolean isMandatory = XmlUtils.readBooleanAttribute(
- parser, "mandatory", false);
if (pkgname == null) {
Slog.w(TAG, "<" + name + "> without package in "
+ permFile + " at " + parser.getPositionDescription());
} else {
- mAllowlistCameraPrivacy.put(pkgname, isMandatory);
+ mAllowlistCameraPrivacy.add(pkgname);
}
} else {
logNotAllowedInPartition(name, permFile, parser);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 663ba8a..cfe1e18 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -4779,36 +4779,47 @@
// being bound to an application.
thread.runIsolatedEntryPoint(
app.getIsolatedEntryPoint(), app.getIsolatedEntryPointArgs());
- } else if (instr2 != null) {
- thread.bindApplication(processName, appInfo,
- app.sdkSandboxClientAppVolumeUuid, app.sdkSandboxClientAppPackage,
- instr2.mIsSdkInSandbox,
- providerList,
- instr2.mClass,
- profilerInfo, instr2.mArguments,
- instr2.mWatcher,
- instr2.mUiAutomationConnection, testMode,
- mBinderTransactionTrackingEnabled, enableTrackAllocation,
- isRestrictedBackupMode || !normalMode, app.isPersistent(),
- new Configuration(app.getWindowProcessController().getConfiguration()),
- app.getCompat(), getCommonServicesLocked(app.isolated),
- mCoreSettingsObserver.getCoreSettingsLocked(),
- buildSerial, autofillOptions, contentCaptureOptions,
- app.getDisabledCompatChanges(), serializedSystemFontMap,
- app.getStartElapsedTime(), app.getStartUptime());
} else {
- thread.bindApplication(processName, appInfo,
- app.sdkSandboxClientAppVolumeUuid, app.sdkSandboxClientAppPackage,
- /* isSdkInSandbox= */ false,
- providerList, null, profilerInfo, null, null, null, testMode,
- mBinderTransactionTrackingEnabled, enableTrackAllocation,
- isRestrictedBackupMode || !normalMode, app.isPersistent(),
+ boolean isSdkInSandbox = false;
+ ComponentName instrumentationName = null;
+ Bundle instrumentationArgs = null;
+ IInstrumentationWatcher instrumentationWatcher = null;
+ IUiAutomationConnection instrumentationUiConnection = null;
+ if (instr2 != null) {
+ isSdkInSandbox = instr2.mIsSdkInSandbox;
+ instrumentationName = instr2.mClass;
+ instrumentationArgs = instr2.mArguments;
+ instrumentationWatcher = instr2.mWatcher;
+ instrumentationUiConnection = instr2.mUiAutomationConnection;
+ }
+ thread.bindApplication(
+ processName,
+ appInfo,
+ app.sdkSandboxClientAppVolumeUuid,
+ app.sdkSandboxClientAppPackage,
+ isSdkInSandbox,
+ providerList,
+ instrumentationName,
+ profilerInfo,
+ instrumentationArgs,
+ instrumentationWatcher,
+ instrumentationUiConnection,
+ testMode,
+ mBinderTransactionTrackingEnabled,
+ enableTrackAllocation,
+ isRestrictedBackupMode || !normalMode,
+ app.isPersistent(),
new Configuration(app.getWindowProcessController().getConfiguration()),
- app.getCompat(), getCommonServicesLocked(app.isolated),
+ app.getCompat(),
+ getCommonServicesLocked(app.isolated),
mCoreSettingsObserver.getCoreSettingsLocked(),
- buildSerial, autofillOptions, contentCaptureOptions,
- app.getDisabledCompatChanges(), serializedSystemFontMap,
- app.getStartElapsedTime(), app.getStartUptime());
+ buildSerial,
+ autofillOptions,
+ contentCaptureOptions,
+ app.getDisabledCompatChanges(),
+ serializedSystemFontMap,
+ app.getStartElapsedTime(),
+ app.getStartUptime());
}
Message msg = mHandler.obtainMessage(BIND_APPLICATION_TIMEOUT_SOFT_MSG);
@@ -9363,7 +9374,9 @@
sb.append("Animations-Running: ").append(info.numAnimationsRunning).append("\n");
}
if (info.broadcastIntentAction != null) {
- sb.append("Broadcast-Intent-Action: ").append(info.broadcastIntentAction).append("\n");
+ sb.append("Broadcast-Intent-Action: ")
+ .append(info.broadcastIntentAction)
+ .append("\n");
}
if (info.durationMillis != -1) {
sb.append("Duration-Millis: ").append(info.durationMillis).append("\n");
@@ -9723,82 +9736,126 @@
// If process is null, we are being called from some internal code
// and may be about to die -- run this synchronously.
final boolean runSynchronously = process == null;
- Thread worker = new Thread("Error dump: " + dropboxTag) {
- @Override
- public void run() {
- if (report != null) {
- sb.append(report);
- }
-
- String logcatSetting = Settings.Global.ERROR_LOGCAT_PREFIX + dropboxTag;
- String maxBytesSetting = Settings.Global.MAX_ERROR_BYTES_PREFIX + dropboxTag;
- int lines = Build.IS_USER
- ? 0
- : Settings.Global.getInt(mContext.getContentResolver(), logcatSetting, 0);
- int dropboxMaxSize = Settings.Global.getInt(
- mContext.getContentResolver(), maxBytesSetting, DROPBOX_DEFAULT_MAX_SIZE);
-
- if (dataFile != null) {
- // Attach the stack traces file to the report so collectors can load them
- // by file if they have access.
- sb.append(DATA_FILE_PATH_HEADER)
- .append(dataFile.getAbsolutePath()).append('\n');
-
- int maxDataFileSize = dropboxMaxSize
- - sb.length()
- - lines * RESERVED_BYTES_PER_LOGCAT_LINE
- - DATA_FILE_PATH_FOOTER.length();
-
- if (maxDataFileSize > 0) {
- // Inline dataFile contents if there is room.
- try {
- sb.append(FileUtils.readTextFile(dataFile, maxDataFileSize,
- "\n\n[[TRUNCATED]]\n"));
- } catch (IOException e) {
- Slog.e(TAG, "Error reading " + dataFile, e);
+ Thread worker =
+ new Thread("Error dump: " + dropboxTag) {
+ @Override
+ public void run() {
+ if (report != null) {
+ sb.append(report);
}
+
+ String logcatSetting = Settings.Global.ERROR_LOGCAT_PREFIX + dropboxTag;
+ String maxBytesSetting =
+ Settings.Global.MAX_ERROR_BYTES_PREFIX + dropboxTag;
+ int lines =
+ Build.IS_USER
+ ? 0
+ : Settings.Global.getInt(
+ mContext.getContentResolver(), logcatSetting, 0);
+ int dropboxMaxSize =
+ Settings.Global.getInt(
+ mContext.getContentResolver(),
+ maxBytesSetting,
+ DROPBOX_DEFAULT_MAX_SIZE);
+
+ if (dataFile != null) {
+ // Attach the stack traces file to the report so collectors can load
+ // them
+ // by file if they have access.
+ sb.append(DATA_FILE_PATH_HEADER)
+ .append(dataFile.getAbsolutePath())
+ .append('\n');
+
+ int maxDataFileSize =
+ dropboxMaxSize
+ - sb.length()
+ - lines * RESERVED_BYTES_PER_LOGCAT_LINE
+ - DATA_FILE_PATH_FOOTER.length();
+
+ if (maxDataFileSize > 0) {
+ // Inline dataFile contents if there is room.
+ try {
+ sb.append(
+ FileUtils.readTextFile(
+ dataFile,
+ maxDataFileSize,
+ "\n\n[[TRUNCATED]]\n"));
+ } catch (IOException e) {
+ Slog.e(TAG, "Error reading " + dataFile, e);
+ }
+ }
+
+ // Always append the footer, even there wasn't enough space to inline
+ // the
+ // dataFile contents.
+ sb.append(DATA_FILE_PATH_FOOTER);
+ }
+
+ if (crashInfo != null && crashInfo.stackTrace != null) {
+ sb.append(crashInfo.stackTrace);
+ }
+
+ if (lines > 0 && !runSynchronously) {
+ sb.append("\n");
+
+ InputStreamReader input = null;
+ try {
+ java.lang.Process logcat =
+ new ProcessBuilder(
+ // Time out after 10s of inactivity, but
+ // kill logcat with SEGV
+ // so we can investigate why it didn't
+ // finish.
+ "/system/bin/timeout",
+ "-i",
+ "-s",
+ "SEGV",
+ "10s",
+ // Merge several logcat streams, and take
+ // the last N lines.
+ "/system/bin/logcat",
+ "-v",
+ "threadtime",
+ "-b",
+ "events",
+ "-b",
+ "system",
+ "-b",
+ "main",
+ "-b",
+ "crash",
+ "-t",
+ String.valueOf(lines))
+ .redirectErrorStream(true)
+ .start();
+
+ try {
+ logcat.getOutputStream().close();
+ } catch (IOException e) {
+ }
+ try {
+ logcat.getErrorStream().close();
+ } catch (IOException e) {
+ }
+ input = new InputStreamReader(logcat.getInputStream());
+
+ int num;
+ char[] buf = new char[8192];
+ while ((num = input.read(buf)) > 0) sb.append(buf, 0, num);
+ } catch (IOException e) {
+ Slog.e(TAG, "Error running logcat", e);
+ } finally {
+ if (input != null)
+ try {
+ input.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ dbox.addText(dropboxTag, sb.toString());
}
-
- // Always append the footer, even there wasn't enough space to inline the
- // dataFile contents.
- sb.append(DATA_FILE_PATH_FOOTER);
- }
-
- if (crashInfo != null && crashInfo.stackTrace != null) {
- sb.append(crashInfo.stackTrace);
- }
-
- if (lines > 0 && !runSynchronously) {
- sb.append("\n");
-
- InputStreamReader input = null;
- try {
- java.lang.Process logcat = new ProcessBuilder(
- // Time out after 10s of inactivity, but kill logcat with SEGV
- // so we can investigate why it didn't finish.
- "/system/bin/timeout", "-i", "-s", "SEGV", "10s",
- // Merge several logcat streams, and take the last N lines.
- "/system/bin/logcat", "-v", "threadtime", "-b", "events", "-b", "system",
- "-b", "main", "-b", "crash", "-t", String.valueOf(lines))
- .redirectErrorStream(true).start();
-
- try { logcat.getOutputStream().close(); } catch (IOException e) {}
- try { logcat.getErrorStream().close(); } catch (IOException e) {}
- input = new InputStreamReader(logcat.getInputStream());
-
- int num;
- char[] buf = new char[8192];
- while ((num = input.read(buf)) > 0) sb.append(buf, 0, num);
- } catch (IOException e) {
- Slog.e(TAG, "Error running logcat", e);
- } finally {
- if (input != null) try { input.close(); } catch (IOException e) {}
- }
- }
-
- dbox.addText(dropboxTag, sb.toString());
- }
- };
+ };
if (runSynchronously) {
final int oldMask = StrictMode.allowThreadDiskWritesMask();
@@ -10166,43 +10223,48 @@
mOomAdjuster.dumpCacheOomRankerSettings(pw);
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
-
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
dumpAllowedAssociationsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
-
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
mPendingIntentController.dumpPendingIntents(pw, dumpAll, dumpPackage);
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
dumpBroadcastsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
if (dumpAll || dumpPackage != null) {
dumpBroadcastStatsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
}
mCpHelper.dumpProvidersLocked(fd, pw, args, opti, dumpAll, dumpPackage);
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
dumpPermissions(fd, pw, args, opti, dumpAll, dumpPackage);
pw.println();
sdumper = mServices.newServiceDumperLocked(fd, pw, args, opti, dumpAll, dumpPackage);
if (!dumpClient) {
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
sdumper.dumpLocked();
}
@@ -10217,7 +10279,8 @@
// method with the lock held.
if (dumpClient) {
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
sdumper.dumpWithClient();
}
@@ -10230,33 +10293,38 @@
// proxies in the first place.
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
dumpBinderProxies(pw, BINDER_PROXY_HIGH_WATERMARK /* minToDump */);
}
synchronized(this) {
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
mAtmInternal.dump(DUMP_RECENTS_CMD, fd, pw, args, opti, dumpAll, dumpClient,
dumpPackage, displayIdFilter);
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
mAtmInternal.dump(DUMP_LASTANR_CMD, fd, pw, args, opti, dumpAll, dumpClient,
dumpPackage, displayIdFilter);
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
mAtmInternal.dump(DUMP_STARTER_CMD, fd, pw, args, opti, dumpAll, dumpClient,
dumpPackage, displayIdFilter);
if (dumpPackage == null) {
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
mAtmInternal.dump(DUMP_CONTAINERS_CMD, fd, pw, args, opti, dumpAll, dumpClient,
dumpPackage, displayIdFilter);
@@ -10266,7 +10334,8 @@
if (!dumpNormalPriority) {
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
mAtmInternal.dump(DUMP_ACTIVITIES_CMD, fd, pw, args, opti, dumpAll, dumpClient,
dumpPackage, displayIdFilter);
@@ -10274,45 +10343,53 @@
if (mAssociations.size() > 0) {
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
dumpAssociationsLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage);
}
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
mProcessList.getAppStartInfoTracker().dumpHistoryProcessStartInfo(pw, dumpPackage);
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
mProcessList.mAppExitInfoTracker.dumpHistoryProcessExitInfo(pw, dumpPackage);
}
if (dumpPackage == null) {
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
mOomAdjProfiler.dump(pw);
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
dumpLmkLocked(pw);
}
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
synchronized (mProcLock) {
mProcessList.dumpProcessesLSP(fd, pw, args, opti, dumpAll, dumpPackage, dumpAppId);
}
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
dumpUsers(pw);
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
mComponentAliasResolver.dump(pw);
}
@@ -10322,7 +10399,8 @@
* Dump the app restriction controller, it's required not to hold the global lock here.
*/
private void dumpAppRestrictionController(PrintWriter pw) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
mAppRestrictionController.dump(pw, "");
}
@@ -11462,7 +11540,9 @@
void dumpAllowedAssociationsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
int opti, boolean dumpAll, String dumpPackage) {
- pw.println("ACTIVITY MANAGER ALLOWED ASSOCIATION STATE (dumpsys activity allowed-associations)");
+ pw.println(
+ "ACTIVITY MANAGER ALLOWED ASSOCIATION STATE (dumpsys activity"
+ + " allowed-associations)");
boolean printed = false;
if (mAllowedAssociations != null) {
for (int i = 0; i < mAllowedAssociations.size(); i++) {
@@ -12816,7 +12896,8 @@
if (!opts.isCompact) {
pw.print(" Used RAM: "); pw.print(stringifyKBSize(ss[INDEX_TOTAL_PSS] - cachedPss
+ kernelUsed)); pw.print(" (");
- pw.print(stringifyKBSize(ss[INDEX_TOTAL_PSS] - cachedPss)); pw.print(" used pss + ");
+ pw.print(stringifyKBSize(ss[INDEX_TOTAL_PSS] - cachedPss));
+ pw.print(" used pss + ");
pw.print(stringifyKBSize(kernelUsed)); pw.print(" kernel)\n");
pw.print(" Lost RAM: "); pw.println(stringifyKBSize(lostRAM));
} else {
@@ -13686,8 +13767,15 @@
}
validateServiceInstanceName(instanceName);
- if (DEBUG_SERVICE) Slog.v(TAG_SERVICE,
- "*** startService: " + service + " type=" + resolvedType + " fg=" + requireForeground);
+ if (DEBUG_SERVICE)
+ Slog.v(
+ TAG_SERVICE,
+ "*** startService: "
+ + service
+ + " type="
+ + resolvedType
+ + " fg="
+ + requireForeground);
final int callingPid = Binder.getCallingPid();
final int callingUid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
@@ -15448,9 +15536,14 @@
if (checkPermission(android.Manifest.permission.BROADCAST_STICKY,
callingPid, callingUid)
!= PackageManager.PERMISSION_GRANTED) {
- String msg = "Permission Denial: broadcastIntent() requesting a sticky broadcast from pid="
- + callingPid + ", uid=" + callingUid
- + " requires " + android.Manifest.permission.BROADCAST_STICKY;
+ String msg =
+ "Permission Denial: broadcastIntent() requesting a sticky broadcast from"
+ + " pid="
+ + callingPid
+ + ", uid="
+ + callingUid
+ + " requires "
+ + android.Manifest.permission.BROADCAST_STICKY;
Slog.w(TAG, msg);
throw new SecurityException(msg);
}
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index d1c8c30..4a37913 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -276,6 +276,11 @@
&& record.getDeliveryGroupPolicy() == BroadcastOptions.DELIVERY_GROUP_POLICY_ALL) {
final BroadcastRecord replacedBroadcastRecord = replaceBroadcast(record, recordIndex);
if (replacedBroadcastRecord != null) {
+ if (mLastDeferredStates && shouldBeDeferred()
+ && (record.getDeliveryState(recordIndex)
+ == BroadcastRecord.DELIVERY_PENDING)) {
+ deferredStatesApplyConsumer.accept(record, recordIndex);
+ }
return replacedBroadcastRecord;
}
}
diff --git a/services/core/java/com/android/server/biometrics/log/ALSProbe.java b/services/core/java/com/android/server/biometrics/log/ALSProbe.java
index d584c99..d4e46a9 100644
--- a/services/core/java/com/android/server/biometrics/log/ALSProbe.java
+++ b/services/core/java/com/android/server/biometrics/log/ALSProbe.java
@@ -179,15 +179,18 @@
nextConsumer.consume(current);
} else if (mNextConsumer != null) {
mNextConsumer.add(nextConsumer);
- } else {
+ } else if (mLightSensor != null) {
mDestroyed = false;
mNextConsumer = nextConsumer;
enableLightSensorLoggingLocked();
+ } else {
+ Slog.w(TAG, "No light sensor - use current to consume");
+ nextConsumer.consume(current);
}
}
private void enableLightSensorLoggingLocked() {
- if (!mEnabled) {
+ if (!mEnabled && mLightSensor != null) {
mEnabled = true;
mLastAmbientLux = -1;
mSensorManager.registerListener(mLightSensorListener, mLightSensor,
@@ -201,7 +204,7 @@
private void disableLightSensorLoggingLocked(boolean destroying) {
resetTimerLocked(false /* start */);
- if (mEnabled) {
+ if (mEnabled && mLightSensor != null) {
mEnabled = false;
if (!destroying) {
mLastAmbientLux = -1;
diff --git a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
index a43f93a..91e560e 100644
--- a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
+++ b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
@@ -20,7 +20,6 @@
import android.annotation.Nullable;
import android.hardware.display.DisplayManagerInternal;
-import android.os.PowerManager;
import android.os.Trace;
/**
@@ -110,7 +109,6 @@
try {
mDisplayOffloader.stopOffload();
mIsActive = false;
- mDisplayPowerController.setBrightnessFromOffload(PowerManager.BRIGHTNESS_INVALID_FLOAT);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_POWER);
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 2010aca..7f014f6 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -375,7 +375,8 @@
// information.
// At the time of this writing, this value is changed within updatePowerState() only, which is
// limited to the thread used by DisplayControllerHandler.
- private final BrightnessReason mBrightnessReason = new BrightnessReason();
+ @VisibleForTesting
+ final BrightnessReason mBrightnessReason = new BrightnessReason();
private final BrightnessReason mBrightnessReasonTemp = new BrightnessReason();
// Brightness animation ramp rates in brightness units per second
@@ -1379,7 +1380,7 @@
// Switch to doze auto-brightness mode if needed
if (mFlags.areAutoBrightnessModesEnabled() && mAutomaticBrightnessController != null
&& !mAutomaticBrightnessController.isInIdleMode()) {
- mAutomaticBrightnessController.switchMode(mPowerRequest.policy == POLICY_DOZE
+ mAutomaticBrightnessController.switchMode(Display.isDozeState(state)
? AUTO_BRIGHTNESS_MODE_DOZE : AUTO_BRIGHTNESS_MODE_DEFAULT);
}
@@ -1434,7 +1435,9 @@
float currentBrightnessSetting = mDisplayBrightnessController.getCurrentBrightness();
// Apply auto-brightness.
int brightnessAdjustmentFlags = 0;
- if (Float.isNaN(brightnessState)) {
+ // AutomaticBrightnessStrategy has higher priority than OffloadBrightnessStrategy
+ if (Float.isNaN(brightnessState)
+ || mBrightnessReasonTemp.getReason() == BrightnessReason.REASON_OFFLOAD) {
if (mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()) {
brightnessState = mAutomaticBrightnessStrategy.getAutomaticScreenBrightness(
mTempBrightnessEvent);
@@ -1455,8 +1458,11 @@
if (mScreenOffBrightnessSensorController != null) {
mScreenOffBrightnessSensorController.setLightSensorEnabled(false);
}
+ setBrightnessFromOffload(PowerManager.BRIGHTNESS_INVALID_FLOAT);
} else {
mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
+ // Restore the lower-priority brightness strategy
+ brightnessState = displayBrightnessState.getBrightness();
}
}
} else {
@@ -3020,9 +3026,10 @@
setDwbcLoggingEnabled(msg.arg1);
break;
case MSG_SET_BRIGHTNESS_FROM_OFFLOAD:
- mDisplayBrightnessController.setBrightnessFromOffload(
- Float.intBitsToFloat(msg.arg1));
- updatePowerState();
+ if (mDisplayBrightnessController.setBrightnessFromOffload(
+ Float.intBitsToFloat(msg.arg1))) {
+ updatePowerState();
+ }
break;
}
}
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index f6d02db..34d53be 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -26,6 +26,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
import com.android.server.display.AutomaticBrightnessController;
import com.android.server.display.BrightnessMappingStrategy;
import com.android.server.display.BrightnessSetting;
@@ -175,14 +176,19 @@
/**
* Sets the brightness from the offload session.
+ * @return Whether the offload brightness has changed
*/
- public void setBrightnessFromOffload(float brightness) {
+ public boolean setBrightnessFromOffload(float brightness) {
synchronized (mLock) {
- if (mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy() != null) {
+ if (mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy() != null
+ && !BrightnessSynchronizer.floatEquals(mDisplayBrightnessStrategySelector
+ .getOffloadBrightnessStrategy().getOffloadScreenBrightness(), brightness)) {
mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy()
.setOffloadScreenBrightness(brightness);
+ return true;
}
}
+ return false;
}
/**
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
index 8e84450..71cc872 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
@@ -133,7 +133,8 @@
} else if (BrightnessUtils.isValidBrightnessValue(
mTemporaryBrightnessStrategy.getTemporaryScreenBrightness())) {
displayBrightnessStrategy = mTemporaryBrightnessStrategy;
- } else if (mOffloadBrightnessStrategy != null && BrightnessUtils.isValidBrightnessValue(
+ } else if (mAutomaticBrightnessStrategy.shouldUseAutoBrightness()
+ && mOffloadBrightnessStrategy != null && BrightnessUtils.isValidBrightnessValue(
mOffloadBrightnessStrategy.getOffloadScreenBrightness())) {
displayBrightnessStrategy = mOffloadBrightnessStrategy;
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
index d1ca49b..8b54b22 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
@@ -108,7 +108,6 @@
mIsAutoBrightnessEnabled = shouldUseAutoBrightness()
&& (targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze)
&& brightnessReason != BrightnessReason.REASON_OVERRIDE
- && brightnessReason != BrightnessReason.REASON_OFFLOAD
&& mAutomaticBrightnessController != null;
mAutoBrightnessDisabledDueToDisplayOff = shouldUseAutoBrightness()
&& !(targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze);
diff --git a/services/core/java/com/android/server/input/debug/FocusEventDebugView.java b/services/core/java/com/android/server/input/debug/FocusEventDebugView.java
index b30f5ec..6eae9a4 100644
--- a/services/core/java/com/android/server/input/debug/FocusEventDebugView.java
+++ b/services/core/java/com/android/server/input/debug/FocusEventDebugView.java
@@ -229,7 +229,8 @@
/** Report a key event to the debug view. */
@AnyThread
public void reportKeyEvent(KeyEvent event) {
- post(() -> handleKeyEvent(KeyEvent.obtain((KeyEvent) event)));
+ KeyEvent keyEvent = KeyEvent.obtain(event);
+ post(() -> handleKeyEvent(keyEvent));
}
/** Report a motion event to the debug view. */
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index fee0342..2205986 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -39,7 +39,6 @@
import static android.server.inputmethod.InputMethodManagerServiceProto.IN_FULLSCREEN_MODE;
import static android.server.inputmethod.InputMethodManagerServiceProto.IS_INTERACTIVE;
import static android.server.inputmethod.InputMethodManagerServiceProto.LAST_IME_TARGET_WINDOW_NAME;
-import static android.server.inputmethod.InputMethodManagerServiceProto.LAST_SWITCH_USER_ID;
import static android.server.inputmethod.InputMethodManagerServiceProto.SHOW_IME_WITH_HARD_KEYBOARD;
import static android.server.inputmethod.InputMethodManagerServiceProto.SYSTEM_READY;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -276,9 +275,6 @@
@NonNull
private final String[] mNonPreemptibleInputMethods;
- @UserIdInt
- private int mLastSwitchUserId;
-
final Context mContext;
final Resources mRes;
private final Handler mHandler;
@@ -1311,20 +1307,35 @@
@Override
public void onPackageDataCleared(String packageName, int uid) {
+ final int userId = getChangingUserId();
synchronized (ImfLock.class) {
+ final boolean isCurrentUser = (userId == mSettings.getUserId());
+ final AdditionalSubtypeMap additionalSubtypeMap;
+ final InputMethodSettings settings;
+ if (isCurrentUser) {
+ additionalSubtypeMap = mAdditionalSubtypeMap;
+ settings = mSettings;
+ } else {
+ additionalSubtypeMap = AdditionalSubtypeUtils.load(userId);
+ settings = queryInputMethodServicesInternal(mContext, userId,
+ additionalSubtypeMap, DirectBootAwareness.AUTO);
+ }
+
// Note that one package may implement multiple IMEs.
final ArrayList<String> changedImes = new ArrayList<>();
- for (InputMethodInfo imi : mSettings.getMethodList()) {
+ for (InputMethodInfo imi : settings.getMethodList()) {
if (imi.getPackageName().equals(packageName)) {
changedImes.add(imi.getId());
}
}
final AdditionalSubtypeMap newMap =
- mAdditionalSubtypeMap.cloneWithRemoveOrSelf(changedImes);
- if (newMap != mAdditionalSubtypeMap) {
- mAdditionalSubtypeMap = newMap;
+ additionalSubtypeMap.cloneWithRemoveOrSelf(changedImes);
+ if (newMap != additionalSubtypeMap) {
+ if (isCurrentUser) {
+ mAdditionalSubtypeMap = newMap;
+ }
AdditionalSubtypeUtils.save(
- mAdditionalSubtypeMap, mSettings.getMethodMap(), mSettings.getUserId());
+ newMap, settings.getMethodMap(), settings.getUserId());
}
if (!changedImes.isEmpty()) {
mChangedPackages.add(packageName);
@@ -1649,8 +1660,6 @@
final int userId = mActivityManagerInternal.getCurrentUserId();
- mLastSwitchUserId = userId;
-
// mSettings should be created before buildInputMethodListLocked
mSettings = InputMethodSettings.createEmptyMap(userId);
@@ -1837,7 +1846,6 @@
+ " selectedIme=" + mSettings.getSelectedInputMethod());
}
- mLastSwitchUserId = newUserId;
if (mIsInteractive && clientToBeReset != null) {
final ClientState cs = mClientController.getClient(clientToBeReset.asBinder());
if (cs == null) {
@@ -4710,7 +4718,6 @@
proto.write(CUR_TOKEN, Objects.toString(getCurTokenLocked()));
proto.write(CUR_TOKEN_DISPLAY_ID, mCurTokenDisplayId);
proto.write(SYSTEM_READY, mSystemReady);
- proto.write(LAST_SWITCH_USER_ID, mLastSwitchUserId);
proto.write(HAVE_CONNECTION, hasConnectionLocked());
proto.write(BOUND_TO_METHOD, mBoundToMethod);
proto.write(IS_INTERACTIVE, mIsInteractive);
@@ -6288,8 +6295,6 @@
@ShellCommandResult
private int onCommandWithSystemIdentity(@Nullable String cmd) {
switch (TextUtils.emptyIfNull(cmd)) {
- case "get-last-switch-user-id":
- return mService.getLastSwitchUserId(this);
case "tracing":
return mService.handleShellCommandTraceInputMethod(this);
case "ime": { // For "adb shell ime <command>".
@@ -6403,15 +6408,6 @@
// ----------------------------------------------------------------------
// Shell command handlers:
- @BinderThread
- @ShellCommandResult
- private int getLastSwitchUserId(@NonNull ShellCommand shellCommand) {
- synchronized (ImfLock.class) {
- shellCommand.getOutPrintWriter().println(mLastSwitchUserId);
- return ShellCommandResult.SUCCESS;
- }
- }
-
/**
* Handles {@code adb shell ime list}.
* @param shellCommand {@link ShellCommand} object that is handling this command.
diff --git a/services/core/java/com/android/server/net/TEST_MAPPING b/services/core/java/com/android/server/net/TEST_MAPPING
index e0376ed..8db5905 100644
--- a/services/core/java/com/android/server/net/TEST_MAPPING
+++ b/services/core/java/com/android/server/net/TEST_MAPPING
@@ -28,5 +28,10 @@
}
]
}
+ ],
+ "postsubmit":[
+ {
+ "name":"FrameworksVpnTests"
+ }
]
}
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index f645eaa..3ecc58e 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -942,6 +942,23 @@
return false;
}
+ protected boolean isPackageOrComponentAllowedWithPermission(ComponentName component,
+ int userId) {
+ if (!(isPackageOrComponentAllowed(component.flattenToString(), userId)
+ || isPackageOrComponentAllowed(component.getPackageName(), userId))) {
+ return false;
+ }
+ return componentHasBindPermission(component, userId);
+ }
+
+ private boolean componentHasBindPermission(ComponentName component, int userId) {
+ ServiceInfo info = getServiceInfo(component, userId);
+ if (info == null) {
+ return false;
+ }
+ return mConfig.bindPermission.equals(info.permission);
+ }
+
boolean isPackageOrComponentUserSet(String pkgOrComponent, int userId) {
synchronized (mApproved) {
ArraySet<String> services = mUserSetServices.get(userId);
@@ -1003,6 +1020,7 @@
for (int uid : uidList) {
if (isPackageAllowed(pkgName, UserHandle.getUserId(uid))) {
anyServicesInvolved = true;
+ trimApprovedListsForInvalidServices(pkgName, UserHandle.getUserId(uid));
}
}
}
@@ -1135,8 +1153,7 @@
synchronized (mMutex) {
if (enabled) {
- if (isPackageOrComponentAllowed(component.flattenToString(), userId)
- || isPackageOrComponentAllowed(component.getPackageName(), userId)) {
+ if (isPackageOrComponentAllowedWithPermission(component, userId)) {
registerServiceLocked(component, userId);
} else {
Slog.d(TAG, component + " no longer has permission to be bound");
@@ -1270,6 +1287,33 @@
return removed;
}
+ private void trimApprovedListsForInvalidServices(String packageName, int userId) {
+ synchronized (mApproved) {
+ final ArrayMap<Boolean, ArraySet<String>> approvedByType = mApproved.get(userId);
+ if (approvedByType == null) {
+ return;
+ }
+ for (int i = 0; i < approvedByType.size(); i++) {
+ final ArraySet<String> approved = approvedByType.valueAt(i);
+ for (int j = approved.size() - 1; j >= 0; j--) {
+ final String approvedPackageOrComponent = approved.valueAt(j);
+ if (TextUtils.equals(getPackageName(approvedPackageOrComponent), packageName)) {
+ final ComponentName component = ComponentName.unflattenFromString(
+ approvedPackageOrComponent);
+ if (component != null && !componentHasBindPermission(component, userId)) {
+ approved.removeAt(j);
+ if (DEBUG) {
+ Slog.v(TAG, "Removing " + approvedPackageOrComponent
+ + " from approved list; no bind permission found "
+ + mConfig.bindPermission);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
protected String getPackageName(String packageOrComponent) {
final ComponentName component = ComponentName.unflattenFromString(packageOrComponent);
if (component != null) {
@@ -1519,8 +1563,7 @@
void reregisterService(final ComponentName cn, final int userId) {
// If rebinding a package that died, ensure it still has permission
// after the rebind delay
- if (isPackageOrComponentAllowed(cn.getPackageName(), userId)
- || isPackageOrComponentAllowed(cn.flattenToString(), userId)) {
+ if (isPackageOrComponentAllowedWithPermission(cn, userId)) {
registerService(cn, userId);
}
}
diff --git a/services/core/java/com/android/server/notification/ZenAdapters.java b/services/core/java/com/android/server/notification/ZenAdapters.java
deleted file mode 100644
index 37b263c..0000000
--- a/services/core/java/com/android/server/notification/ZenAdapters.java
+++ /dev/null
@@ -1,83 +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.server.notification;
-
-import android.app.Flags;
-import android.app.NotificationManager.Policy;
-import android.service.notification.ZenModeConfig;
-import android.service.notification.ZenPolicy;
-
-/**
- * Converters between different Zen representations.
- */
-class ZenAdapters {
-
- static ZenPolicy notificationPolicyToZenPolicy(Policy policy) {
- ZenPolicy.Builder zenPolicyBuilder = new ZenPolicy.Builder()
- .allowAlarms(policy.allowAlarms())
- .allowCalls(
- policy.allowCalls()
- ? ZenModeConfig.getZenPolicySenders(policy.allowCallsFrom())
- : ZenPolicy.PEOPLE_TYPE_NONE)
- .allowConversations(
- policy.allowConversations()
- ? notificationPolicyConversationSendersToZenPolicy(
- policy.allowConversationsFrom())
- : ZenPolicy.CONVERSATION_SENDERS_NONE)
- .allowEvents(policy.allowEvents())
- .allowMedia(policy.allowMedia())
- .allowMessages(
- policy.allowMessages()
- ? ZenModeConfig.getZenPolicySenders(policy.allowMessagesFrom())
- : ZenPolicy.PEOPLE_TYPE_NONE)
- .allowReminders(policy.allowReminders())
- .allowRepeatCallers(policy.allowRepeatCallers())
- .allowSystem(policy.allowSystem());
-
- if (policy.suppressedVisualEffects != Policy.SUPPRESSED_EFFECTS_UNSET) {
- zenPolicyBuilder.showBadges(policy.showBadges())
- .showFullScreenIntent(policy.showFullScreenIntents())
- .showInAmbientDisplay(policy.showAmbient())
- .showInNotificationList(policy.showInNotificationList())
- .showLights(policy.showLights())
- .showPeeking(policy.showPeeking())
- .showStatusBarIcons(policy.showStatusBarIcons());
- }
-
- if (Flags.modesApi()) {
- zenPolicyBuilder.allowPriorityChannels(policy.allowPriorityChannels());
- }
-
- return zenPolicyBuilder.build();
- }
-
- @ZenPolicy.ConversationSenders
- private static int notificationPolicyConversationSendersToZenPolicy(
- int npPriorityConversationSenders) {
- switch (npPriorityConversationSenders) {
- case Policy.CONVERSATION_SENDERS_ANYONE:
- return ZenPolicy.CONVERSATION_SENDERS_ANYONE;
- case Policy.CONVERSATION_SENDERS_IMPORTANT:
- return ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
- case Policy.CONVERSATION_SENDERS_NONE:
- return ZenPolicy.CONVERSATION_SENDERS_NONE;
- case Policy.CONVERSATION_SENDERS_UNSET:
- default:
- return ZenPolicy.CONVERSATION_SENDERS_UNSET;
- }
- }
-}
diff --git a/services/core/java/com/android/server/notification/ZenModeEventLogger.java b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
index b9a267f..8e37b4f 100644
--- a/services/core/java/com/android/server/notification/ZenModeEventLogger.java
+++ b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
@@ -32,6 +32,7 @@
import android.content.pm.PackageManager;
import android.os.Process;
import android.service.notification.DNDPolicyProto;
+import android.service.notification.ZenAdapters;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenModeConfig.ConfigChangeOrigin;
import android.service.notification.ZenModeConfig.ZenRule;
@@ -591,9 +592,11 @@
// This applies to both call and message senders, but not conversation senders,
// where they use the same enum values.
proto.write(DNDPolicyProto.ALLOW_CALLS_FROM,
- ZenModeConfig.getZenPolicySenders(mNewPolicy.allowCallsFrom()));
+ ZenAdapters.notificationPolicySendersToZenPolicyPeopleType(
+ mNewPolicy.allowCallsFrom()));
proto.write(DNDPolicyProto.ALLOW_MESSAGES_FROM,
- ZenModeConfig.getZenPolicySenders(mNewPolicy.allowMessagesFrom()));
+ ZenAdapters.notificationPolicySendersToZenPolicyPeopleType(
+ mNewPolicy.allowMessagesFrom()));
proto.write(DNDPolicyProto.ALLOW_CONVERSATIONS_FROM,
mNewPolicy.allowConversationsFrom());
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 6857869..bc86c82 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -84,6 +84,7 @@
import android.service.notification.Condition;
import android.service.notification.ConditionProviderService;
import android.service.notification.DeviceEffectsApplier;
+import android.service.notification.ZenAdapters;
import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenModeConfig.ConfigChangeOrigin;
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 4f86adf..4eb8b2b 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -354,6 +354,10 @@
DevicePolicyManager getDevicePolicyManager() {
return mContext.getSystemService(DevicePolicyManager.class);
}
+
+ void setSystemProperty(String key, String value) {
+ SystemProperties.set(key, value);
+ }
}
BugreportManagerServiceImpl(Context context) {
@@ -737,7 +741,7 @@
@GuardedBy("mLock")
private IDumpstate startAndGetDumpstateBinderServiceLocked() {
// Start bugreport service.
- SystemProperties.set("ctl.start", BUGREPORT_SERVICE);
+ mInjector.setSystemProperty("ctl.start", BUGREPORT_SERVICE);
IDumpstate ds = null;
boolean timedOut = false;
@@ -769,7 +773,7 @@
// This tells init to cancel bugreportd service. Note that this is achieved through
// setting a system property which is not thread-safe. So the lock here offers
// thread-safety only among callers of the API.
- SystemProperties.set("ctl.stop", BUGREPORT_SERVICE);
+ mInjector.setSystemProperty("ctl.stop", BUGREPORT_SERVICE);
}
@RequiresPermission(android.Manifest.permission.DUMP)
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 9afdde5..b5476fd 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -760,13 +760,18 @@
if (pkgName == null) {
if (!mCrossProfileIntentResolverEngine.shouldSkipCurrentProfile(this, intent,
resolvedType, userId)) {
- /*
- Check for results in the current profile only if there is no
- {@link CrossProfileIntentFilter} for user with flag
- {@link PackageManager.SKIP_CURRENT_PROFILE} set.
- */
- result.addAll(filterIfNotSystemUser(mComponentResolver.queryActivities(this,
- intent, resolvedType, flags, userId), userId));
+
+ final List<ResolveInfo> queryResult = mComponentResolver.queryActivities(this,
+ intent, resolvedType, flags, userId);
+ // If the user doesn't exist, the queryResult is null
+ if (queryResult != null) {
+ /*
+ Check for results in the current profile only if there is no
+ {@link CrossProfileIntentFilter} for user with flag
+ {@link PackageManager.SKIP_CURRENT_PROFILE} set.
+ */
+ result.addAll(filterIfNotSystemUser(queryResult, userId));
+ }
}
addInstant = isInstantAppResolutionAllowed(intent, result, userId,
false /*skipPackageCheck*/, flags);
@@ -788,9 +793,13 @@
if (setting != null && setting.getAndroidPackage() != null && (resolveForStart
|| !shouldFilterApplication(setting, filterCallingUid, userId))) {
- result.addAll(filterIfNotSystemUser(mComponentResolver.queryActivities(this,
+ final List<ResolveInfo> queryResult = mComponentResolver.queryActivities(this,
intent, resolvedType, flags, setting.getAndroidPackage().getActivities(),
- userId), userId));
+ userId);
+ // If the user doesn't exist, the queryResult is null
+ if (queryResult != null) {
+ result.addAll(filterIfNotSystemUser(queryResult, userId));
+ }
}
if (result == null || result.size() == 0) {
// the caller wants to resolve for a particular package; however, there
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index ef8453d..29de26e 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -31,6 +31,7 @@
import static android.content.pm.PackageManager.DELETE_ALL_USERS;
import static android.content.pm.PackageManager.DELETE_ARCHIVE;
import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
+import static android.content.pm.PackageManager.INSTALL_UNARCHIVE;
import static android.content.pm.PackageManager.INSTALL_UNARCHIVE_DRAFT;
import static android.graphics.drawable.AdaptiveIconDrawable.getExtraInsetFraction;
import static android.os.PowerExemptionManager.REASON_PACKAGE_UNARCHIVE;
@@ -754,8 +755,9 @@
int draftSessionId;
try {
- draftSessionId = Binder.withCleanCallingIdentity(() ->
- createDraftSession(packageName, installerPackage, statusReceiver, userId));
+ draftSessionId = Binder.withCleanCallingIdentity(
+ () -> createDraftSession(packageName, installerPackage, callerPackageName,
+ statusReceiver, userId));
} catch (RuntimeException e) {
if (e.getCause() instanceof IOException) {
throw ExceptionUtils.wrap((IOException) e.getCause());
@@ -795,11 +797,18 @@
}
private int createDraftSession(String packageName, String installerPackage,
+ String callerPackageName,
IntentSender statusReceiver, int userId) throws IOException {
PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
sessionParams.setAppPackageName(packageName);
- sessionParams.installFlags = INSTALL_UNARCHIVE_DRAFT;
+ sessionParams.setAppLabel(
+ mContext.getString(com.android.internal.R.string.unarchival_session_app_label));
+ sessionParams.setAppIcon(
+ getArchivedAppIcon(packageName, UserHandle.of(userId), callerPackageName));
+ // To make sure SessionInfo::isUnarchival returns true for draft sessions,
+ // INSTALL_UNARCHIVE is also set.
+ sessionParams.installFlags = (INSTALL_UNARCHIVE_DRAFT | INSTALL_UNARCHIVE);
int installerUid = mPm.snapshotComputer().getPackageUid(installerPackage, 0, userId);
// Handles case of repeated unarchival calls for the same package.
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index fe65010..59d6219 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -3386,12 +3386,7 @@
} else if (tagName.equals("verifier")) {
final String deviceIdentity = parser.getAttributeValue(null, "device");
- try {
- mVerifierDeviceIdentity = VerifierDeviceIdentity.parse(deviceIdentity);
- } catch (IllegalArgumentException e) {
- Slog.w(PackageManagerService.TAG, "Discard invalid verifier device id: "
- + e.getMessage());
- }
+ mVerifierDeviceIdentity = VerifierDeviceIdentity.parse(deviceIdentity);
} else if (TAG_READ_EXTERNAL_STORAGE.equals(tagName)) {
// No longer used.
} else if (tagName.equals("keyset-settings")) {
@@ -3419,7 +3414,8 @@
}
str.close();
- } catch (IOException | XmlPullParserException | ArrayIndexOutOfBoundsException e) {
+ } catch (IOException | XmlPullParserException | ArrayIndexOutOfBoundsException
+ | IllegalArgumentException e) {
// Remove corrupted file and retry.
atomicFile.failRead(str, e);
@@ -4558,6 +4554,10 @@
for (int i = 0; i < size; i++) {
final PackageSetting ps = mPackages.valueAt(i);
if (ps.getPkg() == null) {
+ // This would force-create correct per-user state.
+ ps.setInstalled(false, userHandle);
+ // Make sure the app is excluded from storage mapping for this user.
+ writeKernelMappingLPr(ps);
continue;
}
final boolean shouldMaybeInstall = ps.isSystem() &&
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
index f8c678a..52ef87c 100644
--- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
@@ -45,11 +45,9 @@
import static android.hardware.SensorPrivacyManager.Sources.QS_TILE;
import static android.hardware.SensorPrivacyManager.Sources.SETTINGS;
import static android.hardware.SensorPrivacyManager.Sources.SHELL;
-import static android.hardware.SensorPrivacyManager.StateTypes.AUTOMOTIVE_DRIVER_ASSISTANCE_APPS;
-import static android.hardware.SensorPrivacyManager.StateTypes.AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS;
-import static android.hardware.SensorPrivacyManager.StateTypes.AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS;
import static android.hardware.SensorPrivacyManager.StateTypes.DISABLED;
import static android.hardware.SensorPrivacyManager.StateTypes.ENABLED;
+import static android.hardware.SensorPrivacyManager.StateTypes.ENABLED_EXCEPT_ALLOWLISTED_APPS;
import static android.hardware.SensorPrivacyManager.TOGGLE_TYPE_HARDWARE;
import static android.hardware.SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE;
import static android.os.UserHandle.USER_NULL;
@@ -57,11 +55,9 @@
import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION;
import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__ACTION_UNKNOWN;
-import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_APPS;
-import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS;
-import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS;
import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_OFF;
import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_ON;
+import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_ON_EXCEPT_ALLOWLISTED_APPS;
import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__SENSOR__CAMERA;
import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__SENSOR__MICROPHONE;
import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__SENSOR__SENSOR_UNKNOWN;
@@ -98,7 +94,6 @@
import android.content.res.Configuration;
import android.database.ContentObserver;
import android.graphics.drawable.Icon;
-import android.hardware.CameraPrivacyAllowlistEntry;
import android.hardware.ISensorPrivacyListener;
import android.hardware.ISensorPrivacyManager;
import android.hardware.SensorPrivacyManager;
@@ -153,7 +148,6 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
@@ -170,18 +164,12 @@
public static final int REMINDER_DIALOG_DELAY_MILLIS = 500;
@FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
- private static final int ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS =
- PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS;
- @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
- private static final int ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS =
- PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS;
- @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
- private static final int ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_APPS =
- PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_APPS;
- @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
private static final int ACTION__TOGGLE_ON =
PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_ON;
@FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
+ private static final int ACTION__TOGGLE_ON_EXCEPT_ALLOWLISTED_APPS =
+ PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_ON_EXCEPT_ALLOWLISTED_APPS;
+ @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
private static final int ACTION__TOGGLE_OFF =
PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_OFF;
@FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
@@ -208,8 +196,7 @@
private CallStateHelper mCallStateHelper;
private KeyguardManager mKeyguardManager;
- List<CameraPrivacyAllowlistEntry> mCameraPrivacyAllowlist =
- new ArrayList<CameraPrivacyAllowlistEntry>();
+ List<String> mCameraPrivacyAllowlist = new ArrayList<String>();
private int mCurrentUser = USER_NULL;
@@ -227,14 +214,8 @@
mPackageManagerInternal = getLocalService(PackageManagerInternal.class);
mNotificationManager = mContext.getSystemService(NotificationManager.class);
mSensorPrivacyServiceImpl = new SensorPrivacyServiceImpl();
- ArrayMap<String, Boolean> cameraPrivacyAllowlist =
- SystemConfig.getInstance().getCameraPrivacyAllowlist();
-
- for (Map.Entry<String, Boolean> entry : cameraPrivacyAllowlist.entrySet()) {
- CameraPrivacyAllowlistEntry ent = new CameraPrivacyAllowlistEntry();
- ent.packageName = entry.getKey();
- ent.isMandatory = entry.getValue();
- mCameraPrivacyAllowlist.add(ent);
+ for (String entry : SystemConfig.getInstance().getCameraPrivacyAllowlist()) {
+ mCameraPrivacyAllowlist.add(entry);
}
}
@@ -908,14 +889,8 @@
case DISABLED :
logAction = ACTION__TOGGLE_ON;
break;
- case AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS :
- logAction = ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS;
- break;
- case AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS :
- logAction = ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS;
- break;
- case AUTOMOTIVE_DRIVER_ASSISTANCE_APPS :
- logAction = ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_APPS;
+ case ENABLED_EXCEPT_ALLOWLISTED_APPS :
+ logAction = ACTION__TOGGLE_ON_EXCEPT_ALLOWLISTED_APPS;
break;
default :
logAction = ACTION__ACTION_UNKNOWN;
@@ -981,11 +956,23 @@
@Override
@FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
@RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
- public List<CameraPrivacyAllowlistEntry> getCameraPrivacyAllowlist() {
+ public List<String> getCameraPrivacyAllowlist() {
enforceObserveSensorPrivacyPermission();
return mCameraPrivacyAllowlist;
}
+ /**
+ * Sets camera privacy allowlist.
+ * @param allowlist List of automotive driver assistance packages for
+ * privacy allowlisting.
+ * @hide
+ */
+ @Override
+ public void setCameraPrivacyAllowlist(List<String> allowlist) {
+ enforceManageSensorPrivacyPermission();
+ mCameraPrivacyAllowlist = new ArrayList<>(allowlist);
+ }
+
@Override
@FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
@RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
@@ -1005,23 +992,9 @@
return true;
} else if (state == DISABLED) {
return false;
- } else if (state == AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS) {
- for (CameraPrivacyAllowlistEntry entry : mCameraPrivacyAllowlist) {
- if ((packageName.equals(entry.packageName)) && !entry.isMandatory) {
- return false;
- }
- }
- return true;
- } else if (state == AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS) {
- for (CameraPrivacyAllowlistEntry entry : mCameraPrivacyAllowlist) {
- if ((packageName.equals(entry.packageName)) && entry.isMandatory) {
- return false;
- }
- }
- return true;
- } else if (state == AUTOMOTIVE_DRIVER_ASSISTANCE_APPS) {
- for (CameraPrivacyAllowlistEntry entry : mCameraPrivacyAllowlist) {
- if (packageName.equals(entry.packageName)) {
+ } else if (state == ENABLED_EXCEPT_ALLOWLISTED_APPS) {
+ for (String entry : mCameraPrivacyAllowlist) {
+ if (packageName.equals(entry)) {
return false;
}
}
@@ -1616,7 +1589,7 @@
setToggleSensorPrivacy(userId, SHELL, sensor, false);
}
break;
- case "automotive_driver_assistance_apps" : {
+ case "enable_except_allowlisted_apps" : {
if (Flags.cameraPrivacyAllowlist()) {
int sensor = sensorStrToId(getNextArgRequired());
if ((!isAutomotive(mContext)) || (sensor != CAMERA)) {
@@ -1625,33 +1598,7 @@
}
setToggleSensorPrivacyState(userId, SHELL, sensor,
- AUTOMOTIVE_DRIVER_ASSISTANCE_APPS);
- }
- }
- break;
- case "automotive_driver_assistance_helpful_apps" : {
- if (Flags.cameraPrivacyAllowlist()) {
- int sensor = sensorStrToId(getNextArgRequired());
- if ((!isAutomotive(mContext)) || (sensor != CAMERA)) {
- pw.println("Command not valid for this sensor");
- return -1;
- }
-
- setToggleSensorPrivacyState(userId, SHELL, sensor,
- AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS);
- }
- }
- break;
- case "automotive_driver_assistance_required_apps" : {
- if (Flags.cameraPrivacyAllowlist()) {
- int sensor = sensorStrToId(getNextArgRequired());
- if ((!isAutomotive(mContext)) || (sensor != CAMERA)) {
- pw.println("Command not valid for this sensor");
- return -1;
- }
-
- setToggleSensorPrivacyState(userId, SHELL, sensor,
- AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS);
+ ENABLED_EXCEPT_ALLOWLISTED_APPS);
}
}
break;
@@ -1679,18 +1626,9 @@
pw.println("");
if (Flags.cameraPrivacyAllowlist()) {
if (isAutomotive(mContext)) {
- pw.println(" automotive_driver_assistance_apps USER_ID SENSOR");
- pw.println(" Disable privacy for automotive apps which help you"
- + " drive and apps which are required by OEM");
- pw.println("");
- pw.println(" automotive_driver_assistance_helpful_apps "
+ pw.println(" enable_except_allowlisted_apps "
+ "USER_ID SENSOR");
- pw.println(" Disable privacy for automotive apps which "
- + "help you drive.");
- pw.println("");
- pw.println(" automotive_driver_assistance_required_apps "
- + "USER_ID SENSOR");
- pw.println(" Disable privacy for automotive apps which are "
+ pw.println(" Enable privacy except for automotive apps which are "
+ "required by OEM.");
pw.println("");
}
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index ffce50e..79adcb4 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -349,7 +349,6 @@
}
}
- userState.mIAppMap.clear();
userState.mAdServiceMap = adServiceMap;
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
index 601c7f4..5175b74 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
@@ -39,6 +39,7 @@
import android.view.DisplayInfo;
import android.view.View;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.utils.TimingsTraceAndSlog;
import libcore.io.IoUtils;
@@ -65,7 +66,7 @@
* Maximum acceptable parallax.
* A value of 1 means "the additional width for parallax is at most 100% of the screen width"
*/
- private static final float MAX_PARALLAX = 1f;
+ @VisibleForTesting static final float MAX_PARALLAX = 1f;
/**
* We define three ways to adjust a crop. These modes are used depending on the situation:
@@ -73,10 +74,9 @@
* - When going from folded to unfolded, we want to add content
* - For a screen rotation, we want to keep the same amount of content
*/
- private static final int ADD = 1;
- private static final int REMOVE = 2;
- private static final int BALANCE = 3;
-
+ @VisibleForTesting static final int ADD = 1;
+ @VisibleForTesting static final int REMOVE = 2;
+ @VisibleForTesting static final int BALANCE = 3;
private final WallpaperDisplayHelper mWallpaperDisplayHelper;
@@ -131,19 +131,23 @@
(bitmapSize.y - crop.height()) / 2);
return crop;
}
+
+ // If any suggested crop is invalid, fallback to case 1
+ for (int i = 0; i < suggestedCrops.size(); i++) {
+ Rect testCrop = suggestedCrops.valueAt(i);
+ if (testCrop == null || testCrop.left < 0 || testCrop.top < 0
+ || testCrop.right > bitmapSize.x || testCrop.bottom > bitmapSize.y) {
+ Slog.w(TAG, "invalid crop: " + testCrop + " for bitmap size: " + bitmapSize);
+ return getCrop(displaySize, bitmapSize, new SparseArray<>(), rtl);
+ }
+ }
+
int orientation = getOrientation(displaySize);
// Case 2: if the orientation exists in the suggested crops, adjust the suggested crop
Rect suggestedCrop = suggestedCrops.get(orientation);
if (suggestedCrop != null) {
- if (suggestedCrop.left < 0 || suggestedCrop.top < 0
- || suggestedCrop.right > bitmapSize.x || suggestedCrop.bottom > bitmapSize.y) {
- Slog.w(TAG, "invalid suggested crop: " + suggestedCrop);
- Rect fullImage = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
- return getAdjustedCrop(fullImage, bitmapSize, displaySize, true, rtl, ADD);
- } else {
return getAdjustedCrop(suggestedCrop, bitmapSize, displaySize, true, rtl, ADD);
- }
}
// Case 3: if we have the 90° rotated orientation in the suggested crops, reuse it and
@@ -209,7 +213,8 @@
* Given a crop, a displaySize for the orientation of that crop, compute the visible part of the
* crop. This removes any additional width used for parallax. No-op if displaySize == null.
*/
- private static Rect noParallax(Rect crop, Point displaySize, Point bitmapSize, boolean rtl) {
+ @VisibleForTesting
+ static Rect noParallax(Rect crop, Point displaySize, Point bitmapSize, boolean rtl) {
if (displaySize == null) return crop;
Rect adjustedCrop = getAdjustedCrop(crop, bitmapSize, displaySize, true, rtl, ADD);
// only keep the visible part (without parallax)
@@ -240,12 +245,14 @@
* </li>
* </ul>
*/
- private static Rect getAdjustedCrop(Rect crop, Point bitmapSize, Point screenSize,
+ @VisibleForTesting
+ static Rect getAdjustedCrop(Rect crop, Point bitmapSize, Point screenSize,
boolean parallax, boolean rtl, int mode) {
Rect adjustedCrop = new Rect(crop);
float cropRatio = ((float) crop.width()) / crop.height();
float screenRatio = ((float) screenSize.x) / screenSize.y;
- if (cropRatio >= screenRatio) {
+ if (cropRatio == screenRatio) return crop;
+ if (cropRatio > screenRatio) {
if (!parallax) {
// rotate everything 90 degrees clockwise, compute the result, and rotate back
int newLeft = bitmapSize.y - crop.bottom;
@@ -274,6 +281,7 @@
}
}
} else {
+ // TODO (b/281648899) the third case is not always correct, fix that.
int widthToAdd = mode == REMOVE ? 0
: mode == ADD ? (int) (0.5 + crop.height() * screenRatio - crop.width())
: (int) (0.5 + crop.height() - crop.width());
@@ -644,6 +652,9 @@
if (!success) {
Slog.e(TAG, "Unable to apply new wallpaper");
wallpaper.getCropFile().delete();
+ wallpaper.mCropHints.clear();
+ wallpaper.cropHint.set(0, 0, 0, 0);
+ wallpaper.mSampleSize = 1f;
}
if (wallpaper.getCropFile().exists()) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
index 88e9672..0165d65 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
@@ -341,6 +341,7 @@
} else {
wallpaper.cropHint.set(totalCropHint);
}
+ wallpaper.mSampleSize = parser.getAttributeFloat(null, "sampleSize", 1f);
} else {
wallpaper.cropHint.set(totalCropHint);
}
@@ -493,6 +494,7 @@
out.attributeInt(null, "totalCropTop", wallpaper.cropHint.top);
out.attributeInt(null, "totalCropRight", wallpaper.cropHint.right);
out.attributeInt(null, "totalCropBottom", wallpaper.cropHint.bottom);
+ out.attributeFloat(null, "sampleSize", wallpaper.mSampleSize);
} else if (!multiCrop()) {
final DisplayData wpdData =
mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
index e9c4096..043470f 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
@@ -30,6 +30,7 @@
import android.os.Process;
import android.os.ResultReceiver;
import android.os.ShellCallback;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Slog;
import android.webkit.IWebViewUpdateService;
@@ -37,6 +38,7 @@
import android.webkit.WebViewProviderResponse;
import com.android.internal.util.DumpUtils;
+import com.android.modules.expresslog.Histogram;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -52,6 +54,14 @@
private static final String TAG = "WebViewUpdateService";
+ private static final Histogram sPrepareWebViewInSystemServerLatency = new Histogram(
+ "webview.value_prepare_webview_in_system_server_latency",
+ new Histogram.ScaledRangeOptions(20, 0, 1, 1.5f));
+
+ private static final Histogram sAppWaitingForRelroCompletionDelay = new Histogram(
+ "webview.value_app_waiting_for_relro_completion_delay",
+ new Histogram.ScaledRangeOptions(20, 0, 1, 1.4f));
+
private BroadcastReceiver mWebViewUpdatedReceiver;
private WebViewUpdateServiceInterface mImpl;
@@ -132,7 +142,10 @@
}
public void prepareWebViewInSystemServer() {
+ long currentTimeMs = SystemClock.uptimeMillis();
mImpl.prepareWebViewInSystemServer();
+ sPrepareWebViewInSystemServerLatency.logSample(
+ (float) (SystemClock.uptimeMillis() - currentTimeMs));
}
private static String packageNameFromIntent(Intent intent) {
@@ -204,8 +217,12 @@
throw new IllegalStateException("Cannot create a WebView from the SystemServer");
}
+ long startTimeMs = SystemClock.uptimeMillis();
final WebViewProviderResponse webViewProviderResponse =
WebViewUpdateService.this.mImpl.waitForAndGetProvider();
+ long endTimeMs = SystemClock.uptimeMillis();
+ sAppWaitingForRelroCompletionDelay.logSample((float) (endTimeMs - startTimeMs));
+
if (webViewProviderResponse.packageInfo != null) {
grantVisibilityToCaller(
webViewProviderResponse.packageInfo.packageName, Binder.getCallingUid());
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
index 1d6ad6d..532ff98 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
@@ -23,12 +23,15 @@
import android.os.AsyncTask;
import android.os.Trace;
import android.os.UserHandle;
+import android.util.AndroidRuntimeException;
import android.util.Slog;
import android.webkit.UserPackage;
import android.webkit.WebViewFactory;
import android.webkit.WebViewProviderInfo;
import android.webkit.WebViewProviderResponse;
+import com.android.modules.expresslog.Counter;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -357,6 +360,12 @@
mNumRelroCreationsFinished = 0;
mNumRelroCreationsStarted =
mSystemInterface.onWebViewProviderChanged(newPackage);
+ Counter.logIncrement("webview.value_on_webview_provider_changed_counter");
+ if (newPackage.packageName.equals(getDefaultWebViewPackage().packageName)) {
+ Counter.logIncrement(
+ "webview.value_on_webview_provider_changed_"
+ + "with_default_package_counter");
+ }
// If the relro creations finish before we know the number of started creations
// we will have to do any cleanup/notifying here.
checkIfRelrosDoneLocked();
@@ -388,9 +397,15 @@
@Override
public WebViewProviderInfo getDefaultWebViewPackage() {
- throw new IllegalStateException(
- "getDefaultWebViewPackage shouldn't be called if update_service_v2 flag is"
- + " disabled.");
+ for (WebViewProviderInfo provider : getWebViewPackages()) {
+ if (provider.availableByDefault) {
+ return provider;
+ }
+ }
+
+ // This should be unreachable because the config parser enforces that there is at least
+ // one availableByDefault provider.
+ throw new AndroidRuntimeException("No available by default WebView Provider.");
}
private static class ProviderAndPackageInfo {
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
index 1f9d265..fb338ba 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
@@ -31,6 +31,8 @@
import android.webkit.WebViewProviderInfo;
import android.webkit.WebViewProviderResponse;
+import com.android.modules.expresslog.Counter;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -412,6 +414,12 @@
mNumRelroCreationsFinished = 0;
mNumRelroCreationsStarted =
mSystemInterface.onWebViewProviderChanged(newPackage);
+ Counter.logIncrement("webview.value_on_webview_provider_changed_counter");
+ if (newPackage.packageName.equals(getDefaultWebViewPackage().packageName)) {
+ Counter.logIncrement(
+ "webview.value_on_webview_provider_changed_"
+ + "with_default_package_counter");
+ }
// If the relro creations finish before we know the number of started creations
// we will have to do any cleanup/notifying here.
checkIfRelrosDoneLocked();
@@ -479,6 +487,7 @@
* for all users, otherwise use the default provider.
*/
private PackageInfo findPreferredWebViewPackage() throws WebViewPackageMissingException {
+ Counter.logIncrement("webview.value_find_preferred_webview_package_counter");
// If the user has chosen provider, use that (if it's installed and enabled for all
// users).
String userChosenPackageName = mSystemInterface.getUserChosenWebViewProvider(mContext);
@@ -508,12 +517,15 @@
PackageInfo packageInfo = mSystemInterface.getPackageInfoForProvider(mDefaultProvider);
if (validityResult(mDefaultProvider, packageInfo) == VALIDITY_OK) {
return packageInfo;
+ } else {
+ Counter.logIncrement("webview.value_default_webview_package_invalid_counter");
}
} catch (NameNotFoundException e) {
Slog.w(TAG, "Default WebView package (" + mDefaultProvider.packageName + ") not found");
}
// This should never happen during normal operation (only with modified system images).
+ Counter.logIncrement("webview.value_webview_not_usable_for_all_users_counter");
mAnyWebViewInstalled = false;
throw new WebViewPackageMissingException("Could not find a loadable WebView package");
}
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 91eff18..4189988 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -1209,6 +1209,7 @@
private boolean mShown;
private boolean mLastSurfaceShown;
private int mAlpha;
+ private int mPreviousAlpha;
private volatile boolean mInvalidated;
@@ -1344,6 +1345,7 @@
// using WindowManagerGlobalLock. Grab copies of these values before
// drawing on the canvas so that drawing can be performed outside of the lock.
int alpha;
+ boolean redrawBounds;
Rect drawingRect = null;
Region drawingBounds = null;
synchronized (mService.mGlobalLock) {
@@ -1361,7 +1363,13 @@
mInvalidated = false;
alpha = mAlpha;
- if (alpha > 0) {
+ // For b/325863281, we should ensure the drawn border path is cleared when
+ // alpha = 0. Therefore, we cache the last used alpha when drawing as
+ // mPreviousAlpha and check it here. If mPreviousAlpha > 0, which means
+ // the border is showing now, then we should still redraw the clear path
+ // on the canvas so the border is cleared.
+ redrawBounds = mAlpha > 0 || mPreviousAlpha > 0;
+ if (redrawBounds) {
drawingBounds = new Region(mBounds);
// Empty dirty rectangle means unspecified.
if (mDirtyRect.isEmpty()) {
@@ -1378,7 +1386,7 @@
final boolean showSurface;
// Draw without holding WindowManagerGlobalLock.
- if (alpha > 0) {
+ if (redrawBounds) {
Canvas canvas = null;
try {
canvas = mSurface.lockCanvas(drawingRect);
@@ -1392,11 +1400,11 @@
mPaint.setAlpha(alpha);
canvas.drawPath(drawingBounds.getBoundaryPath(), mPaint);
mSurface.unlockCanvasAndPost(canvas);
- showSurface = true;
- } else {
- showSurface = false;
+ mPreviousAlpha = alpha;
}
+ showSurface = alpha > 0;
+
if (showSurface && !mLastSurfaceShown) {
mTransaction.show(mSurfaceControl).apply();
mLastSurfaceShown = true;
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index fa76774..83f44d2 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -311,7 +311,7 @@
return mInsetsHint;
}
final WindowState win = mWindowContainer.asWindowState();
- if (win != null && win.mGivenInsetsPending && win.mAttrs.providedInsets == null) {
+ if (win != null && win.mGivenInsetsPending) {
return mInsetsHint;
}
if (mInsetsHintStale) {
@@ -520,37 +520,11 @@
updateVisibility();
mControl = new InsetsSourceControl(mSource.getId(), mSource.getType(), leash,
mClientVisible, surfacePosition, getInsetsHint());
- mStateController.notifySurfaceTransactionReady(this, getSurfaceTransactionId(leash), true);
ProtoLog.d(WM_DEBUG_WINDOW_INSETS,
"InsetsSource Control %s for target %s", mControl, mControlTarget);
}
- private long getSurfaceTransactionId(SurfaceControl leash) {
- // Here returns mNativeObject (long) as the ID instead of the leash itself so that
- // InsetsStateController won't keep referencing the leash unexpectedly.
- return leash != null ? leash.mNativeObject : 0;
- }
-
- /**
- * This is called when the surface transaction of the leash initialization has been committed.
- *
- * @param id Indicates which transaction is committed so that stale callbacks can be dropped.
- */
- void onSurfaceTransactionCommitted(long id) {
- if (mIsLeashReadyForDispatching) {
- return;
- }
- if (mControl == null) {
- return;
- }
- if (id != getSurfaceTransactionId(mControl.getLeash())) {
- return;
- }
- mIsLeashReadyForDispatching = true;
- mStateController.notifySurfaceTransactionReady(this, 0, false);
- }
-
void startSeamlessRotation() {
if (!mSeamlessRotating) {
mSeamlessRotating = true;
@@ -571,6 +545,10 @@
return true;
}
+ void onSurfaceTransactionApplied() {
+ mIsLeashReadyForDispatching = true;
+ }
+
void setClientVisible(boolean clientVisible) {
if (mClientVisible == clientVisible) {
return;
@@ -755,7 +733,6 @@
public void onAnimationCancelled(SurfaceControl animationLeash) {
if (mAdapter == this) {
mStateController.notifyControlRevoked(mControlTarget, InsetsSourceProvider.this);
- mStateController.notifySurfaceTransactionReady(InsetsSourceProvider.this, 0, false);
mControl = null;
mControlTarget = null;
mAdapter = null;
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index ba578f6..6b9fcf4 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -34,7 +34,6 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.SparseArray;
-import android.util.SparseLongArray;
import android.util.proto.ProtoOutputStream;
import android.view.InsetsSource;
import android.view.InsetsSourceControl;
@@ -59,7 +58,6 @@
private final DisplayContent mDisplayContent;
private final SparseArray<InsetsSourceProvider> mProviders = new SparseArray<>();
- private final SparseLongArray mSurfaceTransactionIds = new SparseLongArray();
private final ArrayMap<InsetsControlTarget, ArrayList<InsetsSourceProvider>>
mControlTargetProvidersMap = new ArrayMap<>();
private final SparseArray<InsetsControlTarget> mIdControlTargetMap = new SparseArray<>();
@@ -362,32 +360,14 @@
notifyPendingInsetsControlChanged();
}
- void notifySurfaceTransactionReady(InsetsSourceProvider provider, long id, boolean ready) {
- if (ready) {
- mSurfaceTransactionIds.put(provider.getSource().getId(), id);
- } else {
- mSurfaceTransactionIds.delete(provider.getSource().getId());
- }
- }
-
private void notifyPendingInsetsControlChanged() {
if (mPendingControlChanged.isEmpty()) {
return;
}
- final int size = mSurfaceTransactionIds.size();
- final SparseLongArray surfaceTransactionIds = new SparseLongArray(size);
- for (int i = 0; i < size; i++) {
- surfaceTransactionIds.append(
- mSurfaceTransactionIds.keyAt(i), mSurfaceTransactionIds.valueAt(i));
- }
mDisplayContent.mWmService.mAnimator.addAfterPrepareSurfacesRunnable(() -> {
- for (int i = 0; i < size; i++) {
- final int sourceId = surfaceTransactionIds.keyAt(i);
- final InsetsSourceProvider provider = mProviders.get(sourceId);
- if (provider == null) {
- continue;
- }
- provider.onSurfaceTransactionCommitted(surfaceTransactionIds.valueAt(i));
+ for (int i = mProviders.size() - 1; i >= 0; i--) {
+ final InsetsSourceProvider provider = mProviders.valueAt(i);
+ provider.onSurfaceTransactionApplied();
}
final ArraySet<InsetsControlTarget> newControlTargets = new ArraySet<>();
int displayId = mDisplayContent.getDisplayId();
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 1353ff0..e16d869 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3751,9 +3751,11 @@
// Boost the adjacent TaskFragment for dimmer if needed.
final TaskFragment taskFragment = wc.asTaskFragment();
if (taskFragment != null && taskFragment.isEmbedded()) {
+ taskFragment.mDimmerSurfaceBoosted = false;
final TaskFragment adjacentTf = taskFragment.getAdjacentTaskFragment();
if (adjacentTf != null && adjacentTf.shouldBoostDimmer()) {
adjacentTf.assignLayer(t, layer++);
+ adjacentTf.mDimmerSurfaceBoosted = true;
}
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 78ababc..a818a72 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -216,6 +216,9 @@
Dimmer mDimmer = Dimmer.DIMMER_REFACTOR
? new SmoothDimmer(this) : new LegacyDimmer(this);
+ /** {@code true} if the dimmer surface is boosted. {@code false} otherwise. */
+ boolean mDimmerSurfaceBoosted;
+
/** Apply the dim layer on the embedded TaskFragment. */
static final int EMBEDDED_DIM_AREA_TASK_FRAGMENT = 0;
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 594043d..9b19a70 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -349,7 +349,7 @@
final Rect lastWallpaperBounds = wallpaperWin.getParentFrame();
int screenWidth = lastWallpaperBounds.width();
int screenHeight = lastWallpaperBounds.height();
- float screenRatio = ((float) screenWidth) / screenHeight;
+ float screenRatio = (float) screenWidth / screenHeight;
Point screenSize = new Point(screenWidth, screenHeight);
WallpaperWindowToken token = wallpaperWin.mToken.asWallpaperToken();
@@ -399,20 +399,32 @@
Point bitmapSize = new Point(
wallpaperWin.mRequestedWidth, wallpaperWin.mRequestedHeight);
SparseArray<Rect> cropHints = token.getCropHints();
- wallpaperFrame = mWallpaperCropUtils.getCrop(
- screenSize, bitmapSize, cropHints, wallpaperWin.isRtl());
+ wallpaperFrame = bitmapSize.x <= 0 || bitmapSize.y <= 0 ? wallpaperWin.getFrame()
+ : mWallpaperCropUtils.getCrop(screenSize, bitmapSize, cropHints,
+ wallpaperWin.isRtl());
+ int frameWidth = wallpaperFrame.width();
+ int frameHeight = wallpaperFrame.height();
+ float frameRatio = (float) frameWidth / frameHeight;
- cropZoom = wallpaperFrame.isEmpty() ? 1f
- : ((float) screenHeight) / wallpaperFrame.height() / wallpaperWin.mVScale;
+ // If the crop is proportionally wider/taller than the screen, scale it so that its
+ // height/width matches the screen height/width, and use the additional width/height
+ // for parallax (respectively).
+ boolean scaleHeight = frameRatio >= screenRatio;
+ cropZoom = wallpaperFrame.isEmpty() ? 1f : scaleHeight
+ ? (float) screenHeight / frameHeight / wallpaperWin.mVScale
+ : (float) screenWidth / frameWidth / wallpaperWin.mHScale;
- // A positive x / y offset shifts the wallpaper to the right / bottom respectively.
- cropOffsetX = -wallpaperFrame.left
- + (int) ((cropZoom - 1f) * wallpaperFrame.height() * screenRatio / 2f);
- cropOffsetY = -wallpaperFrame.top
- + (int) ((cropZoom - 1f) * wallpaperFrame.height() / 2f);
+ // The dimensions of the frame, without the additional width or height for parallax.
+ float w = scaleHeight ? frameHeight * screenRatio : frameWidth;
+ float h = scaleHeight ? frameHeight : frameWidth / screenRatio;
- diffWidth = (int) (wallpaperFrame.width() * wallpaperWin.mHScale) - screenWidth;
- diffHeight = (int) (wallpaperFrame.height() * wallpaperWin.mVScale) - screenHeight;
+ // Note: a positive x/y offset shifts the wallpaper to the right/bottom respectively.
+ cropOffsetX = -wallpaperFrame.left + (int) ((cropZoom - 1f) * w / 2f);
+ cropOffsetY = -wallpaperFrame.top + (int) ((cropZoom - 1f) * h / 2f);
+
+ // Available width or height for parallax
+ diffWidth = (int) ((frameWidth - w) * wallpaperWin.mHScale);
+ diffHeight = (int) ((frameHeight - h) * wallpaperWin.mVScale);
} else {
wallpaperFrame = wallpaperWin.getFrame();
cropZoom = 1f;
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 5e7f1cb..b43a454 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -28,7 +28,6 @@
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.content.Context;
-import android.os.HandlerExecutor;
import android.os.Trace;
import android.util.Slog;
import android.util.TimeUtils;
@@ -70,8 +69,6 @@
private Choreographer mChoreographer;
- private final HandlerExecutor mExecutor;
-
/**
* Indicates whether we have an animation frame callback scheduled, which will happen at
* vsync-app and then schedule the animation tick at the right time (vsync-sf).
@@ -83,7 +80,8 @@
* A list of runnable that need to be run after {@link WindowContainer#prepareSurfaces} is
* executed and the corresponding transaction is closed and applied.
*/
- private ArrayList<Runnable> mAfterPrepareSurfacesRunnables = new ArrayList<>();
+ private final ArrayList<Runnable> mAfterPrepareSurfacesRunnables = new ArrayList<>();
+ private boolean mInExecuteAfterPrepareSurfacesRunnables;
private final SurfaceControl.Transaction mTransaction;
@@ -94,7 +92,6 @@
mTransaction = service.mTransactionFactory.get();
service.mAnimationHandler.runWithScissors(
() -> mChoreographer = Choreographer.getSfInstance(), 0 /* timeout */);
- mExecutor = new HandlerExecutor(service.mAnimationHandler);
mAnimationFrameCallback = frameTimeNs -> {
synchronized (mService.mGlobalLock) {
@@ -200,19 +197,6 @@
updateRunningExpensiveAnimationsLegacy();
}
- final ArrayList<Runnable> afterPrepareSurfacesRunnables = mAfterPrepareSurfacesRunnables;
- if (!afterPrepareSurfacesRunnables.isEmpty()) {
- mAfterPrepareSurfacesRunnables = new ArrayList<>();
- mTransaction.addTransactionCommittedListener(mExecutor, () -> {
- synchronized (mService.mGlobalLock) {
- // Traverse in order they were added.
- for (int i = 0, size = afterPrepareSurfacesRunnables.size(); i < size; i++) {
- afterPrepareSurfacesRunnables.get(i).run();
- }
- afterPrepareSurfacesRunnables.clear();
- }
- });
- }
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "applyTransaction");
mTransaction.apply();
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
@@ -220,6 +204,7 @@
ProtoLog.i(WM_SHOW_TRANSACTIONS, "<<< CLOSE TRANSACTION animate");
mService.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
+ executeAfterPrepareSurfacesRunnables();
if (DEBUG_WINDOW_TRACE) {
Slog.i(TAG, "!!! animate: exit"
@@ -301,10 +286,34 @@
/**
* Adds a runnable to be executed after {@link WindowContainer#prepareSurfaces} is called and
- * the corresponding transaction is closed, applied, and committed.
+ * the corresponding transaction is closed and applied.
*/
void addAfterPrepareSurfacesRunnable(Runnable r) {
+ // If runnables are already being handled in executeAfterPrepareSurfacesRunnable, then just
+ // immediately execute the runnable passed in.
+ if (mInExecuteAfterPrepareSurfacesRunnables) {
+ r.run();
+ return;
+ }
+
mAfterPrepareSurfacesRunnables.add(r);
scheduleAnimation();
}
+
+ void executeAfterPrepareSurfacesRunnables() {
+
+ // Don't even think about to start recursing!
+ if (mInExecuteAfterPrepareSurfacesRunnables) {
+ return;
+ }
+ mInExecuteAfterPrepareSurfacesRunnables = true;
+
+ // Traverse in order they were added.
+ final int size = mAfterPrepareSurfacesRunnables.size();
+ for (int i = 0; i < size; i++) {
+ mAfterPrepareSurfacesRunnables.get(i).run();
+ }
+ mAfterPrepareSurfacesRunnables.clear();
+ mInExecuteAfterPrepareSurfacesRunnables = false;
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index ae5a5cb..a055db2 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2109,7 +2109,15 @@
+ ", touchableRegion=" + w.mGivenTouchableRegion + " -> " + touchableRegion
+ ", touchableInsets " + w.mTouchableInsets + " -> " + touchableInsets);
if (w != null) {
+ final boolean wasGivenInsetsPending = w.mGivenInsetsPending;
w.mGivenInsetsPending = false;
+ if ((!wasGivenInsetsPending || !w.hasInsetsSourceProvider())
+ && w.mTouchableInsets == touchableInsets
+ && w.mGivenContentInsets.equals(contentInsets)
+ && w.mGivenVisibleInsets.equals(visibleInsets)
+ && w.mGivenTouchableRegion.equals(touchableRegion)) {
+ return;
+ }
w.mGivenContentInsets.set(contentInsets);
w.mGivenVisibleInsets.set(visibleInsets);
w.mGivenTouchableRegion.set(touchableRegion);
@@ -9214,6 +9222,11 @@
return false;
}
+ if (taskFragment.mDimmerSurfaceBoosted) {
+ // Skip if the TaskFragment currently has dimmer surface boosted.
+ return false;
+ }
+
final ActivityRecord topActivity =
taskFragment.getTask().topRunningActivity(true /* focusableOnly */);
if (topActivity == null || topActivity == focusedWindow.mActivityRecord) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 18ac0e7..1106a95 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1342,7 +1342,7 @@
// This window doesn't provide any insets.
return;
}
- if (mGivenInsetsPending && mAttrs.providedInsets == null) {
+ if (mGivenInsetsPending) {
// The given insets are pending, and they are not reliable for now. The source frame
// should be updated after the new given insets are sent to window manager.
return;
diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java
index 6d5fc80..b0e71bd 100644
--- a/services/core/java/com/android/server/wm/WindowTracing.java
+++ b/services/core/java/com/android/server/wm/WindowTracing.java
@@ -109,6 +109,9 @@
return;
}
synchronized (mEnabledLock) {
+ if (!android.tracing.Flags.perfettoProtologTracing()) {
+ ((LegacyProtoLogImpl) ProtoLog.getSingleInstance()).startProtoLog(pw);
+ }
logAndPrintln(pw, "Start tracing to " + mTraceFile + ".");
mBuffer.resetBuffer();
mEnabled = mEnabledLockFree = true;
@@ -136,6 +139,9 @@
writeTraceToFileLocked();
logAndPrintln(pw, "Trace written to " + mTraceFile + ".");
}
+ if (!android.tracing.Flags.perfettoProtologTracing()) {
+ ((LegacyProtoLogImpl) ProtoLog.getSingleInstance()).stopProtoLog(pw, true);
+ }
}
/**
diff --git a/services/core/jni/com_android_server_hint_HintManagerService.cpp b/services/core/jni/com_android_server_hint_HintManagerService.cpp
index ccd9bd0..b2bdaa3 100644
--- a/services/core/jni/com_android_server_hint_HintManagerService.cpp
+++ b/services/core/jni/com_android_server_hint_HintManagerService.cpp
@@ -24,16 +24,17 @@
#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedPrimitiveArray.h>
#include <powermanager/PowerHalController.h>
+#include <powermanager/PowerHintSessionWrapper.h>
#include <utils/Log.h>
#include <unordered_map>
#include "jni.h"
-using aidl::android::hardware::power::IPowerHintSession;
using aidl::android::hardware::power::SessionHint;
using aidl::android::hardware::power::SessionMode;
using aidl::android::hardware::power::WorkDuration;
+using android::power::PowerHintSessionWrapper;
using android::base::StringPrintf;
@@ -49,7 +50,7 @@
} gWorkDurationInfo;
static power::PowerHalController gPowerHalController;
-static std::unordered_map<jlong, std::shared_ptr<IPowerHintSession>> gSessionMap;
+static std::unordered_map<jlong, std::shared_ptr<PowerHintSessionWrapper>> gSessionMap;
static std::mutex gSessionMapLock;
static int64_t getHintSessionPreferredRate() {
@@ -76,45 +77,45 @@
}
static void pauseHintSession(JNIEnv* env, int64_t session_ptr) {
- auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+ auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
appSession->pause();
}
static void resumeHintSession(JNIEnv* env, int64_t session_ptr) {
- auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+ auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
appSession->resume();
}
static void closeHintSession(JNIEnv* env, int64_t session_ptr) {
- auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+ auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
appSession->close();
std::unique_lock<std::mutex> sessionLock(gSessionMapLock);
gSessionMap.erase(session_ptr);
}
static void updateTargetWorkDuration(int64_t session_ptr, int64_t targetDurationNanos) {
- auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+ auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
appSession->updateTargetWorkDuration(targetDurationNanos);
}
static void reportActualWorkDuration(int64_t session_ptr,
const std::vector<WorkDuration>& actualDurations) {
- auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+ auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
appSession->reportActualWorkDuration(actualDurations);
}
static void sendHint(int64_t session_ptr, SessionHint hint) {
- auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+ auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
appSession->sendHint(hint);
}
static void setThreads(int64_t session_ptr, const std::vector<int32_t>& threadIds) {
- auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+ auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
appSession->setThreads(threadIds);
}
static void setMode(int64_t session_ptr, SessionMode mode, bool enabled) {
- auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+ auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
appSession->setMode(mode, enabled);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 80046b60..c37946b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -23389,7 +23389,7 @@
DEFAULT_VALUE_PERMISSION_BASED_ACCESS_FLAG);
}
- private boolean isUnicornFlagEnabled() {
+ static boolean isUnicornFlagEnabled() {
return false;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index c108deaf..a7adc5b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -66,6 +66,10 @@
private static final String LOG_TAG = "PolicyEnforcerCallbacks";
static boolean setAutoTimezoneEnabled(@Nullable Boolean enabled, @NonNull Context context) {
+ if (!DevicePolicyManagerService.isUnicornFlagEnabled()) {
+ Slogf.w(LOG_TAG, "Trying to enforce setAutoTimezoneEnabled while flag is off.");
+ return true;
+ }
return Binder.withCleanCallingIdentity(() -> {
Objects.requireNonNull(context);
@@ -79,6 +83,10 @@
static boolean setPermissionGrantState(
@Nullable Integer grantState, @NonNull Context context, int userId,
@NonNull PolicyKey policyKey) {
+ if (!DevicePolicyManagerService.isUnicornFlagEnabled()) {
+ Slogf.w(LOG_TAG, "Trying to enforce setPermissionGrantState while flag is off.");
+ return true;
+ }
return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> {
if (!(policyKey instanceof PackagePermissionPolicyKey)) {
throw new IllegalArgumentException("policyKey is not of type "
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java
index 82d5247..209107e 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java
@@ -37,6 +37,7 @@
import android.util.ArraySet;
import android.util.Dumpable;
import android.view.Display;
+import android.view.DisplayInfo;
import android.view.Surface;
import com.android.server.policy.BookStylePreferredScreenCalculator.PreferredScreen;
@@ -65,6 +66,7 @@
private final Handler mHandler = new Handler();
private final PostureEstimator mPostureEstimator;
private final DisplayManager mDisplayManager;
+ private final DisplayInfo mDefaultDisplayInfo = new DisplayInfo();
/**
* Creates {@link BookStyleClosedStatePredicate}. It is expected that the device has a pair
@@ -140,10 +142,11 @@
public void onDisplayChanged(int displayId) {
if (displayId == DEFAULT_DISPLAY) {
final Display display = mDisplayManager.getDisplay(displayId);
+ display.getDisplayInfo(mDefaultDisplayInfo);
int displayState = display.getState();
boolean isDisplayOn = displayState == Display.STATE_ON;
mPostureEstimator.onDisplayPowerStatusChanged(isDisplayOn);
- mPostureEstimator.onDisplayRotationChanged(display.getRotation());
+ mPostureEstimator.onDisplayRotationChanged(mDefaultDisplayInfo.rotation);
}
}
diff --git a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java
index 8d01b7a..901f24d 100644
--- a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java
+++ b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java
@@ -48,6 +48,7 @@
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
import android.view.Display;
+import android.view.DisplayInfo;
import android.view.Surface;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -629,7 +630,11 @@
}
private void sendScreenRotation(int rotation) {
- when(mDisplay.getRotation()).thenReturn(rotation);
+ doAnswer(invocation -> {
+ final DisplayInfo displayInfo = invocation.getArgument(0);
+ displayInfo.rotation = rotation;
+ return null;
+ }).when(mDisplay).getDisplayInfo(any());
mDisplayListenerCaptor.getAllValues().forEach((l) -> l.onDisplayChanged(DEFAULT_DISPLAY));
}
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
index fc2eb26..c0d988d 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
@@ -20,27 +20,63 @@
import android.companion.virtual.VirtualDeviceManager
import android.os.Handler
import android.os.UserHandle
+import android.permission.flags.Flags
import android.util.ArrayMap
import android.util.ArraySet
+import android.util.LongSparseArray
+import android.util.Slog
+import android.util.SparseArray
import android.util.SparseBooleanArray
import android.util.SparseIntArray
import com.android.internal.annotations.VisibleForTesting
+import com.android.internal.util.IntPair
import com.android.server.appop.AppOpsCheckingServiceInterface
import com.android.server.appop.AppOpsCheckingServiceInterface.AppOpsModeChangedListener
import com.android.server.permission.access.AccessCheckingService
import com.android.server.permission.access.AppOpUri
+import com.android.server.permission.access.DevicePermissionUri
+import com.android.server.permission.access.GetStateScope
import com.android.server.permission.access.PackageUri
+import com.android.server.permission.access.PermissionUri
import com.android.server.permission.access.UidUri
+import com.android.server.permission.access.appop.AppOpModes.MODE_ALLOWED
+import com.android.server.permission.access.appop.AppOpModes.MODE_FOREGROUND
+import com.android.server.permission.access.appop.AppOpModes.MODE_IGNORED
import com.android.server.permission.access.collection.forEachIndexed
import com.android.server.permission.access.collection.set
+import com.android.server.permission.access.permission.AppIdPermissionPolicy
+import com.android.server.permission.access.permission.DevicePermissionPolicy
+import com.android.server.permission.access.permission.PermissionFlags
+import com.android.server.permission.access.permission.PermissionService
class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingServiceInterface {
private val packagePolicy =
service.getSchemePolicy(PackageUri.SCHEME, AppOpUri.SCHEME) as PackageAppOpPolicy
private val appIdPolicy =
service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME) as AppIdAppOpPolicy
+ private val permissionPolicy =
+ service.getSchemePolicy(UidUri.SCHEME, PermissionUri.SCHEME) as AppIdPermissionPolicy
+ private val devicePermissionPolicy =
+ service.getSchemePolicy(UidUri.SCHEME, DevicePermissionUri.SCHEME) as DevicePermissionPolicy
private val context = service.context
+
+ // Maps appop code to its runtime permission
+ private val runtimeAppOpToPermissionNames = SparseArray<String>()
+
+ // Maps runtime permission to its appop codes
+ private val runtimePermissionNameToAppOp = ArrayMap<String, Int>()
+
+ private var foregroundableOps = SparseBooleanArray()
+
+ /* Maps foreground permissions to their background permission. Background permissions aren't
+ required to be runtime */
+ private val foregroundToBackgroundPermissionName = ArrayMap<String, String>()
+
+ /* Maps background permissions to their foreground permissions. Background permissions aren't
+ required to be runtime */
+ private val backgroundToForegroundPermissionNames = ArrayMap<String, ArraySet<String>>()
+
private lateinit var handler: Handler
@Volatile private var listeners = ArraySet<AppOpsModeChangedListener>()
@@ -69,11 +105,60 @@
}
override fun systemReady() {
- // Not implemented because upgrades are handled automatically.
+ if (Flags.runtimePermissionAppopsMappingEnabled()) {
+ createPermissionAppOpMapping()
+ val permissionListener = OnPermissionFlagsChangedListener()
+ permissionPolicy.addOnPermissionFlagsChangedListener(permissionListener)
+ devicePermissionPolicy.addOnPermissionFlagsChangedListener(permissionListener)
+ }
+ }
+
+ private fun createPermissionAppOpMapping() {
+ val permissions = service.getState { with(permissionPolicy) { getPermissions() } }
+
+ for (appOpCode in 0 until AppOpsManager._NUM_OP) {
+ AppOpsManager.opToPermission(appOpCode)?.let { permissionName ->
+ // Multiple ops might map to a single permission but only one is considered the
+ // runtime appop calculations.
+ if (appOpCode == AppOpsManager.permissionToOpCode(permissionName)) {
+ val permission = permissions[permissionName]!!
+ if (permission.isRuntime) {
+ runtimePermissionNameToAppOp[permissionName] = appOpCode
+ runtimeAppOpToPermissionNames[appOpCode] = permissionName
+ permission.permissionInfo.backgroundPermission?.let {
+ backgroundPermissionName ->
+ // Note: background permission may not be runtime,
+ // e.g. microphone/camera.
+ foregroundableOps[appOpCode] = true
+ foregroundToBackgroundPermissionName[permissionName] =
+ backgroundPermissionName
+ backgroundToForegroundPermissionNames
+ .getOrPut(backgroundPermissionName, ::ArraySet)
+ .add(permissionName)
+ }
+ }
+ }
+ }
+ }
}
override fun getNonDefaultUidModes(uid: Int, persistentDeviceId: String): SparseIntArray {
- return opNameMapToOpSparseArray(getUidModes(uid))
+ val appId = UserHandle.getAppId(uid)
+ val userId = UserHandle.getUserId(uid)
+ service.getState {
+ val modes =
+ with(appIdPolicy) { opNameMapToOpSparseArray(getAppOpModes(appId, userId)?.map) }
+ if (Flags.runtimePermissionAppopsMappingEnabled()) {
+ runtimePermissionNameToAppOp.forEachIndexed { _, permissionName, appOpCode ->
+ val mode = getUidModeFromPermissionState(appId, userId, permissionName)
+ if (mode != AppOpsManager.opToDefaultMode(appOpCode)) {
+ modes[appOpCode] = mode
+ }
+ }
+ }
+
+ return modes
+ }
}
override fun getNonDefaultPackageModes(packageName: String, userId: Int): SparseIntArray {
@@ -84,7 +169,13 @@
val appId = UserHandle.getAppId(uid)
val userId = UserHandle.getUserId(uid)
val opName = AppOpsManager.opToPublicName(op)
- return service.getState { with(appIdPolicy) { getAppOpMode(appId, userId, opName) } }
+ val permissionName = runtimeAppOpToPermissionNames[op]
+
+ return if (!Flags.runtimePermissionAppopsMappingEnabled() || permissionName == null) {
+ service.getState { with(appIdPolicy) { getAppOpMode(appId, userId, opName) } }
+ } else {
+ service.getState { getUidModeFromPermissionState(appId, userId, permissionName) }
+ }
}
private fun getUidModes(uid: Int): ArrayMap<String, Int>? {
@@ -93,13 +184,66 @@
return service.getState { with(appIdPolicy) { getAppOpModes(appId, userId) } }?.map
}
- override fun setUidMode(uid: Int, persistentDeviceId: String, op: Int, mode: Int): Boolean {
+ private fun GetStateScope.getUidModeFromPermissionState(
+ appId: Int,
+ userId: Int,
+ permissionName: String
+ ): Int {
+ val permissionFlags =
+ with(permissionPolicy) { getPermissionFlags(appId, userId, permissionName) }
+ val backgroundPermissionName = foregroundToBackgroundPermissionName[permissionName]
+ val backgroundPermissionFlags =
+ if (backgroundPermissionName != null) {
+ with(permissionPolicy) {
+ getPermissionFlags(appId, userId, backgroundPermissionName)
+ }
+ } else {
+ PermissionFlags.RUNTIME_GRANTED
+ }
+ val result = evaluateModeFromPermissionFlags(permissionFlags, backgroundPermissionFlags)
+ if (result != MODE_IGNORED) {
+ return result
+ }
+
+ val fullerPermissionName =
+ PermissionService.getFullerPermission(permissionName) ?: return result
+ return getUidModeFromPermissionState(appId, userId, fullerPermissionName)
+ }
+
+ private fun evaluateModeFromPermissionFlags(
+ foregroundFlags: Int,
+ backgroundFlags: Int = PermissionFlags.RUNTIME_GRANTED
+ ): Int =
+ if (PermissionFlags.isAppOpGranted(foregroundFlags)) {
+ if (PermissionFlags.isAppOpGranted(backgroundFlags)) {
+ MODE_ALLOWED
+ } else {
+ MODE_FOREGROUND
+ }
+ } else {
+ MODE_IGNORED
+ }
+
+ override fun setUidMode(uid: Int, persistentDeviceId: String, code: Int, mode: Int): Boolean {
+ if (
+ Flags.runtimePermissionAppopsMappingEnabled() && code in runtimeAppOpToPermissionNames
+ ) {
+ Slog.w(
+ LOG_TAG,
+ "Cannot set UID mode for runtime permission app op, uid = $uid," +
+ " code = ${AppOpsManager.opToName(code)}," +
+ " mode = ${AppOpsManager.modeToName(mode)}",
+ RuntimeException()
+ )
+ return false
+ }
+
val appId = UserHandle.getAppId(uid)
val userId = UserHandle.getUserId(uid)
- val opName = AppOpsManager.opToPublicName(op)
- var wasChanged = false
+ val appOpName = AppOpsManager.opToPublicName(code)
+ var wasChanged: Boolean
service.mutateState {
- wasChanged = with(appIdPolicy) { setAppOpMode(appId, userId, opName, mode) }
+ wasChanged = with(appIdPolicy) { setAppOpMode(appId, userId, appOpName, mode) }
}
return wasChanged
}
@@ -114,10 +258,23 @@
private fun getPackageModes(packageName: String, userId: Int): ArrayMap<String, Int>? =
service.getState { with(packagePolicy) { getAppOpModes(packageName, userId) } }?.map
- override fun setPackageMode(packageName: String, op: Int, mode: Int, userId: Int) {
- val opName = AppOpsManager.opToPublicName(op)
+ override fun setPackageMode(packageName: String, appOpCode: Int, mode: Int, userId: Int) {
+ val appOpName = AppOpsManager.opToPublicName(appOpCode)
+
+ if (
+ Flags.runtimePermissionAppopsMappingEnabled() &&
+ appOpCode in runtimeAppOpToPermissionNames
+ ) {
+ Slog.w(
+ LOG_TAG,
+ "(packageName=$packageName, userId=$userId)'s appop state" +
+ " for runtime op $appOpName should not be set directly.",
+ RuntimeException()
+ )
+ return
+ }
service.mutateState {
- with(packagePolicy) { setAppOpMode(packageName, userId, opName, mode) }
+ with(packagePolicy) { setAppOpMode(packageName, userId, appOpName, mode) }
}
}
@@ -128,7 +285,7 @@
}
override fun removePackage(packageName: String, userId: Int): Boolean {
- var wasChanged = false
+ var wasChanged: Boolean
service.mutateState {
wasChanged = with(packagePolicy) { removeAppOpModes(packageName, userId) }
}
@@ -158,6 +315,13 @@
this[AppOpsManager.strOpToOp(op)] = true
}
}
+ if (Flags.runtimePermissionAppopsMappingEnabled()) {
+ foregroundableOps.forEachIndexed { _, op, _ ->
+ if (getUidMode(uid, persistentDeviceId, op) == AppOpsManager.MODE_FOREGROUND) {
+ this[op] = true
+ }
+ }
+ }
}
}
@@ -168,6 +332,13 @@
this[AppOpsManager.strOpToOp(op)] = true
}
}
+ if (Flags.runtimePermissionAppopsMappingEnabled()) {
+ foregroundableOps.forEachIndexed { _, op, _ ->
+ if (getPackageMode(packageName, op, userId) == AppOpsManager.MODE_FOREGROUND) {
+ this[op] = true
+ }
+ }
+ }
}
}
@@ -189,9 +360,10 @@
}
}
- inner class OnAppIdAppOpModeChangedListener : AppIdAppOpPolicy.OnAppOpModeChangedListener() {
+ private inner class OnAppIdAppOpModeChangedListener :
+ AppIdAppOpPolicy.OnAppOpModeChangedListener() {
// (uid, appOpCode) -> newMode
- val pendingChanges = ArrayMap<Pair<Int, Int>, Int>()
+ private val pendingChanges = LongSparseArray<Int>()
override fun onAppOpModeChanged(
appId: Int,
@@ -202,7 +374,7 @@
) {
val uid = UserHandle.getUid(userId, appId)
val appOpCode = AppOpsManager.strOpToOp(appOpName)
- val key = Pair(uid, appOpCode)
+ val key = IntPair.of(uid, appOpCode)
pendingChanges[key] = newMode
}
@@ -211,13 +383,15 @@
val listenersLocal = listeners
pendingChanges.forEachIndexed { _, key, mode ->
listenersLocal.forEachIndexed { _, listener ->
- val uid = key.first
- val appOpCode = key.second
+ val uid = IntPair.first(key)
+ val appOpCode = IntPair.second(key)
- listener.onUidModeChanged(uid,
+ listener.onUidModeChanged(
+ uid,
appOpCode,
mode,
- VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT)
+ VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT
+ )
}
}
@@ -228,7 +402,7 @@
private inner class OnPackageAppOpModeChangedListener :
PackageAppOpPolicy.OnAppOpModeChangedListener() {
// (packageName, userId, appOpCode) -> newMode
- val pendingChanges = ArrayMap<Triple<String, Int, Int>, Int>()
+ private val pendingChanges = ArrayMap<Triple<String, Int, Int>, Int>()
override fun onAppOpModeChanged(
packageName: String,
@@ -258,4 +432,130 @@
pendingChanges.clear()
}
}
+
+ private inner class OnPermissionFlagsChangedListener :
+ AppIdPermissionPolicy.OnPermissionFlagsChangedListener,
+ DevicePermissionPolicy.OnDevicePermissionFlagsChangedListener {
+ // (uid, deviceId, appOpCode) -> newMode
+ private val pendingChanges = ArrayMap<Triple<Int, String, Int>, Int>()
+
+ override fun onPermissionFlagsChanged(
+ appId: Int,
+ userId: Int,
+ permissionName: String,
+ oldFlags: Int,
+ newFlags: Int
+ ) {
+ onDevicePermissionFlagsChanged(
+ appId,
+ userId,
+ VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT,
+ permissionName,
+ oldFlags,
+ newFlags
+ )
+ }
+
+ override fun onDevicePermissionFlagsChanged(
+ appId: Int,
+ userId: Int,
+ deviceId: String,
+ permissionName: String,
+ oldFlags: Int,
+ newFlags: Int
+ ) {
+ backgroundToForegroundPermissionNames[permissionName]?.let { foregroundPermissions ->
+ // This is a background permission; there may be multiple foreground permissions
+ // affected.
+ foregroundPermissions.forEachIndexed { _, foregroundPermissionName ->
+ runtimePermissionNameToAppOp[foregroundPermissionName]?.let { appOpCode ->
+ val foregroundPermissionFlags =
+ getPermissionFlags(appId, userId, foregroundPermissionName)
+ addPendingChangedModeIfNeeded(
+ appId,
+ userId,
+ deviceId,
+ appOpCode,
+ foregroundPermissionFlags,
+ oldFlags,
+ foregroundPermissionFlags,
+ newFlags
+ )
+ }
+ }
+ }
+ ?: foregroundToBackgroundPermissionName[permissionName]?.let { backgroundPermission
+ ->
+ runtimePermissionNameToAppOp[permissionName]?.let { appOpCode ->
+ val backgroundPermissionFlags =
+ getPermissionFlags(appId, userId, backgroundPermission)
+ addPendingChangedModeIfNeeded(
+ appId,
+ userId,
+ deviceId,
+ appOpCode,
+ oldFlags,
+ backgroundPermissionFlags,
+ newFlags,
+ backgroundPermissionFlags
+ )
+ }
+ }
+ ?: runtimePermissionNameToAppOp[permissionName]?.let { appOpCode ->
+ addPendingChangedModeIfNeeded(
+ appId,
+ userId,
+ deviceId,
+ appOpCode,
+ oldFlags,
+ PermissionFlags.RUNTIME_GRANTED,
+ newFlags,
+ PermissionFlags.RUNTIME_GRANTED
+ )
+ }
+ }
+
+ private fun getPermissionFlags(appId: Int, userId: Int, permissionName: String): Int =
+ service.getState {
+ with(permissionPolicy) { getPermissionFlags(appId, userId, permissionName) }
+ }
+
+ private fun addPendingChangedModeIfNeeded(
+ appId: Int,
+ userId: Int,
+ deviceId: String,
+ appOpCode: Int,
+ oldForegroundFlags: Int,
+ oldBackgroundFlags: Int,
+ newForegroundFlags: Int,
+ newBackgroundFlags: Int,
+ ) {
+ val oldMode = evaluateModeFromPermissionFlags(oldForegroundFlags, oldBackgroundFlags)
+ val newMode = evaluateModeFromPermissionFlags(newForegroundFlags, newBackgroundFlags)
+
+ if (oldMode != newMode) {
+ val uid = UserHandle.getUid(userId, appId)
+ pendingChanges[Triple(uid, deviceId, appOpCode)] = newMode
+ }
+ }
+
+ override fun onStateMutated() {
+ val listenersLocal = listeners
+ pendingChanges.forEachIndexed { _, key, mode ->
+ listenersLocal.forEachIndexed { _, listener ->
+ val uid = key.first
+ val deviceId = key.second
+ val appOpCode = key.third
+
+ listener.onUidModeChanged(uid, appOpCode, mode, deviceId)
+ }
+ }
+
+ pendingChanges.clear()
+ }
+ }
+
+ companion object {
+ private val LOG_TAG = AppOpService::class.java.simpleName
+ }
}
diff --git a/services/permission/java/com/android/server/permission/access/collection/LongSparseArrayExtensions.kt b/services/permission/java/com/android/server/permission/access/collection/LongSparseArrayExtensions.kt
new file mode 100644
index 0000000..827dd0e
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/collection/LongSparseArrayExtensions.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.access.collection
+
+import android.util.LongSparseArray
+
+inline fun <T> LongSparseArray<T>.allIndexed(predicate: (Int, Long, T) -> Boolean): Boolean {
+ forEachIndexed { index, key, value ->
+ if (!predicate(index, key, value)) {
+ return false
+ }
+ }
+ return true
+}
+
+inline fun <T> LongSparseArray<T>.anyIndexed(predicate: (Int, Long, T) -> Boolean): Boolean {
+ forEachIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
+ return true
+ }
+ }
+ return false
+}
+
+inline fun <T> LongSparseArray<T>.forEachIndexed(action: (Int, Long, T) -> Unit) {
+ for (index in 0 until size) {
+ action(index, keyAt(index), valueAt(index))
+ }
+}
+
+inline fun <T> LongSparseArray<T>.forEachReversedIndexed(action: (Int, Long, T) -> Unit) {
+ for (index in lastIndex downTo 0) {
+ action(index, keyAt(index), valueAt(index))
+ }
+}
+
+inline fun <T> LongSparseArray<T>.getOrPut(key: Long, defaultValue: () -> T): T {
+ val index = indexOfKey(key)
+ return if (index >= 0) {
+ valueAt(index)
+ } else {
+ defaultValue().also { put(key, it) }
+ }
+}
+
+inline val <T> LongSparseArray<T>.lastIndex: Int
+ get() = size - 1
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> LongSparseArray<T>.minusAssign(key: Long) {
+ delete(key)
+}
+
+inline fun <T> LongSparseArray<T>.noneIndexed(predicate: (Int, Long, T) -> Boolean): Boolean {
+ forEachIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
+ return false
+ }
+ }
+ return true
+}
+
+inline fun <T> LongSparseArray<T>.removeAllIndexed(predicate: (Int, Long, T) -> Boolean): Boolean {
+ var isChanged = false
+ forEachReversedIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
+ removeAt(index)
+ isChanged = true
+ }
+ }
+ return isChanged
+}
+
+inline fun <T> LongSparseArray<T>.retainAllIndexed(predicate: (Int, Long, T) -> Boolean): Boolean {
+ var isChanged = false
+ forEachReversedIndexed { index, key, value ->
+ if (!predicate(index, key, value)) {
+ removeAt(index)
+ isChanged = true
+ }
+ }
+ return isChanged
+}
+
+inline val <T> LongSparseArray<T>.size: Int
+ get() = size()
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> LongSparseArray<T>.set(key: Long, value: T) {
+ put(key, value)
+}
diff --git a/services/permission/java/com/android/server/permission/access/collection/SparseIntArrayExtensions.kt b/services/permission/java/com/android/server/permission/access/collection/SparseIntArrayExtensions.kt
new file mode 100644
index 0000000..a582431
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/collection/SparseIntArrayExtensions.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.access.collection
+
+import android.util.SparseIntArray
+
+inline fun SparseIntArray.allIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean {
+ forEachIndexed { index, key, value ->
+ if (!predicate(index, key, value)) {
+ return false
+ }
+ }
+ return true
+}
+
+inline fun SparseIntArray.anyIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean {
+ forEachIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
+ return true
+ }
+ }
+ return false
+}
+
+inline fun SparseIntArray.forEachIndexed(action: (Int, Int, Int) -> Unit) {
+ for (index in 0 until size) {
+ action(index, keyAt(index), valueAt(index))
+ }
+}
+
+inline fun SparseIntArray.forEachReversedIndexed(action: (Int, Int, Int) -> Unit) {
+ for (index in lastIndex downTo 0) {
+ action(index, keyAt(index), valueAt(index))
+ }
+}
+
+inline fun SparseIntArray.getOrPut(key: Int, defaultValue: () -> Int): Int {
+ val index = indexOfKey(key)
+ return if (index >= 0) {
+ valueAt(index)
+ } else {
+ defaultValue().also { put(key, it) }
+ }
+}
+
+inline val SparseIntArray.lastIndex: Int
+ get() = size - 1
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun SparseIntArray.minusAssign(key: Int) {
+ delete(key)
+}
+
+inline fun SparseIntArray.noneIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean {
+ forEachIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
+ return false
+ }
+ }
+ return true
+}
+
+fun SparseIntArray.remove(key: Int) {
+ delete(key)
+}
+
+fun SparseIntArray.remove(key: Int, defaultValue: Int): Int {
+ val index = indexOfKey(key)
+ return if (index >= 0) {
+ val oldValue = valueAt(index)
+ removeAt(index)
+ oldValue
+ } else {
+ defaultValue
+ }
+}
+
+inline fun SparseIntArray.removeAllIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean {
+ var isChanged = false
+ forEachReversedIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
+ removeAt(index)
+ isChanged = true
+ }
+ }
+ return isChanged
+}
+
+inline fun SparseIntArray.retainAllIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean {
+ var isChanged = false
+ forEachReversedIndexed { index, key, value ->
+ if (!predicate(index, key, value)) {
+ removeAt(index)
+ isChanged = true
+ }
+ }
+ return isChanged
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun SparseIntArray.set(key: Int, value: Int) {
+ put(key, value)
+}
+
+inline val SparseIntArray.size: Int
+ get() = size()
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index 1f65463..0b58543 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -2826,5 +2826,8 @@
} else {
emptySet<String>()
}
+
+ fun getFullerPermission(permissionName: String): String? =
+ FULLER_PERMISSIONS[permissionName]
}
}
diff --git a/services/tests/VpnTests/java/android/net/Ikev2VpnProfileTest.java b/services/tests/VpnTests/java/android/net/Ikev2VpnProfileTest.java
new file mode 100644
index 0000000..180f54e
--- /dev/null
+++ b/services/tests/VpnTests/java/android/net/Ikev2VpnProfileTest.java
@@ -0,0 +1,563 @@
+/*
+ * Copyright (C) 2019 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.net;
+
+import static android.net.cts.util.IkeSessionTestUtils.CHILD_PARAMS;
+import static android.net.cts.util.IkeSessionTestUtils.IKE_PARAMS_V6;
+import static android.net.cts.util.IkeSessionTestUtils.getTestIkeSessionParams;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.net.ipsec.ike.IkeKeyIdIdentification;
+import android.net.ipsec.ike.IkeTunnelConnectionParams;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.net.VpnProfile;
+import com.android.internal.org.bouncycastle.x509.X509V1CertificateGenerator;
+import com.android.net.module.util.ProxyUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import javax.security.auth.x500.X500Principal;
+
+/** Unit tests for {@link Ikev2VpnProfile.Builder}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class Ikev2VpnProfileTest {
+ private static final String SERVER_ADDR_STRING = "1.2.3.4";
+ private static final String IDENTITY_STRING = "Identity";
+ private static final String USERNAME_STRING = "username";
+ private static final String PASSWORD_STRING = "pa55w0rd";
+ private static final String EXCL_LIST = "exclList";
+ private static final byte[] PSK_BYTES = "preSharedKey".getBytes();
+ private static final int TEST_MTU = 1300;
+
+ private final ProxyInfo mProxy = ProxyInfo.buildDirectProxy(
+ SERVER_ADDR_STRING, -1, ProxyUtils.exclusionStringAsList(EXCL_LIST));
+
+ private X509Certificate mUserCert;
+ private X509Certificate mServerRootCa;
+ private PrivateKey mPrivateKey;
+
+ @Before
+ public void setUp() throws Exception {
+ mServerRootCa = generateRandomCertAndKeyPair().cert;
+
+ final CertificateAndKey userCertKey = generateRandomCertAndKeyPair();
+ mUserCert = userCertKey.cert;
+ mPrivateKey = userCertKey.key;
+ }
+
+ private Ikev2VpnProfile.Builder getBuilderWithDefaultOptions() {
+ final Ikev2VpnProfile.Builder builder =
+ new Ikev2VpnProfile.Builder(SERVER_ADDR_STRING, IDENTITY_STRING);
+
+ builder.setBypassable(true);
+ builder.setProxy(mProxy);
+ builder.setMaxMtu(TEST_MTU);
+ builder.setMetered(true);
+
+ return builder;
+ }
+
+ @Test
+ public void testBuildValidProfileWithOptions() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
+ final Ikev2VpnProfile profile = builder.build();
+ assertNotNull(profile);
+
+ // Check non-auth parameters correctly stored
+ assertEquals(SERVER_ADDR_STRING, profile.getServerAddr());
+ assertEquals(IDENTITY_STRING, profile.getUserIdentity());
+ assertEquals(mProxy, profile.getProxyInfo());
+ assertTrue(profile.isBypassable());
+ assertTrue(profile.isMetered());
+ assertEquals(TEST_MTU, profile.getMaxMtu());
+ assertEquals(Ikev2VpnProfile.DEFAULT_ALGORITHMS, profile.getAllowedAlgorithms());
+ }
+
+ @Test
+ public void testBuildUsernamePasswordProfile() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
+ final Ikev2VpnProfile profile = builder.build();
+ assertNotNull(profile);
+
+ assertEquals(USERNAME_STRING, profile.getUsername());
+ assertEquals(PASSWORD_STRING, profile.getPassword());
+ assertEquals(mServerRootCa, profile.getServerRootCaCert());
+
+ assertNull(profile.getPresharedKey());
+ assertNull(profile.getRsaPrivateKey());
+ assertNull(profile.getUserCert());
+ }
+
+ @Test
+ public void testBuildDigitalSignatureProfile() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+ final Ikev2VpnProfile profile = builder.build();
+ assertNotNull(profile);
+
+ assertEquals(profile.getUserCert(), mUserCert);
+ assertEquals(mPrivateKey, profile.getRsaPrivateKey());
+ assertEquals(profile.getServerRootCaCert(), mServerRootCa);
+
+ assertNull(profile.getPresharedKey());
+ assertNull(profile.getUsername());
+ assertNull(profile.getPassword());
+ }
+
+ @Test
+ public void testBuildPresharedKeyProfile() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthPsk(PSK_BYTES);
+ final Ikev2VpnProfile profile = builder.build();
+ assertNotNull(profile);
+
+ assertArrayEquals(PSK_BYTES, profile.getPresharedKey());
+
+ assertNull(profile.getServerRootCaCert());
+ assertNull(profile.getUsername());
+ assertNull(profile.getPassword());
+ assertNull(profile.getRsaPrivateKey());
+ assertNull(profile.getUserCert());
+ }
+
+ @Test
+ public void testBuildWithAllowedAlgorithmsAead() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+ builder.setAuthPsk(PSK_BYTES);
+
+ List<String> allowedAlgorithms =
+ Arrays.asList(
+ IpSecAlgorithm.AUTH_CRYPT_AES_GCM,
+ IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305);
+ builder.setAllowedAlgorithms(allowedAlgorithms);
+
+ final Ikev2VpnProfile profile = builder.build();
+ assertEquals(allowedAlgorithms, profile.getAllowedAlgorithms());
+ }
+
+ @Test
+ public void testBuildWithAllowedAlgorithmsNormal() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+ builder.setAuthPsk(PSK_BYTES);
+
+ List<String> allowedAlgorithms =
+ Arrays.asList(
+ IpSecAlgorithm.AUTH_HMAC_SHA512,
+ IpSecAlgorithm.AUTH_AES_XCBC,
+ IpSecAlgorithm.AUTH_AES_CMAC,
+ IpSecAlgorithm.CRYPT_AES_CBC,
+ IpSecAlgorithm.CRYPT_AES_CTR);
+ builder.setAllowedAlgorithms(allowedAlgorithms);
+
+ final Ikev2VpnProfile profile = builder.build();
+ assertEquals(allowedAlgorithms, profile.getAllowedAlgorithms());
+ }
+
+ @Test
+ public void testSetAllowedAlgorithmsEmptyList() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ try {
+ builder.setAllowedAlgorithms(new ArrayList<>());
+ fail("Expected exception due to no valid algorithm set");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testSetAllowedAlgorithmsInvalidList() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ try {
+ builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.AUTH_HMAC_SHA256));
+ fail("Expected exception due to missing encryption");
+ } catch (IllegalArgumentException expected) {
+ }
+
+ try {
+ builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.CRYPT_AES_CBC));
+ fail("Expected exception due to missing authentication");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testSetAllowedAlgorithmsInsecureAlgorithm() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ try {
+ builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.AUTH_HMAC_MD5));
+ fail("Expected exception due to insecure algorithm");
+ } catch (IllegalArgumentException expected) {
+ }
+
+ try {
+ builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.AUTH_HMAC_SHA1));
+ fail("Expected exception due to insecure algorithm");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testBuildNoAuthMethodSet() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ try {
+ builder.build();
+ fail("Expected exception due to lack of auth method");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testBuildExcludeLocalRoutesSet() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+ builder.setAuthPsk(PSK_BYTES);
+ builder.setLocalRoutesExcluded(true);
+
+ final Ikev2VpnProfile profile = builder.build();
+ assertNotNull(profile);
+ assertTrue(profile.areLocalRoutesExcluded());
+
+ builder.setBypassable(false);
+ try {
+ builder.build();
+ fail("Expected exception because excludeLocalRoutes should be set only"
+ + " on the bypassable VPN");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testBuildInvalidMtu() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ try {
+ builder.setMaxMtu(500);
+ fail("Expected exception due to too-small MTU");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ private void verifyVpnProfileCommon(VpnProfile profile) {
+ assertEquals(SERVER_ADDR_STRING, profile.server);
+ assertEquals(IDENTITY_STRING, profile.ipsecIdentifier);
+ assertEquals(mProxy, profile.proxy);
+ assertTrue(profile.isBypassable);
+ assertTrue(profile.isMetered);
+ assertEquals(TEST_MTU, profile.maxMtu);
+ }
+
+ @Test
+ public void testPskConvertToVpnProfile() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthPsk(PSK_BYTES);
+ final VpnProfile profile = builder.build().toVpnProfile();
+
+ verifyVpnProfileCommon(profile);
+ assertEquals(Ikev2VpnProfile.encodeForIpsecSecret(PSK_BYTES), profile.ipsecSecret);
+
+ // Check nothing else is set
+ assertEquals("", profile.username);
+ assertEquals("", profile.password);
+ assertEquals("", profile.ipsecUserCert);
+ assertEquals("", profile.ipsecCaCert);
+ }
+
+ @Test
+ public void testUsernamePasswordConvertToVpnProfile() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
+ final VpnProfile profile = builder.build().toVpnProfile();
+
+ verifyVpnProfileCommon(profile);
+ assertEquals(USERNAME_STRING, profile.username);
+ assertEquals(PASSWORD_STRING, profile.password);
+ assertEquals(Ikev2VpnProfile.certificateToPemString(mServerRootCa), profile.ipsecCaCert);
+
+ // Check nothing else is set
+ assertEquals("", profile.ipsecUserCert);
+ assertEquals("", profile.ipsecSecret);
+ }
+
+ @Test
+ public void testRsaConvertToVpnProfile() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+ final VpnProfile profile = builder.build().toVpnProfile();
+
+ final String expectedSecret = Ikev2VpnProfile.PREFIX_INLINE
+ + Ikev2VpnProfile.encodeForIpsecSecret(mPrivateKey.getEncoded());
+ verifyVpnProfileCommon(profile);
+ assertEquals(Ikev2VpnProfile.certificateToPemString(mUserCert), profile.ipsecUserCert);
+ assertEquals(
+ expectedSecret,
+ profile.ipsecSecret);
+ assertEquals(Ikev2VpnProfile.certificateToPemString(mServerRootCa), profile.ipsecCaCert);
+
+ // Check nothing else is set
+ assertEquals("", profile.username);
+ assertEquals("", profile.password);
+ }
+
+ @Test
+ public void testPskFromVpnProfileDiscardsIrrelevantValues() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthPsk(PSK_BYTES);
+ final VpnProfile profile = builder.build().toVpnProfile();
+ profile.username = USERNAME_STRING;
+ profile.password = PASSWORD_STRING;
+ profile.ipsecCaCert = Ikev2VpnProfile.certificateToPemString(mServerRootCa);
+ profile.ipsecUserCert = Ikev2VpnProfile.certificateToPemString(mUserCert);
+
+ final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile);
+ assertNull(result.getUsername());
+ assertNull(result.getPassword());
+ assertNull(result.getUserCert());
+ assertNull(result.getRsaPrivateKey());
+ assertNull(result.getServerRootCaCert());
+ }
+
+ @Test
+ public void testUsernamePasswordFromVpnProfileDiscardsIrrelevantValues() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
+ final VpnProfile profile = builder.build().toVpnProfile();
+ profile.ipsecSecret = new String(PSK_BYTES);
+ profile.ipsecUserCert = Ikev2VpnProfile.certificateToPemString(mUserCert);
+
+ final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile);
+ assertNull(result.getPresharedKey());
+ assertNull(result.getUserCert());
+ assertNull(result.getRsaPrivateKey());
+ }
+
+ @Test
+ public void testRsaFromVpnProfileDiscardsIrrelevantValues() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+ final VpnProfile profile = builder.build().toVpnProfile();
+ profile.username = USERNAME_STRING;
+ profile.password = PASSWORD_STRING;
+
+ final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile);
+ assertNull(result.getUsername());
+ assertNull(result.getPassword());
+ assertNull(result.getPresharedKey());
+ }
+
+ @Test
+ public void testPskConversionIsLossless() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthPsk(PSK_BYTES);
+ final Ikev2VpnProfile ikeProfile = builder.build();
+
+ assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
+ }
+
+ @Test
+ public void testUsernamePasswordConversionIsLossless() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
+ final Ikev2VpnProfile ikeProfile = builder.build();
+
+ assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
+ }
+
+ @Test
+ public void testRsaConversionIsLossless() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+ final Ikev2VpnProfile ikeProfile = builder.build();
+
+ assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
+ }
+
+ @Test
+ public void testBuildWithIkeTunConnParamsConvertToVpnProfile() throws Exception {
+ // Special keyId that contains delimiter character of VpnProfile
+ final byte[] keyId = "foo\0bar".getBytes();
+ final IkeTunnelConnectionParams tunnelParams = new IkeTunnelConnectionParams(
+ getTestIkeSessionParams(true /* testIpv6 */, new IkeKeyIdIdentification(keyId)),
+ CHILD_PARAMS);
+ final Ikev2VpnProfile ikev2VpnProfile = new Ikev2VpnProfile.Builder(tunnelParams).build();
+ final VpnProfile vpnProfile = ikev2VpnProfile.toVpnProfile();
+
+ assertEquals(VpnProfile.TYPE_IKEV2_FROM_IKE_TUN_CONN_PARAMS, vpnProfile.type);
+
+ // Username, password, server, ipsecIdentifier, ipsecCaCert, ipsecSecret, ipsecUserCert and
+ // getAllowedAlgorithms should not be set if IkeTunnelConnectionParams is set.
+ assertEquals("", vpnProfile.server);
+ assertEquals("", vpnProfile.ipsecIdentifier);
+ assertEquals("", vpnProfile.username);
+ assertEquals("", vpnProfile.password);
+ assertEquals("", vpnProfile.ipsecCaCert);
+ assertEquals("", vpnProfile.ipsecSecret);
+ assertEquals("", vpnProfile.ipsecUserCert);
+ assertEquals(0, vpnProfile.getAllowedAlgorithms().size());
+
+ // IkeTunnelConnectionParams should stay the same.
+ assertEquals(tunnelParams, vpnProfile.ikeTunConnParams);
+
+ // Convert to disk-stable format and then back to Ikev2VpnProfile should be the same.
+ final VpnProfile decodedVpnProfile =
+ VpnProfile.decode(vpnProfile.key, vpnProfile.encode());
+ final Ikev2VpnProfile convertedIkev2VpnProfile =
+ Ikev2VpnProfile.fromVpnProfile(decodedVpnProfile);
+ assertEquals(ikev2VpnProfile, convertedIkev2VpnProfile);
+ }
+
+ @Test
+ public void testConversionIsLosslessWithIkeTunConnParams() throws Exception {
+ final IkeTunnelConnectionParams tunnelParams =
+ new IkeTunnelConnectionParams(IKE_PARAMS_V6, CHILD_PARAMS);
+ // Config authentication related fields is not required while building with
+ // IkeTunnelConnectionParams.
+ final Ikev2VpnProfile ikeProfile = new Ikev2VpnProfile.Builder(tunnelParams).build();
+ assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
+ }
+
+ @Test
+ public void testAutomaticNattAndIpVersionConversionIsLossless() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+ builder.setAutomaticNattKeepaliveTimerEnabled(true);
+ builder.setAutomaticIpVersionSelectionEnabled(true);
+
+ builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+ final Ikev2VpnProfile ikeProfile = builder.build();
+
+ assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
+ }
+
+ @Test
+ public void testAutomaticNattAndIpVersionDefaults() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+ final Ikev2VpnProfile ikeProfile = builder.build();
+
+ assertEquals(false, ikeProfile.isAutomaticNattKeepaliveTimerEnabled());
+ assertEquals(false, ikeProfile.isAutomaticIpVersionSelectionEnabled());
+ }
+
+ @Test
+ public void testEquals() throws Exception {
+ // Verify building without IkeTunnelConnectionParams
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+ builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+ assertEquals(builder.build(), builder.build());
+
+ // Verify building with IkeTunnelConnectionParams
+ final IkeTunnelConnectionParams tunnelParams =
+ new IkeTunnelConnectionParams(IKE_PARAMS_V6, CHILD_PARAMS);
+ final IkeTunnelConnectionParams tunnelParams2 =
+ new IkeTunnelConnectionParams(IKE_PARAMS_V6, CHILD_PARAMS);
+ assertEquals(new Ikev2VpnProfile.Builder(tunnelParams).build(),
+ new Ikev2VpnProfile.Builder(tunnelParams2).build());
+ }
+
+ @Test
+ public void testBuildProfileWithNullProxy() throws Exception {
+ final Ikev2VpnProfile ikev2VpnProfile =
+ new Ikev2VpnProfile.Builder(SERVER_ADDR_STRING, IDENTITY_STRING)
+ .setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa)
+ .build();
+
+ // ProxyInfo should be null for the profile without setting ProxyInfo.
+ assertNull(ikev2VpnProfile.getProxyInfo());
+
+ // ProxyInfo should stay null after performing toVpnProfile() and fromVpnProfile()
+ final VpnProfile vpnProfile = ikev2VpnProfile.toVpnProfile();
+ assertNull(vpnProfile.proxy);
+
+ final Ikev2VpnProfile convertedIkev2VpnProfile = Ikev2VpnProfile.fromVpnProfile(vpnProfile);
+ assertNull(convertedIkev2VpnProfile.getProxyInfo());
+ }
+
+ private static class CertificateAndKey {
+ public final X509Certificate cert;
+ public final PrivateKey key;
+
+ CertificateAndKey(X509Certificate cert, PrivateKey key) {
+ this.cert = cert;
+ this.key = key;
+ }
+ }
+
+ private static CertificateAndKey generateRandomCertAndKeyPair() throws Exception {
+ final Date validityBeginDate =
+ new Date(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1L));
+ final Date validityEndDate =
+ new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1L));
+
+ // Generate a keypair
+ final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
+ keyPairGenerator.initialize(512);
+ final KeyPair keyPair = keyPairGenerator.generateKeyPair();
+
+ final X500Principal dnName = new X500Principal("CN=test.android.com");
+ final X509V1CertificateGenerator certGen = new X509V1CertificateGenerator();
+ certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
+ certGen.setSubjectDN(dnName);
+ certGen.setIssuerDN(dnName);
+ certGen.setNotBefore(validityBeginDate);
+ certGen.setNotAfter(validityEndDate);
+ certGen.setPublicKey(keyPair.getPublic());
+ certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
+
+ final X509Certificate cert = certGen.generate(keyPair.getPrivate(), "AndroidOpenSSL");
+ return new CertificateAndKey(cert, keyPair.getPrivate());
+ }
+}
diff --git a/services/tests/VpnTests/java/android/net/VpnManagerTest.java b/services/tests/VpnTests/java/android/net/VpnManagerTest.java
new file mode 100644
index 0000000..f5b83f0
--- /dev/null
+++ b/services/tests/VpnTests/java/android/net/VpnManagerTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assume.assumeFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.test.mock.MockContext;
+import android.util.SparseArray;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.net.VpnProfile;
+import com.android.internal.util.MessageUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit tests for {@link VpnManager}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class VpnManagerTest {
+
+ private static final String PKG_NAME = "fooPackage";
+
+ private static final String SERVER_ADDR_STRING = "1.2.3.4";
+ private static final String IDENTITY_STRING = "Identity";
+ private static final byte[] PSK_BYTES = "preSharedKey".getBytes();
+
+ private IVpnManager mMockService;
+ private VpnManager mVpnManager;
+ private final MockContext mMockContext =
+ new MockContext() {
+ @Override
+ public String getOpPackageName() {
+ return PKG_NAME;
+ }
+ };
+
+ @Before
+ public void setUp() throws Exception {
+ assumeFalse("Skipping test because watches don't support VPN",
+ InstrumentationRegistry.getContext().getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_WATCH));
+ mMockService = mock(IVpnManager.class);
+ mVpnManager = new VpnManager(mMockContext, mMockService);
+ }
+
+ @Test
+ public void testProvisionVpnProfilePreconsented() throws Exception {
+ final PlatformVpnProfile profile = getPlatformVpnProfile();
+ when(mMockService.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME)))
+ .thenReturn(true);
+
+ // Expect there to be no intent returned, as consent has already been granted.
+ assertNull(mVpnManager.provisionVpnProfile(profile));
+ verify(mMockService).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME));
+ }
+
+ @Test
+ public void testProvisionVpnProfileNeedsConsent() throws Exception {
+ final PlatformVpnProfile profile = getPlatformVpnProfile();
+ when(mMockService.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME)))
+ .thenReturn(false);
+
+ // Expect intent to be returned, as consent has not already been granted.
+ final Intent intent = mVpnManager.provisionVpnProfile(profile);
+ assertNotNull(intent);
+
+ final ComponentName expectedComponentName =
+ ComponentName.unflattenFromString(
+ "com.android.vpndialogs/com.android.vpndialogs.PlatformVpnConfirmDialog");
+ assertEquals(expectedComponentName, intent.getComponent());
+ verify(mMockService).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME));
+ }
+
+ @Test
+ public void testDeleteProvisionedVpnProfile() throws Exception {
+ mVpnManager.deleteProvisionedVpnProfile();
+ verify(mMockService).deleteVpnProfile(eq(PKG_NAME));
+ }
+
+ @Test
+ public void testStartProvisionedVpnProfile() throws Exception {
+ mVpnManager.startProvisionedVpnProfile();
+ verify(mMockService).startVpnProfile(eq(PKG_NAME));
+ }
+
+ @Test
+ public void testStopProvisionedVpnProfile() throws Exception {
+ mVpnManager.stopProvisionedVpnProfile();
+ verify(mMockService).stopVpnProfile(eq(PKG_NAME));
+ }
+
+ private Ikev2VpnProfile getPlatformVpnProfile() throws Exception {
+ return new Ikev2VpnProfile.Builder(SERVER_ADDR_STRING, IDENTITY_STRING)
+ .setBypassable(true)
+ .setMaxMtu(1300)
+ .setMetered(true)
+ .setAuthPsk(PSK_BYTES)
+ .build();
+ }
+
+ @Test
+ public void testVpnTypesEqual() throws Exception {
+ SparseArray<String> vmVpnTypes = MessageUtils.findMessageNames(
+ new Class[] { VpnManager.class }, new String[]{ "TYPE_VPN_" });
+ SparseArray<String> nativeVpnType = MessageUtils.findMessageNames(
+ new Class[] { NativeVpnType.class }, new String[]{ "" });
+
+ // TYPE_VPN_NONE = -1 is only defined in VpnManager.
+ assertEquals(vmVpnTypes.size() - 1, nativeVpnType.size());
+ for (int i = VpnManager.TYPE_VPN_SERVICE; i < vmVpnTypes.size(); i++) {
+ assertEquals(vmVpnTypes.get(i), "TYPE_VPN_" + nativeVpnType.get(i));
+ }
+ }
+}
diff --git a/services/tests/VpnTests/java/com/android/internal/net/VpnProfileTest.java b/services/tests/VpnTests/java/com/android/internal/net/VpnProfileTest.java
new file mode 100644
index 0000000..acbe8b8
--- /dev/null
+++ b/services/tests/VpnTests/java/com/android/internal/net/VpnProfileTest.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2019 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.internal.net;
+
+import static android.net.cts.util.IkeSessionTestUtils.CHILD_PARAMS;
+import static android.net.cts.util.IkeSessionTestUtils.IKE_PARAMS_V4;
+
+import static com.android.testutils.ParcelUtils.assertParcelSane;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.net.IpSecAlgorithm;
+import android.net.ipsec.ike.IkeTunnelConnectionParams;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/** Unit tests for {@link VpnProfile}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class VpnProfileTest {
+ private static final String DUMMY_PROFILE_KEY = "Test";
+
+ private static final int ENCODED_INDEX_AUTH_PARAMS_INLINE = 23;
+ private static final int ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS = 24;
+ private static final int ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE = 25;
+ private static final int ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION = 26;
+ private static final int ENCODED_INDEX_IKE_TUN_CONN_PARAMS = 27;
+ private static final int ENCODED_INDEX_AUTOMATIC_NATT_KEEPALIVE_TIMER_ENABLED = 28;
+ private static final int ENCODED_INDEX_AUTOMATIC_IP_VERSION_SELECTION_ENABLED = 29;
+
+ @Test
+ public void testDefaults() throws Exception {
+ final VpnProfile p = new VpnProfile(DUMMY_PROFILE_KEY);
+
+ assertEquals(DUMMY_PROFILE_KEY, p.key);
+ assertEquals("", p.name);
+ assertEquals(VpnProfile.TYPE_PPTP, p.type);
+ assertEquals("", p.server);
+ assertEquals("", p.username);
+ assertEquals("", p.password);
+ assertEquals("", p.dnsServers);
+ assertEquals("", p.searchDomains);
+ assertEquals("", p.routes);
+ assertTrue(p.mppe);
+ assertEquals("", p.l2tpSecret);
+ assertEquals("", p.ipsecIdentifier);
+ assertEquals("", p.ipsecSecret);
+ assertEquals("", p.ipsecUserCert);
+ assertEquals("", p.ipsecCaCert);
+ assertEquals("", p.ipsecServerCert);
+ assertEquals(null, p.proxy);
+ assertTrue(p.getAllowedAlgorithms() != null && p.getAllowedAlgorithms().isEmpty());
+ assertFalse(p.isBypassable);
+ assertFalse(p.isMetered);
+ assertEquals(1360, p.maxMtu);
+ assertFalse(p.areAuthParamsInline);
+ assertFalse(p.isRestrictedToTestNetworks);
+ assertFalse(p.excludeLocalRoutes);
+ assertFalse(p.requiresInternetValidation);
+ assertFalse(p.automaticNattKeepaliveTimerEnabled);
+ assertFalse(p.automaticIpVersionSelectionEnabled);
+ }
+
+ private VpnProfile getSampleIkev2Profile(String key) {
+ final VpnProfile p = new VpnProfile(key, true /* isRestrictedToTestNetworks */,
+ false /* excludesLocalRoutes */, true /* requiresPlatformValidation */,
+ null /* ikeTunConnParams */, true /* mAutomaticNattKeepaliveTimerEnabled */,
+ true /* automaticIpVersionSelectionEnabled */);
+
+ p.name = "foo";
+ p.type = VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS;
+ p.server = "bar";
+ p.username = "baz";
+ p.password = "qux";
+ p.dnsServers = "8.8.8.8";
+ p.searchDomains = "";
+ p.routes = "0.0.0.0/0";
+ p.mppe = false;
+ p.l2tpSecret = "";
+ p.ipsecIdentifier = "quux";
+ p.ipsecSecret = "quuz";
+ p.ipsecUserCert = "corge";
+ p.ipsecCaCert = "grault";
+ p.ipsecServerCert = "garply";
+ p.proxy = null;
+ p.setAllowedAlgorithms(
+ Arrays.asList(
+ IpSecAlgorithm.AUTH_CRYPT_AES_GCM,
+ IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305,
+ IpSecAlgorithm.AUTH_HMAC_SHA512,
+ IpSecAlgorithm.CRYPT_AES_CBC));
+ p.isBypassable = true;
+ p.isMetered = true;
+ p.maxMtu = 1350;
+ p.areAuthParamsInline = true;
+
+ // Not saved, but also not compared.
+ p.saveLogin = true;
+
+ return p;
+ }
+
+ private VpnProfile getSampleIkev2ProfileWithIkeTunConnParams(String key) {
+ final VpnProfile p = new VpnProfile(key, true /* isRestrictedToTestNetworks */,
+ false /* excludesLocalRoutes */, true /* requiresPlatformValidation */,
+ new IkeTunnelConnectionParams(IKE_PARAMS_V4, CHILD_PARAMS),
+ true /* mAutomaticNattKeepaliveTimerEnabled */,
+ true /* automaticIpVersionSelectionEnabled */);
+
+ p.name = "foo";
+ p.server = "bar";
+ p.dnsServers = "8.8.8.8";
+ p.searchDomains = "";
+ p.routes = "0.0.0.0/0";
+ p.mppe = false;
+ p.proxy = null;
+ p.setAllowedAlgorithms(
+ Arrays.asList(
+ IpSecAlgorithm.AUTH_CRYPT_AES_GCM,
+ IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305,
+ IpSecAlgorithm.AUTH_HMAC_SHA512,
+ IpSecAlgorithm.CRYPT_AES_CBC));
+ p.isBypassable = true;
+ p.isMetered = true;
+ p.maxMtu = 1350;
+ p.areAuthParamsInline = true;
+
+ // Not saved, but also not compared.
+ p.saveLogin = true;
+
+ return p;
+ }
+
+ @Test
+ public void testEquals() {
+ assertEquals(
+ getSampleIkev2Profile(DUMMY_PROFILE_KEY), getSampleIkev2Profile(DUMMY_PROFILE_KEY));
+
+ final VpnProfile modified = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
+ modified.maxMtu--;
+ assertNotEquals(getSampleIkev2Profile(DUMMY_PROFILE_KEY), modified);
+ }
+
+ @Test
+ public void testParcelUnparcel() {
+ assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 28);
+ assertParcelSane(getSampleIkev2ProfileWithIkeTunConnParams(DUMMY_PROFILE_KEY), 28);
+ }
+
+ @Test
+ public void testEncodeDecodeWithIkeTunConnParams() {
+ final VpnProfile profile = getSampleIkev2ProfileWithIkeTunConnParams(DUMMY_PROFILE_KEY);
+ final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode());
+ assertEquals(profile, decoded);
+ }
+
+ @Test
+ public void testEncodeDecode() {
+ final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
+ final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode());
+ assertEquals(profile, decoded);
+ }
+
+ @Test
+ public void testEncodeDecodeTooManyValues() {
+ final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
+ final byte[] tooManyValues =
+ (new String(profile.encode()) + VpnProfile.VALUE_DELIMITER + "invalid").getBytes();
+
+ assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooManyValues));
+ }
+
+ private String getEncodedDecodedIkev2ProfileMissingValues(int... missingIndices) {
+ // Sort to ensure when we remove, we can do it from greatest first.
+ Arrays.sort(missingIndices);
+
+ final String encoded = new String(getSampleIkev2Profile(DUMMY_PROFILE_KEY).encode());
+ final List<String> parts =
+ new ArrayList<>(Arrays.asList(encoded.split(VpnProfile.VALUE_DELIMITER)));
+
+ // Remove from back first to ensure indexing is consistent.
+ for (int i = missingIndices.length - 1; i >= 0; i--) {
+ parts.remove(missingIndices[i]);
+ }
+
+ return String.join(VpnProfile.VALUE_DELIMITER, parts.toArray(new String[0]));
+ }
+
+ @Test
+ public void testEncodeDecodeInvalidNumberOfValues() {
+ final String tooFewValues =
+ getEncodedDecodedIkev2ProfileMissingValues(
+ ENCODED_INDEX_AUTH_PARAMS_INLINE,
+ ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS,
+ ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE,
+ ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION,
+ ENCODED_INDEX_IKE_TUN_CONN_PARAMS,
+ ENCODED_INDEX_AUTOMATIC_NATT_KEEPALIVE_TIMER_ENABLED,
+ ENCODED_INDEX_AUTOMATIC_IP_VERSION_SELECTION_ENABLED
+ /* missingIndices */);
+
+ assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes()));
+ }
+
+ private String getEncodedDecodedIkev2ProfileWithtooFewValues() {
+ return getEncodedDecodedIkev2ProfileMissingValues(
+ ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS,
+ ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE,
+ ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION,
+ ENCODED_INDEX_IKE_TUN_CONN_PARAMS,
+ ENCODED_INDEX_AUTOMATIC_NATT_KEEPALIVE_TIMER_ENABLED,
+ ENCODED_INDEX_AUTOMATIC_IP_VERSION_SELECTION_ENABLED /* missingIndices */);
+ }
+
+ @Test
+ public void testEncodeDecodeMissingIsRestrictedToTestNetworks() {
+ final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
+
+ // Verify decoding without isRestrictedToTestNetworks defaults to false
+ final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
+ assertFalse(decoded.isRestrictedToTestNetworks);
+ }
+
+ @Test
+ public void testEncodeDecodeMissingExcludeLocalRoutes() {
+ final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
+
+ // Verify decoding without excludeLocalRoutes defaults to false
+ final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
+ assertFalse(decoded.excludeLocalRoutes);
+ }
+
+ @Test
+ public void testEncodeDecodeMissingRequiresValidation() {
+ final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
+
+ // Verify decoding without requiresValidation defaults to false
+ final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
+ assertFalse(decoded.requiresInternetValidation);
+ }
+
+ @Test
+ public void testEncodeDecodeMissingAutomaticNattKeepaliveTimerEnabled() {
+ final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
+
+ // Verify decoding without automaticNattKeepaliveTimerEnabled defaults to false
+ final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
+ assertFalse(decoded.automaticNattKeepaliveTimerEnabled);
+ }
+
+ @Test
+ public void testEncodeDecodeMissingAutomaticIpVersionSelectionEnabled() {
+ final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
+
+ // Verify decoding without automaticIpVersionSelectionEnabled defaults to false
+ final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
+ assertFalse(decoded.automaticIpVersionSelectionEnabled);
+ }
+
+ @Test
+ public void testEncodeDecodeLoginsNotSaved() {
+ final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
+ profile.saveLogin = false;
+
+ final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode());
+ assertNotEquals(profile, decoded);
+
+ // Add the username/password back, everything else must be equal.
+ decoded.username = profile.username;
+ decoded.password = profile.password;
+ assertEquals(profile, decoded);
+ }
+
+ @Test
+ public void testClone() {
+ final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
+ final VpnProfile clone = profile.clone();
+ assertEquals(profile, clone);
+ assertNotSame(profile, clone);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/net/LockdownVpnTrackerTest.java b/services/tests/VpnTests/java/com/android/server/net/LockdownVpnTrackerTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/net/LockdownVpnTrackerTest.java
rename to services/tests/VpnTests/java/com/android/server/net/LockdownVpnTrackerTest.java
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
index fbb14c3..4409051 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
@@ -26,7 +26,6 @@
import static org.mockito.Mockito.when;
import android.hardware.display.DisplayManagerInternal;
-import android.os.PowerManager;
import org.junit.Before;
import org.junit.Test;
@@ -66,8 +65,6 @@
mSession.stopOffload();
assertFalse(mSession.isActive());
- verify(mDisplayPowerController).setBrightnessFromOffload(
- PowerManager.BRIGHTNESS_INVALID_FLOAT);
// An inactive session shouldn't be stopped again
mSession.stopOffload();
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index e9315c8..14d8a9c 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -22,6 +22,7 @@
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -77,6 +78,7 @@
import com.android.server.am.BatteryStatsService;
import com.android.server.display.RampAnimator.DualRampAnimator;
import com.android.server.display.brightness.BrightnessEvent;
+import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.brightness.clamper.BrightnessClamperController;
import com.android.server.display.brightness.clamper.HdrClamper;
import com.android.server.display.color.ColorDisplayService;
@@ -1559,6 +1561,43 @@
verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
+ assertEquals(BrightnessReason.REASON_OFFLOAD, mHolder.dpc.mBrightnessReason.getReason());
+ }
+
+ @Test
+ public void testBrightness_AutomaticHigherPriorityThanOffload() {
+ when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ float brightness = 0.34f;
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+ when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+ any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
+
+ mHolder.dpc.setBrightnessFromOffload(brightness);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
+ assertEquals(BrightnessReason.REASON_OFFLOAD, mHolder.dpc.mBrightnessReason.getReason());
+
+ // Now automatic brightness becomes available
+ brightness = 0.22f;
+ when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+ any(BrightnessEvent.class))).thenReturn(brightness);
+
+ mHolder.dpc.updateBrightness();
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
+ assertEquals(BrightnessReason.REASON_AUTOMATIC, mHolder.dpc.mBrightnessReason.getReason());
}
@Test
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
index b99ecf3..14de527 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -46,7 +46,6 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
-import android.os.PowerManager;
import android.view.Display;
import android.view.DisplayAddress;
import android.view.SurfaceControl;
@@ -1229,8 +1228,6 @@
verify(mDisplayOffloader).stopOffload();
assertFalse(mDisplayOffloadSession.isActive());
- verify(mMockedDisplayPowerController).setBrightnessFromOffload(
- PowerManager.BRIGHTNESS_INVALID_FLOAT);
}
private void initDisplayOffloadSession() {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
index 289d54b..9b6cc0a 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
@@ -393,7 +393,31 @@
OffloadBrightnessStrategy offloadBrightnessStrategy = mock(OffloadBrightnessStrategy.class);
when(mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy()).thenReturn(
offloadBrightnessStrategy);
- mDisplayBrightnessController.setBrightnessFromOffload(brightness);
+ boolean brightnessUpdated =
+ mDisplayBrightnessController.setBrightnessFromOffload(brightness);
verify(offloadBrightnessStrategy).setOffloadScreenBrightness(brightness);
+ assertTrue(brightnessUpdated);
+ }
+
+ @Test
+ public void setBrightnessFromOffload_OffloadStrategyNull() {
+ float brightness = 0.4f;
+ when(mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy()).thenReturn(null);
+ boolean brightnessUpdated =
+ mDisplayBrightnessController.setBrightnessFromOffload(brightness);
+ assertFalse(brightnessUpdated);
+ }
+
+ @Test
+ public void setBrightnessFromOffload_BrightnessUnchanged() {
+ float brightness = 0.4f;
+ OffloadBrightnessStrategy offloadBrightnessStrategy = mock(OffloadBrightnessStrategy.class);
+ when(offloadBrightnessStrategy.getOffloadScreenBrightness()).thenReturn(brightness);
+ when(mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy()).thenReturn(
+ offloadBrightnessStrategy);
+ boolean brightnessUpdated =
+ mDisplayBrightnessController.setBrightnessFromOffload(brightness);
+ verify(offloadBrightnessStrategy, never()).setOffloadScreenBrightness(brightness);
+ assertFalse(brightnessUpdated);
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
index 1c681ce..0e89d83 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
@@ -247,6 +247,7 @@
displayPowerRequest.screenBrightnessOverride = Float.NaN;
when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN);
+ when(mAutomaticBrightnessStrategy.shouldUseAutoBrightness()).thenReturn(true);
when(mOffloadBrightnessStrategy.getOffloadScreenBrightness()).thenReturn(0.3f);
assertEquals(mOffloadBrightnessStrategy, mDisplayBrightnessStrategySelector.selectStrategy(
displayPowerRequest, Display.STATE_ON));
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
index ba462e3..a5dc668 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
@@ -188,29 +188,6 @@
}
@Test
- public void testAutoBrightnessState_BrightnessReasonIsOffload() {
- mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
- int targetDisplayState = Display.STATE_ON;
- boolean allowAutoBrightnessWhileDozing = false;
- int brightnessReason = BrightnessReason.REASON_OFFLOAD;
- int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
- float lastUserSetBrightness = 0.2f;
- boolean userSetBrightnessChanged = true;
- mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
- mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
- allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
- userSetBrightnessChanged);
- verify(mAutomaticBrightnessController)
- .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
- mBrightnessConfiguration,
- lastUserSetBrightness,
- userSetBrightnessChanged, 0.5f,
- false, policy, true);
- assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
- assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
- }
-
- @Test
public void testAutoBrightnessState_DisplayIsInDoze_ConfigDoesAllow() {
mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
int targetDisplayState = Display.STATE_DOZE;
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 3f6117b..e141faf 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -2001,6 +2001,59 @@
}
@Test
+ public void testReplacePendingToCachedProcess_withDeferrableBroadcast() throws Exception {
+ // Legacy stack doesn't support deferral
+ Assume.assumeTrue(mImpl == Impl.MODERN);
+
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
+ final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
+ final ProcessRecord receiverYellowApp = makeActiveProcessRecord(PACKAGE_YELLOW);
+
+ setProcessFreezable(receiverGreenApp, true, false);
+ mQueue.onProcessFreezableChangedLocked(receiverGreenApp);
+ waitForIdle();
+
+ final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK)
+ .addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ final BroadcastOptions opts = BroadcastOptions.makeBasic()
+ .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE);
+
+ final BroadcastFilter receiverGreen = makeRegisteredReceiver(receiverGreenApp, 10);
+ final BroadcastFilter receiverBlue = makeRegisteredReceiver(receiverBlueApp, 5);
+ final BroadcastFilter receiverYellow = makeRegisteredReceiver(receiverYellowApp, 0);
+ enqueueBroadcast(makeBroadcastRecord(timeTick, callerApp, opts, List.of(
+ receiverGreen, receiverBlue, receiverYellow)));
+
+ // Enqueue the broadcast again to replace the earlier one
+ enqueueBroadcast(makeBroadcastRecord(timeTick, callerApp, opts, List.of(
+ receiverGreen, receiverBlue, receiverYellow)));
+
+ waitForIdle();
+ // Green should still be in the cached state and shouldn't receive the broadcast
+ verifyScheduleRegisteredReceiver(never(), receiverGreenApp, timeTick);
+
+ final IApplicationThread blueThread = receiverBlueApp.getThread();
+ final IApplicationThread yellowThread = receiverYellowApp.getThread();
+ final InOrder inOrder = inOrder(blueThread, yellowThread);
+ inOrder.verify(blueThread).scheduleRegisteredReceiver(
+ any(), argThat(filterEqualsIgnoringComponent(timeTick)),
+ anyInt(), any(), any(), anyBoolean(), anyBoolean(), anyBoolean(),
+ eq(UserHandle.USER_SYSTEM), anyInt(), anyInt(), any());
+ inOrder.verify(yellowThread).scheduleRegisteredReceiver(
+ any(), argThat(filterEqualsIgnoringComponent(timeTick)),
+ anyInt(), any(), any(), anyBoolean(), anyBoolean(), anyBoolean(),
+ eq(UserHandle.USER_SYSTEM), anyInt(), anyInt(), any());
+
+ setProcessFreezable(receiverGreenApp, false, false);
+ mQueue.onProcessFreezableChangedLocked(receiverGreenApp);
+ waitForIdle();
+
+ // Confirm that green receives the broadcast once it comes out of the cached state
+ verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, timeTick);
+ }
+
+ @Test
public void testIdleAndBarrier() throws Exception {
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN);
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
index 6bcd778..c6a6865 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
@@ -60,10 +60,13 @@
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.annotation.Nullable;
import android.app.AlarmManager;
import android.app.AppGlobals;
import android.app.job.JobInfo;
@@ -71,12 +74,15 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
import android.net.NetworkRequest;
import android.os.Looper;
import android.os.PowerManager;
+import android.os.UserHandle;
import android.provider.DeviceConfig;
+import android.telephony.TelephonyManager;
+import android.telephony.UiccSlotMapping;
import android.util.ArraySet;
import android.util.EmptyArray;
import android.util.SparseArray;
@@ -104,6 +110,9 @@
import java.time.Instant;
import java.time.ZoneOffset;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
import java.util.concurrent.Executor;
public class FlexibilityControllerTest {
@@ -113,6 +122,9 @@
private MockitoSession mMockingSession;
private BroadcastReceiver mBroadcastReceiver;
+ private final SparseArray<ArraySet<String>> mCarrierPrivilegedApps = new SparseArray<>();
+ private final SparseArray<TelephonyManager.CarrierPrivilegesCallback>
+ mCarrierPrivilegedCallbacks = new SparseArray<>();
private FlexibilityController mFlexibilityController;
private DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder;
private JobStore mJobStore;
@@ -130,6 +142,10 @@
@Mock
private PrefetchController mPrefetchController;
@Mock
+ private TelephonyManager mTelephonyManager;
+ @Mock
+ private IPackageManager mIPackageManager;
+ @Mock
private PackageManager mPackageManager;
@Before
@@ -138,6 +154,7 @@
.initMocks(this)
.strictness(Strictness.LENIENT)
.spyStatic(DeviceConfig.class)
+ .mockStatic(AppGlobals.class)
.mockStatic(LocalServices.class)
.startMocking();
// Called in StateController constructor.
@@ -167,17 +184,23 @@
-> mDeviceConfigPropertiesBuilder.build())
.when(() -> DeviceConfig.getProperties(
eq(DeviceConfig.NAMESPACE_JOB_SCHEDULER), ArgumentMatchers.<String>any()));
+ // Used in FlexibilityController.SpecialAppTracker.
+ when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION))
+ .thenReturn(true);
//used to get jobs by UID
mJobStore = JobStore.initAndGetForTesting(mContext, mContext.getFilesDir());
doReturn(mJobStore).when(mJobSchedulerService).getJobStore();
// Used in JobStatus.
- doReturn(mock(PackageManagerInternal.class))
- .when(() -> LocalServices.getService(PackageManagerInternal.class));
+ doReturn(mIPackageManager).when(AppGlobals::getPackageManager);
// Freeze the clocks at a moment in time
JobSchedulerService.sSystemClock =
Clock.fixed(Instant.ofEpochMilli(FROZEN_TIME), ZoneOffset.UTC);
JobSchedulerService.sElapsedRealtimeClock =
Clock.fixed(Instant.ofEpochMilli(FROZEN_TIME), ZoneOffset.UTC);
+ // Set empty set of privileged apps.
+ setSimSlotMappings(null);
+ setPowerWhitelistExceptIdle();
// Initialize real objects.
doReturn(Long.MAX_VALUE).when(mPrefetchController).getNextEstimatedLaunchTimeLocked(any());
ArgumentCaptor<BroadcastReceiver> receiverCaptor =
@@ -249,9 +272,13 @@
}
private JobStatus createJobStatus(String testTag, JobInfo.Builder job) {
+ return createJobStatus(testTag, job, SOURCE_PACKAGE);
+ }
+
+ private JobStatus createJobStatus(String testTag, JobInfo.Builder job, String sourcePackage) {
JobInfo jobInfo = job.build();
JobStatus js = JobStatus.createFromJobInfo(
- jobInfo, 1000, SOURCE_PACKAGE, SOURCE_USER_ID, "FCTest", testTag);
+ jobInfo, 1000, sourcePackage, SOURCE_USER_ID, "FCTest", testTag);
js.enqueueTime = FROZEN_TIME;
js.setStandbyBucket(ACTIVE_INDEX);
if (js.hasFlexibilityConstraint()) {
@@ -1084,7 +1111,6 @@
@Test
public void testAllowlistedAppBypass() {
- setPowerWhitelistExceptIdle();
mFlexibilityController.onSystemServicesReady();
JobStatus jsHigh = createJobStatus("testAllowlistedAppBypass",
@@ -1118,6 +1144,148 @@
}
@Test
+ public void testCarrierPrivilegedAppBypass() throws Exception {
+ mFlexibilityController.onSystemServicesReady();
+
+ final String carrier1Pkg1 = "com.test.carrier.1.pkg.1";
+ final String carrier1Pkg2 = "com.test.carrier.1.pkg.2";
+ final String carrier2Pkg = "com.test.carrier.2.pkg";
+ final String nonCarrierPkg = "com.test.normal.pkg";
+
+ setPackageUid(carrier1Pkg1, 1);
+ setPackageUid(carrier1Pkg2, 11);
+ setPackageUid(carrier2Pkg, 2);
+ setPackageUid(nonCarrierPkg, 3);
+
+ // Set the second carrier's privileged list before SIM configuration is sent to test
+ // initialization.
+ setCarrierPrivilegedAppList(2, carrier2Pkg);
+
+ UiccSlotMapping sim1 = mock(UiccSlotMapping.class);
+ UiccSlotMapping sim2 = mock(UiccSlotMapping.class);
+ doReturn(1).when(sim1).getLogicalSlotIndex();
+ doReturn(2).when(sim2).getLogicalSlotIndex();
+ setSimSlotMappings(List.of(sim1, sim2));
+
+ JobStatus jsHighC1P1 = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_HIGH), carrier1Pkg1);
+ JobStatus jsDefaultC1P1 = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT), carrier1Pkg1);
+ JobStatus jsLowC1P1 = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_LOW), carrier1Pkg1);
+ JobStatus jsMinC1P1 = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_MIN), carrier1Pkg1);
+ JobStatus jsHighC1P2 = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_HIGH), carrier1Pkg2);
+ JobStatus jsDefaultC1P2 = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT), carrier1Pkg2);
+ JobStatus jsLowC1P2 = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_LOW), carrier1Pkg2);
+ JobStatus jsMinC1P2 = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_MIN), carrier1Pkg2);
+ JobStatus jsHighC2P = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_HIGH), carrier2Pkg);
+ JobStatus jsDefaultC2P = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT), carrier2Pkg);
+ JobStatus jsLowC2P = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_LOW), carrier2Pkg);
+ JobStatus jsMinC2P = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_MIN), carrier2Pkg);
+ JobStatus jsHighNCP = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_HIGH), nonCarrierPkg);
+ JobStatus jsDefaultNCP = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT), nonCarrierPkg);
+ JobStatus jsLowNCP = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_LOW), nonCarrierPkg);
+ JobStatus jsMinNCP = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_MIN), nonCarrierPkg);
+
+ setCarrierPrivilegedAppList(1);
+ synchronized (mFlexibilityController.mLock) {
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P1));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P1));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P1));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P1));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P2));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P2));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P2));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P2));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC2P));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC2P));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC2P));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC2P));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighNCP));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultNCP));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowNCP));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinNCP));
+ }
+
+ // Only mark the first package of carrier 1 as privileged. Only that app's jobs should
+ // be exempted.
+ setCarrierPrivilegedAppList(1, carrier1Pkg1);
+ synchronized (mFlexibilityController.mLock) {
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P1));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P1));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P1));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P1));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P2));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P2));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P2));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P2));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC2P));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC2P));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC2P));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC2P));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighNCP));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultNCP));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowNCP));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinNCP));
+ }
+
+ // Add the second package of carrier 1. Both apps' jobs should be exempted.
+ setCarrierPrivilegedAppList(1, carrier1Pkg1, carrier1Pkg2);
+ synchronized (mFlexibilityController.mLock) {
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P1));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P1));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P1));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P1));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P2));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P2));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P2));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P2));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC2P));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC2P));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC2P));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC2P));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighNCP));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultNCP));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowNCP));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinNCP));
+ }
+
+ // Remove a SIM slot. The relevant app's should no longer have exempted jobs.
+ setSimSlotMappings(List.of(sim1));
+ synchronized (mFlexibilityController.mLock) {
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P1));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P1));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P1));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P1));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P2));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P2));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P2));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P2));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC2P));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC2P));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC2P));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC2P));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighNCP));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultNCP));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowNCP));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinNCP));
+ }
+ }
+
+ @Test
public void testForegroundAppBypass() {
JobStatus jsHigh = createJobStatus("testAllowlistedAppBypass",
createJob(0).setPriority(JobInfo.PRIORITY_HIGH));
@@ -1753,6 +1921,24 @@
}
}
+ private void setCarrierPrivilegedAppList(int logicalIndex, String... packages) {
+ final ArraySet<String> packageSet = packages == null
+ ? new ArraySet<>() : new ArraySet<>(packages);
+ mCarrierPrivilegedApps.put(logicalIndex, packageSet);
+
+ TelephonyManager.CarrierPrivilegesCallback callback =
+ mCarrierPrivilegedCallbacks.get(logicalIndex);
+ if (callback != null) {
+ callback.onCarrierPrivilegesChanged(packageSet, Collections.emptySet());
+ waitForQuietModuleThread();
+ }
+ }
+
+ private void setPackageUid(final String pkgName, final int uid) throws Exception {
+ doReturn(uid).when(mIPackageManager)
+ .getPackageUid(eq(pkgName), anyLong(), eq(UserHandle.getUserId(uid)));
+ }
+
private void setPowerWhitelistExceptIdle(String... packages) {
doReturn(packages == null ? EmptyArray.STRING : packages)
.when(mDeviceIdleInternal).getFullPowerWhitelistExceptIdle();
@@ -1763,6 +1949,47 @@
}
}
+ private void setSimSlotMappings(@Nullable Collection<UiccSlotMapping> simSlotMapping) {
+ clearInvocations(mTelephonyManager);
+ final Collection<UiccSlotMapping> returnedMapping = simSlotMapping == null
+ ? Collections.emptyList() : simSlotMapping;
+ doReturn(returnedMapping).when(mTelephonyManager).getSimSlotMapping();
+ if (mBroadcastReceiver != null) {
+ final Intent intent = new Intent(TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED);
+ mBroadcastReceiver.onReceive(mContext, intent);
+ waitForQuietModuleThread();
+ }
+ if (returnedMapping.size() > 0) {
+ ArgumentCaptor<TelephonyManager.CarrierPrivilegesCallback> callbackCaptor =
+ ArgumentCaptor.forClass(TelephonyManager.CarrierPrivilegesCallback.class);
+ ArgumentCaptor<Integer> logicalIndexCaptor = ArgumentCaptor.forClass(Integer.class);
+
+ final int minExpectedNewRegistrations = Math.max(0,
+ returnedMapping.size() - mCarrierPrivilegedCallbacks.size());
+ verify(mTelephonyManager, atLeast(minExpectedNewRegistrations))
+ .registerCarrierPrivilegesCallback(
+ logicalIndexCaptor.capture(), any(), callbackCaptor.capture());
+
+ final List<Integer> registeredIndices = logicalIndexCaptor.getAllValues();
+ final List<TelephonyManager.CarrierPrivilegesCallback> registeredCallbacks =
+ callbackCaptor.getAllValues();
+ for (int i = 0; i < registeredIndices.size(); ++i) {
+ final int logicalIndex = registeredIndices.get(i);
+ final TelephonyManager.CarrierPrivilegesCallback callback =
+ registeredCallbacks.get(i);
+
+ mCarrierPrivilegedCallbacks.put(logicalIndex, callback);
+
+ // The API contract promises a callback upon registration with the current list.
+ final ArraySet<String> cpApps = mCarrierPrivilegedApps.get(logicalIndex);
+ callback.onCarrierPrivilegesChanged(
+ cpApps == null ? Collections.emptySet() : cpApps,
+ Collections.emptySet());
+ }
+ waitForQuietModuleThread();
+ }
+ }
+
private void setUidBias(int uid, int bias) {
int prevBias = mJobSchedulerService.getUidBias(uid);
doReturn(bias).when(mJobSchedulerService).getUidBias(uid);
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp b/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp
new file mode 100644
index 0000000..e94b8ad
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp
@@ -0,0 +1,56 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "RollbackPackageHealthObserverTests",
+
+ srcs: [
+ "*.java",
+ ],
+
+ static_libs: [
+ "androidx.test.runner",
+ "mockito-target-extended-minus-junit4",
+ "services.core",
+ "truth",
+ "flag-junit",
+ ],
+
+ libs: [
+ "android.test.mock",
+ "android.test.base",
+ "android.test.runner",
+ ],
+
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ "libstaticjvmtiagent",
+ ],
+
+ certificate: "platform",
+ platform_apis: true,
+ test_suites: [
+ "device-tests",
+ "automotive-tests",
+ ],
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidManifest.xml b/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidManifest.xml
new file mode 100644
index 0000000..c52dbde
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.rollback">
+
+ <uses-sdk android:targetSdkVersion="35" />
+
+ <application android:testOnly="true"
+ android:debuggable="true">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.server.rollback"
+ android:label="Frameworks Rollback Package Health Observer test" />
+</manifest>
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidTest.xml b/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidTest.xml
new file mode 100644
index 0000000..635183c
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidTest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<configuration description="Runs Rollback Package Health Observer Tests.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-instrumentation" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="install-arg" value="-t" />
+ <option name="test-file-name" value="RollbackPackageHealthObserverTests.apk" />
+ </target_preparer>
+
+ <option name="test-tag" value="RollbackPackageHealthObserverTests" />
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.server.rollback" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration>
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING b/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING
index e42bdad..6ac56bf 100644
--- a/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING
@@ -1,7 +1,7 @@
{
"postsubmit": [
{
- "name": "FrameworksMockingServicesTests",
+ "name": "RollbackPackageHealthObserverTests",
"options": [
{
"include-filter": "com.android.server.rollback"
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
new file mode 100644
index 0000000..7ecc7fd
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
@@ -0,0 +1,640 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wallpaper;
+
+import static android.app.WallpaperManager.LANDSCAPE;
+import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
+import static android.app.WallpaperManager.PORTRAIT;
+import static android.app.WallpaperManager.SQUARE_LANDSCAPE;
+import static android.app.WallpaperManager.SQUARE_PORTRAIT;
+import static android.app.WallpaperManager.getOrientation;
+import static android.app.WallpaperManager.getRotatedOrientation;
+
+import static com.android.window.flags.Flags.FLAG_MULTI_CROP;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.util.SparseArray;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * Unit tests for the most important helpers of {@link WallpaperCropper}, in particular
+ * {@link WallpaperCropper#getCrop(Point, Point, SparseArray, boolean)}.
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@RequiresFlagsEnabled(FLAG_MULTI_CROP)
+public class WallpaperCropperTest {
+
+ @Mock
+ private WallpaperDisplayHelper mWallpaperDisplayHelper;
+ private WallpaperCropper mWallpaperCropper;
+
+ private static final Point PORTRAIT_ONE = new Point(500, 800);
+ private static final Point PORTRAIT_TWO = new Point(400, 1000);
+ private static final Point PORTRAIT_THREE = new Point(2000, 800);
+ private static final Point PORTRAIT_FOUR = new Point(1600, 1000);
+
+ private static final Point SQUARE_PORTRAIT_ONE = new Point(1000, 800);
+ private static final Point SQUARE_LANDSCAPE_ONE = new Point(800, 1000);
+
+ /**
+ * Common device: a single screen of portrait/landscape orientation
+ */
+ private static final List<Point> STANDARD_DISPLAY = List.of(PORTRAIT_ONE);
+
+ /** 1: folded: portrait, unfolded: square with w < h */
+ private static final List<Point> FOLDABLE_ONE = List.of(PORTRAIT_ONE, SQUARE_PORTRAIT_ONE);
+
+ /** 2: folded: portrait, unfolded: square with w > h */
+ private static final List<Point> FOLDABLE_TWO = List.of(PORTRAIT_TWO, SQUARE_LANDSCAPE_ONE);
+
+ /** 3: folded: square with w < h, unfolded: portrait */
+ private static final List<Point> FOLDABLE_THREE = List.of(SQUARE_PORTRAIT_ONE, PORTRAIT_THREE);
+
+ /** 4: folded: square with w > h, unfolded: portrait */
+ private static final List<Point> FOLDABLE_FOUR = List.of(SQUARE_LANDSCAPE_ONE, PORTRAIT_FOUR);
+
+ /**
+ * List of different sets of displays for foldable devices. Foldable devices have two displays:
+ * a folded (smaller) unfolded (larger).
+ */
+ private static final List<List<Point>> ALL_FOLDABLE_DISPLAYS = List.of(
+ FOLDABLE_ONE, FOLDABLE_TWO, FOLDABLE_THREE, FOLDABLE_FOUR);
+
+ private SparseArray<Point> mDisplaySizes = new SparseArray<>();
+ private int mFolded = ORIENTATION_UNKNOWN;
+ private int mFoldedRotated = ORIENTATION_UNKNOWN;
+ private int mUnfolded = ORIENTATION_UNKNOWN;
+ private int mUnfoldedRotated = ORIENTATION_UNKNOWN;
+
+ private static final List<Integer> ALL_MODES = List.of(
+ WallpaperCropper.ADD, WallpaperCropper.REMOVE, WallpaperCropper.BALANCE);
+
+ @Before
+ public void setUp() {
+ initMocks(this);
+ mWallpaperCropper = new WallpaperCropper(mWallpaperDisplayHelper);
+ }
+
+ private void setUpWithDisplays(List<Point> displaySizes) {
+ mDisplaySizes = new SparseArray<>();
+ displaySizes.forEach(size -> {
+ mDisplaySizes.put(getOrientation(size), size);
+ Point rotated = new Point(size.y, size.x);
+ mDisplaySizes.put(getOrientation(rotated), rotated);
+ });
+ when(mWallpaperDisplayHelper.getDefaultDisplaySizes()).thenReturn(mDisplaySizes);
+ if (displaySizes.size() == 2) {
+ Point largestDisplay = displaySizes.stream().max(
+ Comparator.comparingInt(p -> p.x * p.y)).get();
+ Point smallestDisplay = displaySizes.stream().min(
+ Comparator.comparingInt(p -> p.x * p.y)).get();
+ mUnfolded = getOrientation(largestDisplay);
+ mFolded = getOrientation(smallestDisplay);
+ mUnfoldedRotated = getRotatedOrientation(mUnfolded);
+ mFoldedRotated = getRotatedOrientation(mFolded);
+ }
+ doAnswer(invocation -> getFoldedOrientation(invocation.getArgument(0)))
+ .when(mWallpaperDisplayHelper).getFoldedOrientation(anyInt());
+ doAnswer(invocation -> getUnfoldedOrientation(invocation.getArgument(0)))
+ .when(mWallpaperDisplayHelper).getUnfoldedOrientation(anyInt());
+ }
+
+ private int getFoldedOrientation(int orientation) {
+ if (orientation == ORIENTATION_UNKNOWN) return ORIENTATION_UNKNOWN;
+ if (orientation == mUnfolded) return mFolded;
+ if (orientation == mUnfoldedRotated) return mFoldedRotated;
+ return ORIENTATION_UNKNOWN;
+ }
+
+ private int getUnfoldedOrientation(int orientation) {
+ if (orientation == ORIENTATION_UNKNOWN) return ORIENTATION_UNKNOWN;
+ if (orientation == mFolded) return mUnfolded;
+ if (orientation == mFoldedRotated) return mUnfoldedRotated;
+ return ORIENTATION_UNKNOWN;
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#noParallax} successfully removes the parallax in a simple
+ * case, removing the right or left part depending on the "rtl" argument.
+ */
+ @Test
+ public void testNoParallax_noScale() {
+ Point displaySize = new Point(1000, 1000);
+ Point bitmapSize = new Point(1200, 1000);
+ Point expectedCropSize = new Point(1000, 1000);
+ Rect crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+ assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ false))
+ .isEqualTo(leftOf(crop, expectedCropSize));
+ assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ true))
+ .isEqualTo(rightOf(crop, expectedCropSize));
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#noParallax} correctly takes zooming into account.
+ */
+ @Test
+ public void testNoParallax_withScale() {
+ Point displaySize = new Point(1000, 1000);
+ Point bitmapSize = new Point(600, 500);
+ Rect crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+ Point expectedCropSize = new Point(500, 500);
+ assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ false))
+ .isEqualTo(leftOf(crop, expectedCropSize));
+ assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ true))
+ .isEqualTo(rightOf(crop, expectedCropSize));
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#noParallax} correctly removes parallax when the image is
+ * cropped, i.e. when the crop rectangle is not the full bitmap.
+ */
+ @Test
+ public void testNoParallax_withScaleAndCrop() {
+ Point displaySize = new Point(1000, 1000);
+ Point bitmapSize = new Point(2000, 2000);
+ Rect crop = new Rect(300, 1000, 900, 1500);
+ Point expectedCropSize = new Point(500, 500);
+ assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ false))
+ .isEqualTo(leftOf(crop, expectedCropSize));
+ assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ true))
+ .isEqualTo(rightOf(crop, expectedCropSize));
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getAdjustedCrop} does nothing when the crop has the same
+ * width/height ratio than the screen.
+ */
+ @Test
+ public void testGetAdjustedCrop_noOp() {
+ Point displaySize = new Point(1000, 1000);
+
+ for (Point bitmapSize: List.of(
+ new Point(1000, 1000),
+ new Point(2000, 2000),
+ new Point(500, 500))) {
+ for (Rect crop: List.of(
+ new Rect(0, 0, bitmapSize.x, bitmapSize.y),
+ new Rect(100, 200, bitmapSize.x - 100, bitmapSize.y))) {
+ for (int mode: ALL_MODES) {
+ for (boolean rtl: List.of(true, false)) {
+ for (boolean parallax: List.of(true, false)) {
+ assertThat(WallpaperCropper.getAdjustedCrop(
+ crop, bitmapSize, displaySize, parallax, rtl, mode))
+ .isEqualTo(crop);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getAdjustedCrop}, when called with parallax = true,
+ * does not keep more width than needed for {@link WallpaperCropper#MAX_PARALLAX}.
+ */
+ @Test
+ public void testGetAdjustedCrop_tooMuchParallax() {
+ Point displaySize = new Point(1000, 1000);
+ int tooLargeWidth = (int) (displaySize.x * (1 + 2 * WallpaperCropper.MAX_PARALLAX));
+ Point bitmapSize = new Point(tooLargeWidth, 1000);
+ Rect crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+ int expectedWidth = (int) (displaySize.x * (1 + WallpaperCropper.MAX_PARALLAX));
+ Point expectedCropSize = new Point(expectedWidth, 1000);
+ for (int mode: ALL_MODES) {
+ assertThat(WallpaperCropper.getAdjustedCrop(
+ crop, bitmapSize, displaySize, true, false, mode))
+ .isEqualTo(leftOf(crop, expectedCropSize));
+ assertThat(WallpaperCropper.getAdjustedCrop(
+ crop, bitmapSize, displaySize, true, true, mode))
+ .isEqualTo(rightOf(crop, expectedCropSize));
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getAdjustedCrop}, when called with parallax = true,
+ * does not remove parallax if the parallax is below {@link WallpaperCropper#MAX_PARALLAX}.
+ */
+ @Test
+ public void testGetAdjustedCrop_acceptableParallax() {
+ Point displaySize = new Point(1000, 1000);
+ List<Integer> acceptableWidths = List.of(displaySize.x,
+ (int) (displaySize.x * (1 + 0.5 * WallpaperCropper.MAX_PARALLAX)),
+ (int) (displaySize.x * (1 + 0.9 * WallpaperCropper.MAX_PARALLAX)),
+ (int) (displaySize.x * (1 + 1.0 * WallpaperCropper.MAX_PARALLAX)));
+ for (int acceptableWidth: acceptableWidths) {
+ Point bitmapSize = new Point(acceptableWidth, 1000);
+ Rect crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+ for (int mode : ALL_MODES) {
+ for (boolean rtl : List.of(false, true)) {
+ assertThat(WallpaperCropper.getAdjustedCrop(
+ crop, bitmapSize, displaySize, true, rtl, mode))
+ .isEqualTo(crop);
+ }
+ }
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getAdjustedCrop}, when called with
+ * {@link WallpaperCropper#ADD}, correctly enlarges the crop to match the display dimensions,
+ * and adds content to the crop by an equal amount on both sides when possible.
+ */
+ @Test
+ public void testGetAdjustedCrop_add() {
+ Point displaySize = new Point(1000, 1000);
+ Point bitmapSize = new Point(1000, 1000);
+
+ List<Rect> crops = List.of(
+ new Rect(0, 0, 900, 1000),
+ new Rect(0, 0, 1000, 900),
+ new Rect(0, 0, 400, 500),
+ new Rect(500, 600, 1000, 1000));
+
+ List<Rect> expectedAdjustedCrops = List.of(
+ new Rect(0, 0, 1000, 1000),
+ new Rect(0, 0, 1000, 1000),
+ new Rect(0, 0, 500, 500),
+ new Rect(500, 500, 1000, 1000));
+
+ for (int i = 0; i < crops.size(); i++) {
+ Rect crop = crops.get(i);
+ Rect expectedCrop = expectedAdjustedCrops.get(i);
+ for (boolean rtl: List.of(false, true)) {
+ assertThat(WallpaperCropper.getAdjustedCrop(
+ crop, bitmapSize, displaySize, false, rtl, WallpaperCropper.ADD))
+ .isEqualTo(expectedCrop);
+ }
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getAdjustedCrop}, when called with
+ * {@link WallpaperCropper#REMOVE}, correctly shrinks the crop to match the display dimensions,
+ * and removes content by an equal amount on both sides.
+ */
+ @Test
+ public void testGetAdjustedCrop_remove() {
+ Point displaySize = new Point(1000, 1000);
+ Point bitmapSize = new Point(1500, 1500);
+
+ List<Rect> crops = List.of(
+ new Rect(50, 0, 1150, 1000),
+ new Rect(0, 50, 1000, 1150));
+
+ Point expectedCropSize = new Point(1000, 1000);
+
+ for (Rect crop: crops) {
+ for (boolean rtl : List.of(false, true)) {
+ assertThat(WallpaperCropper.getAdjustedCrop(
+ crop, bitmapSize, displaySize, false, rtl, WallpaperCropper.REMOVE))
+ .isEqualTo(centerOf(crop, expectedCropSize));
+ }
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getAdjustedCrop}, when called with
+ * {@link WallpaperCropper#BALANCE}, gives an adjusted crop with the same center and same number
+ * of pixels when possible.
+ */
+ @Test
+ public void testGetAdjustedCrop_balance() {
+ Point displaySize = new Point(500, 1000);
+ Point transposedDisplaySize = new Point(1000, 500);
+ Point bitmapSize = new Point(1000, 1000);
+
+ List<Rect> crops = List.of(
+ new Rect(0, 250, 1000, 750),
+ new Rect(100, 0, 300, 100));
+
+ List<Rect> expectedAdjustedCrops = List.of(
+ new Rect(250, 0, 750, 1000),
+ new Rect(150, 0, 250, 200));
+
+ for (int i = 0; i < crops.size(); i++) {
+ Rect crop = crops.get(i);
+ Rect expected = expectedAdjustedCrops.get(i);
+ assertThat(WallpaperCropper.getAdjustedCrop(
+ crop, bitmapSize, displaySize, false, false, WallpaperCropper.BALANCE))
+ .isEqualTo(expected);
+
+ Rect transposedCrop = new Rect(crop.top, crop.left, crop.bottom, crop.right);
+ Rect expectedTransposed = new Rect(
+ expected.top, expected.left, expected.bottom, expected.right);
+ assertThat(WallpaperCropper.getAdjustedCrop(transposedCrop, bitmapSize,
+ transposedDisplaySize, false, false, WallpaperCropper.BALANCE))
+ .isEqualTo(expectedTransposed);
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getCrop} follows a simple centre-align strategy when
+ * no suggested crops are provided.
+ */
+ @Test
+ public void testGetCrop_noSuggestedCrops_centersWallpaper() {
+ setUpWithDisplays(STANDARD_DISPLAY);
+ Point bitmapSize = new Point(800, 1000);
+ Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+ SparseArray<Rect> suggestedCrops = new SparseArray<>();
+
+ List<Point> displaySizes = List.of(
+ new Point(500, 1000),
+ new Point(1000, 500));
+ List<Point> expectedCropSizes = List.of(
+ new Point(500, 1000),
+ new Point(800, 400));
+
+ for (int i = 0; i < displaySizes.size(); i++) {
+ Point displaySize = displaySizes.get(i);
+ Point expectedCropSize = expectedCropSizes.get(i);
+ for (boolean rtl : List.of(false, true)) {
+ assertThat(mWallpaperCropper.getCrop(
+ displaySize, bitmapSize, suggestedCrops, rtl))
+ .isEqualTo(centerOf(bitmapRect, expectedCropSize));
+ }
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getCrop} reuses a suggested crop of the same orientation
+ * as the display if possible, and does not remove additional width for parallax,
+ * but adds width if necessary.
+ */
+ @Test
+ public void testGetCrop_hasSuggestedCrop() {
+ setUpWithDisplays(STANDARD_DISPLAY);
+ Point bitmapSize = new Point(800, 1000);
+ SparseArray<Rect> suggestedCrops = new SparseArray<>();
+ suggestedCrops.put(PORTRAIT, new Rect(0, 0, 400, 800));
+ for (int otherOrientation: List.of(LANDSCAPE, SQUARE_LANDSCAPE, SQUARE_PORTRAIT)) {
+ suggestedCrops.put(otherOrientation, new Rect(0, 0, 10, 10));
+ }
+
+ for (boolean rtl : List.of(false, true)) {
+ assertThat(mWallpaperCropper.getCrop(
+ new Point(300, 800), bitmapSize, suggestedCrops, rtl))
+ .isEqualTo(suggestedCrops.get(PORTRAIT));
+ assertThat(mWallpaperCropper.getCrop(
+ new Point(500, 800), bitmapSize, suggestedCrops, rtl))
+ .isEqualTo(new Rect(0, 0, 500, 800));
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getCrop}, if there is no suggested crop of the same
+ * orientation as the display, reuses a suggested crop of the rotated orientation if possible,
+ * and preserves the center and number of pixels of the crop if possible.
+ * <p>
+ * To simplify, in this test case all crops have the same size as the display (no zoom)
+ * and are at the center of the image. Also the image is large enough to preserver the number
+ * of pixels (no additional zoom required).
+ */
+ @Test
+ public void testGetCrop_hasRotatedSuggestedCrop() {
+ setUpWithDisplays(STANDARD_DISPLAY);
+ Point bitmapSize = new Point(2000, 1800);
+ Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+ SparseArray<Rect> suggestedCrops = new SparseArray<>();
+ Point portrait = PORTRAIT_ONE;
+ Point landscape = new Point(PORTRAIT_ONE.y, PORTRAIT_ONE.x);
+ Point squarePortrait = SQUARE_PORTRAIT_ONE;
+ Point squareLandscape = new Point(SQUARE_PORTRAIT_ONE.y, SQUARE_PORTRAIT_ONE.y);
+ suggestedCrops.put(PORTRAIT, centerOf(bitmapRect, portrait));
+ suggestedCrops.put(SQUARE_LANDSCAPE, centerOf(bitmapRect, squareLandscape));
+ for (boolean rtl : List.of(false, true)) {
+ assertThat(mWallpaperCropper.getCrop(
+ landscape, bitmapSize, suggestedCrops, rtl))
+ .isEqualTo(centerOf(bitmapRect, landscape));
+
+ assertThat(mWallpaperCropper.getCrop(
+ squarePortrait, bitmapSize, suggestedCrops, rtl))
+ .isEqualTo(centerOf(bitmapRect, squarePortrait));
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getCrop}, when asked for a folded crop with a suggested
+ * crop only for the relative unfolded orientation, creates the folded crop at the center of the
+ * unfolded crop, by removing content on two sides to match the folded screen dimensions.
+ * <p>
+ * To simplify, in this test case all crops have the same size as the display (no zoom)
+ * and are at the center of the image.
+ */
+ @Test
+ public void testGetCrop_hasUnfoldedSuggestedCrop() {
+ for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) {
+ setUpWithDisplays(displaySizes);
+ Point bitmapSize = new Point(2000, 2400);
+ Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+
+ Point largestDisplay = displaySizes.stream().max(
+ Comparator.comparingInt(a -> a.x * a.y)).orElseThrow();
+ int unfoldedOne = getOrientation(largestDisplay);
+ int unfoldedTwo = getRotatedOrientation(unfoldedOne);
+ Rect unfoldedCropOne = centerOf(bitmapRect, mDisplaySizes.get(unfoldedOne));
+ Rect unfoldedCropTwo = centerOf(bitmapRect, mDisplaySizes.get(unfoldedTwo));
+ SparseArray<Rect> suggestedCrops = new SparseArray<>();
+ suggestedCrops.put(unfoldedOne, unfoldedCropOne);
+ suggestedCrops.put(unfoldedTwo, unfoldedCropTwo);
+
+ int foldedOne = getFoldedOrientation(unfoldedOne);
+ int foldedTwo = getFoldedOrientation(unfoldedTwo);
+ Point foldedDisplayOne = mDisplaySizes.get(foldedOne);
+ Point foldedDisplayTwo = mDisplaySizes.get(foldedTwo);
+
+ for (boolean rtl : List.of(false, true)) {
+ assertThat(mWallpaperCropper.getCrop(
+ foldedDisplayOne, bitmapSize, suggestedCrops, rtl))
+ .isEqualTo(centerOf(unfoldedCropOne, foldedDisplayOne));
+
+ assertThat(mWallpaperCropper.getCrop(
+ foldedDisplayTwo, bitmapSize, suggestedCrops, rtl))
+ .isEqualTo(centerOf(unfoldedCropTwo, foldedDisplayTwo));
+ }
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getCrop}, when asked for an unfolded crop with a suggested
+ * crop only for the relative folded orientation, creates the unfolded crop with the same center
+ * as the folded crop, by adding content on two sides to match the unfolded screen dimensions.
+ * <p>
+ * To simplify, in this test case all crops have the same size as the display (no zoom) and are
+ * at the center of the image. Also the image is large enough to add content.
+ */
+ @Test
+ public void testGetCrop_hasFoldedSuggestedCrop() {
+ for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) {
+ setUpWithDisplays(displaySizes);
+ Point bitmapSize = new Point(2000, 2000);
+ Rect bitmapRect = new Rect(0, 0, 2000, 2000);
+
+ Point smallestDisplay = displaySizes.stream().min(
+ Comparator.comparingInt(a -> a.x * a.y)).orElseThrow();
+ int foldedOne = getOrientation(smallestDisplay);
+ int foldedTwo = getRotatedOrientation(foldedOne);
+ Point foldedDisplayOne = mDisplaySizes.get(foldedOne);
+ Point foldedDisplayTwo = mDisplaySizes.get(foldedTwo);
+ Rect foldedCropOne = centerOf(bitmapRect, foldedDisplayOne);
+ Rect foldedCropTwo = centerOf(bitmapRect, foldedDisplayTwo);
+ SparseArray<Rect> suggestedCrops = new SparseArray<>();
+ suggestedCrops.put(foldedOne, foldedCropOne);
+ suggestedCrops.put(foldedTwo, foldedCropTwo);
+
+ int unfoldedOne = getUnfoldedOrientation(foldedOne);
+ int unfoldedTwo = getUnfoldedOrientation(foldedTwo);
+ Point unfoldedDisplayOne = mDisplaySizes.get(unfoldedOne);
+ Point unfoldedDisplayTwo = mDisplaySizes.get(unfoldedTwo);
+
+ for (boolean rtl : List.of(false, true)) {
+ assertThat(centerOf(mWallpaperCropper.getCrop(
+ unfoldedDisplayOne, bitmapSize, suggestedCrops, rtl), foldedDisplayOne))
+ .isEqualTo(foldedCropOne);
+
+ assertThat(centerOf(mWallpaperCropper.getCrop(
+ unfoldedDisplayTwo, bitmapSize, suggestedCrops, rtl), foldedDisplayTwo))
+ .isEqualTo(foldedCropTwo);
+ }
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getCrop}, when asked for an folded crop with a suggested
+ * crop only for the rotated unfolded orientation, creates the folded crop from that crop by
+ * combining a rotate + fold operation. The folded crop should have less pixels than the
+ * unfolded crop due to the fold operation which removes content on both sides of the image.
+ * <p>
+ * To simplify, in this test case all crops have the same size as the display (no zoom)
+ * and are at the center of the image.
+ */
+ @Test
+ public void testGetCrop_hasRotatedUnfoldedSuggestedCrop() {
+ for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) {
+ setUpWithDisplays(displaySizes);
+ Point bitmapSize = new Point(2000, 2000);
+ Rect bitmapRect = new Rect(0, 0, 2000, 2000);
+ Point largestDisplay = displaySizes.stream().max(
+ Comparator.comparingInt(a -> a.x * a.y)).orElseThrow();
+ int unfoldedOne = getOrientation(largestDisplay);
+ int unfoldedTwo = getRotatedOrientation(unfoldedOne);
+ for (int unfolded: List.of(unfoldedOne, unfoldedTwo)) {
+ Rect unfoldedCrop = centerOf(bitmapRect, mDisplaySizes.get(unfolded));
+ int rotatedUnfolded = getRotatedOrientation(unfolded);
+ Rect rotatedUnfoldedCrop = centerOf(bitmapRect, mDisplaySizes.get(rotatedUnfolded));
+ SparseArray<Rect> suggestedCrops = new SparseArray<>();
+ suggestedCrops.put(unfolded, unfoldedCrop);
+ int rotatedFolded = getFoldedOrientation(rotatedUnfolded);
+ Point rotatedFoldedDisplay = mDisplaySizes.get(rotatedFolded);
+
+ for (boolean rtl : List.of(false, true)) {
+ assertThat(mWallpaperCropper.getCrop(
+ rotatedFoldedDisplay, bitmapSize, suggestedCrops, rtl))
+ .isEqualTo(centerOf(rotatedUnfoldedCrop, rotatedFoldedDisplay));
+ }
+ }
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getCrop}, when asked for an unfolded crop with a suggested
+ * crop only for the rotated folded orientation, creates the unfolded crop from that crop by
+ * combining a rotate + unfold operation. The unfolded crop should have more pixels than the
+ * folded crop due to the unfold operation which adds content on two sides of the image.
+ * <p>
+ * To simplify, in this test case all crops have the same size as the display (no zoom)
+ * and are centered inside the image. Also the image is large enough to add content.
+ */
+ @Test
+ public void testGetCrop_hasRotatedFoldedSuggestedCrop() {
+ for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) {
+ setUpWithDisplays(displaySizes);
+ Point bitmapSize = new Point(2000, 2000);
+ Rect bitmapRect = new Rect(0, 0, 2000, 2000);
+
+ Point smallestDisplay = displaySizes.stream().min(
+ Comparator.comparingInt(a -> a.x * a.y)).orElseThrow();
+ int foldedOne = getOrientation(smallestDisplay);
+ int foldedTwo = getRotatedOrientation(foldedOne);
+ for (int folded: List.of(foldedOne, foldedTwo)) {
+ Rect foldedCrop = centerOf(bitmapRect, mDisplaySizes.get(folded));
+ SparseArray<Rect> suggestedCrops = new SparseArray<>();
+ suggestedCrops.put(folded, foldedCrop);
+ int rotatedFolded = getRotatedOrientation(folded);
+ int rotatedUnfolded = getUnfoldedOrientation(rotatedFolded);
+ Point rotatedFoldedDisplay = mDisplaySizes.get(rotatedFolded);
+ Rect rotatedFoldedCrop = centerOf(bitmapRect, rotatedFoldedDisplay);
+ Point rotatedUnfoldedDisplay = mDisplaySizes.get(rotatedUnfolded);
+
+ for (boolean rtl : List.of(false, true)) {
+ Rect rotatedUnfoldedCrop = mWallpaperCropper.getCrop(
+ rotatedUnfoldedDisplay, bitmapSize, suggestedCrops, rtl);
+ assertThat(centerOf(rotatedUnfoldedCrop, rotatedFoldedDisplay))
+ .isEqualTo(rotatedFoldedCrop);
+ }
+ }
+ }
+ }
+
+ private static Rect centerOf(Rect container, Point point) {
+ checkSubset(container, point);
+ int diffWidth = container.width() - point.x;
+ int diffHeight = container.height() - point.y;
+ int startX = container.left + diffWidth / 2;
+ int startY = container.top + diffHeight / 2;
+ return new Rect(startX, startY, startX + point.x, startY + point.y);
+ }
+
+ private static Rect leftOf(Rect container, Point point) {
+ Rect result = centerOf(container, point);
+ result.offset(container.left - result.left, 0);
+ return result;
+ }
+
+ private static Rect rightOf(Rect container, Point point) {
+ checkSubset(container, point);
+ Rect result = centerOf(container, point);
+ result.offset(container.right - result.right, 0);
+ return result;
+ }
+
+ private static void checkSubset(Rect container, Point point) {
+ if (container.width() < point.x || container.height() < point.y) {
+ throw new IllegalArgumentException();
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
index 4db27d2..dc26e6e 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
@@ -17,9 +17,9 @@
package com.android.server.accessibility;
import static com.android.server.accessibility.AbstractAccessibilityServiceConnection.DISPLAY_TYPE_DEFAULT;
-import static com.android.server.accessibility.AccessibilityWindowManagerTest.DisplayIdMatcher.displayId;
-import static com.android.server.accessibility.AccessibilityWindowManagerTest.WindowChangesMatcher.a11yWindowChanges;
-import static com.android.server.accessibility.AccessibilityWindowManagerTest.WindowIdMatcher.a11yWindowId;
+import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.DisplayIdMatcher.displayId;
+import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.WindowChangesMatcher.a11yWindowChanges;
+import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.WindowIdMatcher.a11yWindowId;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
@@ -27,11 +27,13 @@
import static junit.framework.Assert.assertTrue;
import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
@@ -42,6 +44,7 @@
import static org.mockito.Mockito.when;
import android.annotation.Nullable;
+import android.graphics.Point;
import android.graphics.Region;
import android.os.IBinder;
import android.os.LocaleList;
@@ -63,6 +66,7 @@
import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection;
import com.android.server.accessibility.test.MessageCapturingHandler;
+import com.android.server.wm.AccessibilityWindowsPopulator.AccessibilityWindow;
import com.android.server.wm.WindowManagerInternal;
import com.android.server.wm.WindowManagerInternal.WindowsForAccessibilityCallback;
@@ -117,9 +121,8 @@
// List of window token, mapping from windowId -> window token.
private final SparseArray<IWindow> mA11yWindowTokens = new SparseArray<>();
- // List of window info lists, mapping from displayId -> window info lists.
- private final SparseArray<ArrayList<WindowInfo>> mWindowInfos =
- new SparseArray<>();
+ // List of window info lists, mapping from displayId -> a11y window lists.
+ private final SparseArray<ArrayList<AccessibilityWindow>> mWindows = new SparseArray<>();
// List of callback, mapping from displayId -> callback.
private final SparseArray<WindowsForAccessibilityCallback> mCallbackOfWindows =
new SparseArray<>();
@@ -129,6 +132,13 @@
private final MessageCapturingHandler mHandler = new MessageCapturingHandler(null);
+ // This maps displayId -> next region offset.
+ // Touchable region must have un-occluded area so that it's exposed to a11y services.
+ // This offset can be used as left and top of new region so that top-left of each region are
+ // kept visible.
+ // It's expected to be incremented by some amount everytime the value is used.
+ private final SparseArray<Integer> mNextRegionOffsets = new SparseArray<>();
+
@Mock
private WindowManagerInternal mMockWindowManagerInternal;
@Mock
@@ -162,7 +172,7 @@
anyString(), anyInt(), anyInt(), anyInt())).thenReturn(PACKAGE_NAME);
doAnswer((invocation) -> {
- onWindowsForAccessibilityChanged(invocation.getArgument(0), false);
+ onAccessibilityWindowsChanged(invocation.getArgument(0), false);
return null;
}).when(mMockWindowManagerInternal).computeWindowsForAccessibility(anyInt());
@@ -176,7 +186,7 @@
// as top focused display before each testing starts.
startTrackingPerDisplay(Display.DEFAULT_DISPLAY);
- // AccessibilityEventSender is invoked during onWindowsForAccessibilityChanged.
+ // AccessibilityEventSender is invoked during onAccessibilityWindowsChanged.
// Resets it for mockito verify of further test case.
Mockito.reset(mMockA11yEventSender);
@@ -240,19 +250,18 @@
@Test
public void onWindowsChanged_duringTouchInteractAndFocusChange_shouldChangeActiveWindow() {
final int activeWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID);
- WindowInfo focusedWindowInfo =
- mWindowInfos.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX);
+ final WindowInfo focusedWindowInfo =
+ mWindows.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX).getWindowInfo();
assertEquals(activeWindowId, mA11yWindowManager.findWindowIdLocked(
USER_SYSTEM_ID, focusedWindowInfo.token));
focusedWindowInfo.focused = false;
- focusedWindowInfo =
- mWindowInfos.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX + 1);
- focusedWindowInfo.focused = true;
+ mWindows.get(Display.DEFAULT_DISPLAY).get(
+ DEFAULT_FOCUSED_INDEX + 1).getWindowInfo().focused = true;
mA11yWindowManager.onTouchInteractionStart();
setTopFocusedWindowAndDisplay(Display.DEFAULT_DISPLAY, DEFAULT_FOCUSED_INDEX + 1);
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
assertNotEquals(activeWindowId, mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID));
}
@@ -276,7 +285,7 @@
changeFocusedWindowOnDisplayPerDisplayFocusConfig(SECONDARY_DISPLAY_ID,
DEFAULT_FOCUSED_INDEX + 1, Display.DEFAULT_DISPLAY, DEFAULT_FOCUSED_INDEX);
- onWindowsForAccessibilityChanged(SECONDARY_DISPLAY_ID, SEND_ON_WINDOW_CHANGES);
+ onAccessibilityWindowsChanged(SECONDARY_DISPLAY_ID, SEND_ON_WINDOW_CHANGES);
// The active window should not be changed.
assertEquals(activeWindowId, mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID));
// The top focused window should not be changed.
@@ -304,8 +313,8 @@
changeFocusedWindowOnDisplayPerDisplayFocusConfig(SECONDARY_DISPLAY_ID,
DEFAULT_FOCUSED_INDEX, Display.DEFAULT_DISPLAY, DEFAULT_FOCUSED_INDEX);
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
- onWindowsForAccessibilityChanged(SECONDARY_DISPLAY_ID, SEND_ON_WINDOW_CHANGES);
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ onAccessibilityWindowsChanged(SECONDARY_DISPLAY_ID, SEND_ON_WINDOW_CHANGES);
// The active window should be changed.
assertNotEquals(activeWindowId, mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID));
// The top focused window should be changed.
@@ -315,53 +324,43 @@
@Test
public void onWindowsChanged_shouldReportCorrectLayer() {
- // AccessibilityWindowManager#onWindowsForAccessibilityChanged already invoked in setup.
+ // AccessibilityWindowManager#onAccessibilityWindowsChanged already invoked in setup.
List<AccessibilityWindowInfo> a11yWindows =
mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
for (int i = 0; i < a11yWindows.size(); i++) {
final AccessibilityWindowInfo a11yWindow = a11yWindows.get(i);
- final WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(i);
- assertThat(mWindowInfos.get(Display.DEFAULT_DISPLAY).size() - windowInfo.layer - 1,
+ assertThat(mWindows.get(Display.DEFAULT_DISPLAY).size() - i - 1,
is(a11yWindow.getLayer()));
}
}
@Test
public void onWindowsChanged_shouldReportCorrectOrder() {
- // AccessibilityWindowManager#onWindowsForAccessibilityChanged already invoked in setup.
+ // AccessibilityWindowManager#onAccessibilityWindowsChanged already invoked in setup.
List<AccessibilityWindowInfo> a11yWindows =
mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
for (int i = 0; i < a11yWindows.size(); i++) {
final AccessibilityWindowInfo a11yWindow = a11yWindows.get(i);
final IBinder windowToken = mA11yWindowManager
.getWindowTokenForUserAndWindowIdLocked(USER_SYSTEM_ID, a11yWindow.getId());
- final WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(i);
+ final WindowInfo windowInfo = mWindows.get(Display.DEFAULT_DISPLAY)
+ .get(i).getWindowInfo();
assertThat(windowToken, is(windowInfo.token));
}
}
@Test
public void onWindowsChangedAndForceSend_shouldUpdateWindows() {
- final WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0);
- final int correctLayer =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0).getLayer();
- windowInfo.layer += 1;
+ assertNotEquals("new title",
+ toString(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY)
+ .get(0).getTitle()));
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
- assertNotEquals(correctLayer,
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0).getLayer());
- }
+ mWindows.get(Display.DEFAULT_DISPLAY).get(0).getWindowInfo().title = "new title";
- @Test
- public void onWindowsChangedNoForceSend_layerChanged_shouldNotUpdateWindows() {
- final WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0);
- final int correctLayer =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0).getLayer();
- windowInfo.layer += 1;
-
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
- assertEquals(correctLayer,
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0).getLayer());
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
+ assertEquals("new title",
+ toString(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY)
+ .get(0).getTitle()));
}
@Test
@@ -371,14 +370,10 @@
mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0);
final IWindow token = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
true, USER_SYSTEM_ID);
- final WindowInfo windowInfo = WindowInfo.obtain();
- windowInfo.type = AccessibilityWindowInfo.TYPE_APPLICATION;
- windowInfo.token = token.asBinder();
- windowInfo.layer = 0;
- windowInfo.regionInScreen.set(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
- mWindowInfos.get(Display.DEFAULT_DISPLAY).set(0, windowInfo);
+ mWindows.get(Display.DEFAULT_DISPLAY).set(0,
+ createMockAccessibilityWindow(token, Display.DEFAULT_DISPLAY));
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
assertNotEquals(oldWindow,
mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0));
}
@@ -386,12 +381,12 @@
@Test
public void onWindowsChangedNoForceSend_focusChanged_shouldUpdateWindows() {
final WindowInfo focusedWindowInfo =
- mWindowInfos.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX);
- final WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0);
+ mWindows.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX).getWindowInfo();
+ final WindowInfo windowInfo = mWindows.get(Display.DEFAULT_DISPLAY).get(0).getWindowInfo();
focusedWindowInfo.focused = false;
windowInfo.focused = true;
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
assertTrue(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0)
.isFocused());
}
@@ -500,15 +495,18 @@
@Test
public void computePartialInteractiveRegionForWindow_wholeVisible_returnWholeRegion() {
// Updates top 2 z-order WindowInfo are whole visible.
- WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0);
- windowInfo.regionInScreen.set(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2);
- windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(1);
- windowInfo.regionInScreen.set(0, SCREEN_HEIGHT / 2,
- SCREEN_WIDTH, SCREEN_HEIGHT);
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ final AccessibilityWindow firstWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ setRegionForMockAccessibilityWindow(firstWindow,
+ new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2));
+ final AccessibilityWindow secondWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(1);
+ setRegionForMockAccessibilityWindow(secondWindow,
+ new Region(0, SCREEN_HEIGHT / 2, SCREEN_WIDTH, SCREEN_HEIGHT));
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
final List<AccessibilityWindowInfo> a11yWindows =
mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasSize(2));
final Region outBounds = new Region();
int windowId = a11yWindows.get(0).getId();
@@ -526,12 +524,17 @@
@Test
public void computePartialInteractiveRegionForWindow_halfVisible_returnHalfRegion() {
// Updates z-order #1 WindowInfo is half visible.
- WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0);
- windowInfo.regionInScreen.set(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2);
+ final AccessibilityWindow firstWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ setRegionForMockAccessibilityWindow(firstWindow,
+ new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2));
+ final AccessibilityWindow secondWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(1);
+ setRegionForMockAccessibilityWindow(secondWindow,
+ new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
final List<AccessibilityWindowInfo> a11yWindows =
mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasSize(2));
final Region outBounds = new Region();
int windowId = a11yWindows.get(1).getId();
@@ -542,9 +545,17 @@
@Test
public void computePartialInteractiveRegionForWindow_notVisible_returnEmptyRegion() {
- // Since z-order #0 WindowInfo is full screen, z-order #1 WindowInfo should be invisible.
+ // z-order #0 WindowInfo is full screen, z-order #1 WindowInfo should be invisible.
+ final AccessibilityWindow firstWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ setRegionForMockAccessibilityWindow(firstWindow,
+ new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
final List<AccessibilityWindowInfo> a11yWindows =
mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ // Note that the second window is also exposed even if region is empty because it's focused.
+ assertThat(a11yWindows, hasSize(2));
final Region outBounds = new Region();
int windowId = a11yWindows.get(1).getId();
@@ -555,16 +566,21 @@
@Test
public void computePartialInteractiveRegionForWindow_partialVisible_returnVisibleRegion() {
// Updates z-order #0 WindowInfo to have two interact-able areas.
- Region region = new Region(0, 0, SCREEN_WIDTH, 200);
+ final Region region = new Region(0, 0, SCREEN_WIDTH, 200);
region.op(0, SCREEN_HEIGHT - 200, SCREEN_WIDTH, SCREEN_HEIGHT, Region.Op.UNION);
- WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0);
- windowInfo.regionInScreen.set(region);
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ final AccessibilityWindow firstWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ setRegionForMockAccessibilityWindow(firstWindow, region);
+ final AccessibilityWindow secondWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(1);
+ setRegionForMockAccessibilityWindow(secondWindow,
+ new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
final List<AccessibilityWindowInfo> a11yWindows =
mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasSize(2));
final Region outBounds = new Region();
- int windowId = a11yWindows.get(1).getId();
+ final int windowId = a11yWindows.get(1).getId();
mA11yWindowManager.computePartialInteractiveRegionForWindowLocked(windowId, outBounds);
assertFalse(outBounds.getBounds().isEmpty());
@@ -575,7 +591,8 @@
@Test
public void updateActiveAndA11yFocusedWindow_windowStateChangedEvent_noTracking_shouldUpdate() {
final IBinder eventWindowToken =
- mWindowInfos.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX + 1).token;
+ mWindows.get(Display.DEFAULT_DISPLAY)
+ .get(DEFAULT_FOCUSED_INDEX + 1).getWindowInfo().token;
final int eventWindowId = mA11yWindowManager.findWindowIdLocked(
USER_SYSTEM_ID, eventWindowToken);
when(mMockWindowManagerInternal.getFocusedWindowTokenFromWindowStates())
@@ -766,7 +783,8 @@
public void onTouchInteractionEnd_noServiceInteractiveWindow_shouldClearA11yFocus()
throws RemoteException {
final IBinder defaultFocusWinToken =
- mWindowInfos.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX).token;
+ mWindows.get(Display.DEFAULT_DISPLAY).get(
+ DEFAULT_FOCUSED_INDEX).getWindowInfo().token;
final int defaultFocusWindowId = mA11yWindowManager.findWindowIdLocked(
USER_SYSTEM_ID, defaultFocusWinToken);
when(mMockWindowManagerInternal.getFocusedWindowTokenFromWindowStates())
@@ -811,8 +829,8 @@
@Test
public void getPictureInPictureWindow_shouldNotNull() {
assertNull(mA11yWindowManager.getPictureInPictureWindowLocked());
- mWindowInfos.get(Display.DEFAULT_DISPLAY).get(1).inPictureInPicture = true;
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ mWindows.get(Display.DEFAULT_DISPLAY).get(1).getWindowInfo().inPictureInPicture = true;
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
assertNotNull(mA11yWindowManager.getPictureInPictureWindowLocked());
}
@@ -826,8 +844,9 @@
final IAccessibilityInteractionConnection mockRemoteConnection =
mA11yWindowManager.getConnectionLocked(
USER_SYSTEM_ID, outsideWindowId).getRemote();
- mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0).hasFlagWatchOutsideTouch = true;
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ mWindows.get(Display.DEFAULT_DISPLAY).get(0).getWindowInfo().hasFlagWatchOutsideTouch =
+ true;
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
mA11yWindowManager.notifyOutsideTouch(USER_SYSTEM_ID, targetWindowId);
verify(mockRemoteConnection).notifyOutsideTouch();
@@ -945,18 +964,14 @@
@Test
public void sendAccessibilityEventOnWindowRemoval() {
- final ArrayList<WindowInfo> infos = mWindowInfos.get(Display.DEFAULT_DISPLAY);
+ final ArrayList<AccessibilityWindow> windows = mWindows.get(Display.DEFAULT_DISPLAY);
// Removing index 0 because it's not focused, and avoids unnecessary layer change.
final int windowId =
getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 0);
- infos.remove(0);
- for (WindowInfo info : infos) {
- // Adjust layer number because it should start from 0.
- info.layer--;
- }
+ windows.remove(0);
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
final ArgumentCaptor<AccessibilityEvent> captor =
ArgumentCaptor.forClass(AccessibilityEvent.class);
@@ -970,21 +985,15 @@
@Test
public void sendAccessibilityEventOnWindowAddition() throws RemoteException {
- final ArrayList<WindowInfo> infos = mWindowInfos.get(Display.DEFAULT_DISPLAY);
-
- for (WindowInfo info : infos) {
- // Adjust layer number because new window will have 0 so that layer number in
- // A11yWindowInfo in window won't be changed.
- info.layer++;
- }
+ final ArrayList<AccessibilityWindow> windows = mWindows.get(Display.DEFAULT_DISPLAY);
final IWindow token = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
false, USER_SYSTEM_ID);
- addWindowInfo(infos, token, 0);
- final int windowId =
- getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, infos.size() - 1);
+ // Adding window to the front so that other windows' layer won't change.
+ windows.add(0, createMockAccessibilityWindow(token, Display.DEFAULT_DISPLAY));
+ final int windowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 0);
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
final ArgumentCaptor<AccessibilityEvent> captor =
ArgumentCaptor.forClass(AccessibilityEvent.class);
@@ -998,11 +1007,11 @@
@Test
public void sendAccessibilityEventOnWindowChange() {
- final ArrayList<WindowInfo> infos = mWindowInfos.get(Display.DEFAULT_DISPLAY);
- infos.get(0).title = "new title";
+ final ArrayList<AccessibilityWindow> windows = mWindows.get(Display.DEFAULT_DISPLAY);
+ windows.get(0).getWindowInfo().title = "new title";
final int windowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 0);
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
final ArgumentCaptor<AccessibilityEvent> captor =
ArgumentCaptor.forClass(AccessibilityEvent.class);
@@ -1020,42 +1029,41 @@
}
private void startTrackingPerDisplay(int displayId) throws RemoteException {
- ArrayList<WindowInfo> windowInfosForDisplay = new ArrayList<>();
+ ArrayList<AccessibilityWindow> windowsForDisplay = new ArrayList<>();
// Adds RemoteAccessibilityConnection into AccessibilityWindowManager, and copy
// mock window token into mA11yWindowTokens. Also, preparing WindowInfo mWindowInfos
// for the test.
- int layer = 0;
for (int i = 0; i < NUM_GLOBAL_WINDOWS; i++) {
final IWindow token = addAccessibilityInteractionConnection(displayId,
true, USER_SYSTEM_ID);
- addWindowInfo(windowInfosForDisplay, token, layer++);
+ windowsForDisplay.add(createMockAccessibilityWindow(token, displayId));
}
for (int i = 0; i < NUM_APP_WINDOWS; i++) {
final IWindow token = addAccessibilityInteractionConnection(displayId,
false, USER_SYSTEM_ID);
- addWindowInfo(windowInfosForDisplay, token, layer++);
+ windowsForDisplay.add(createMockAccessibilityWindow(token, displayId));
}
// Sets up current focused window of display.
// Each display has its own current focused window if config_perDisplayFocusEnabled is true.
// Otherwise only default display needs to current focused window.
if (mSupportPerDisplayFocus || displayId == Display.DEFAULT_DISPLAY) {
- windowInfosForDisplay.get(DEFAULT_FOCUSED_INDEX).focused = true;
+ windowsForDisplay.get(DEFAULT_FOCUSED_INDEX).getWindowInfo().focused = true;
}
// Turns on windows tracking, and update window info.
mA11yWindowManager.startTrackingWindows(displayId, false);
// Puts window lists into array.
- mWindowInfos.put(displayId, windowInfosForDisplay);
+ mWindows.put(displayId, windowsForDisplay);
// Sets the default display is the top focused display and
// its current focused window is the top focused window.
if (displayId == Display.DEFAULT_DISPLAY) {
setTopFocusedWindowAndDisplay(displayId, DEFAULT_FOCUSED_INDEX);
}
// Invokes callback for sending window lists to A11y framework.
- onWindowsForAccessibilityChanged(displayId, FORCE_SEND);
+ onAccessibilityWindowsChanged(displayId, FORCE_SEND);
assertEquals(mA11yWindowManager.getWindowListLocked(displayId).size(),
- windowInfosForDisplay.size());
+ windowsForDisplay.size());
}
private WindowsForAccessibilityCallback getWindowsForAccessibilityCallbacks(int displayId) {
@@ -1109,36 +1117,28 @@
return windowId;
}
- private void addWindowInfo(ArrayList<WindowInfo> windowInfos, IWindow windowToken, int layer) {
- final WindowInfo windowInfo = WindowInfo.obtain();
- windowInfo.type = AccessibilityWindowInfo.TYPE_APPLICATION;
- windowInfo.token = windowToken.asBinder();
- windowInfo.layer = layer;
- windowInfo.regionInScreen.set(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
- windowInfos.add(windowInfo);
- }
-
private int getWindowIdFromWindowInfosForDisplay(int displayId, int index) {
- final IBinder windowToken = mWindowInfos.get(displayId).get(index).token;
+ final IBinder windowToken = mWindows.get(displayId).get(index).getWindowInfo().token;
return mA11yWindowManager.findWindowIdLocked(
USER_SYSTEM_ID, windowToken);
}
private void setTopFocusedWindowAndDisplay(int displayId, int index) {
// Sets the top focus window.
- mTopFocusedWindowToken = mWindowInfos.get(displayId).get(index).token;
+ mTopFocusedWindowToken = mWindows.get(displayId).get(index).getWindowInfo().token;
// Sets the top focused display.
mTopFocusedDisplayId = displayId;
}
- private void onWindowsForAccessibilityChanged(int displayId, boolean forceSend) {
+ private void onAccessibilityWindowsChanged(int displayId, boolean forceSend) {
WindowsForAccessibilityCallback callbacks = mCallbackOfWindows.get(displayId);
if (callbacks == null) {
callbacks = getWindowsForAccessibilityCallbacks(displayId);
mCallbackOfWindows.put(displayId, callbacks);
}
- callbacks.onWindowsForAccessibilityChanged(forceSend, mTopFocusedDisplayId,
- mTopFocusedWindowToken, mWindowInfos.get(displayId));
+ callbacks.onAccessibilityWindowsChanged(forceSend, mTopFocusedDisplayId,
+ mTopFocusedWindowToken, new Point(SCREEN_WIDTH, SCREEN_HEIGHT),
+ mWindows.get(displayId));
}
private void changeFocusedWindowOnDisplayPerDisplayFocusConfig(
@@ -1147,23 +1147,23 @@
if (mSupportPerDisplayFocus) {
// Gets the old focused window of display which wants to change focused window.
WindowInfo focusedWindowInfo =
- mWindowInfos.get(changeFocusedDisplayId).get(oldFocusedWindowIndex);
+ mWindows.get(changeFocusedDisplayId).get(oldFocusedWindowIndex).getWindowInfo();
// Resets the focus of old focused window.
focusedWindowInfo.focused = false;
// Gets the new window of display which wants to change focused window.
focusedWindowInfo =
- mWindowInfos.get(changeFocusedDisplayId).get(newFocusedWindowIndex);
+ mWindows.get(changeFocusedDisplayId).get(newFocusedWindowIndex).getWindowInfo();
// Sets the focus of new focused window.
focusedWindowInfo.focused = true;
} else {
// Gets the window of display which wants to change focused window.
WindowInfo focusedWindowInfo =
- mWindowInfos.get(changeFocusedDisplayId).get(newFocusedWindowIndex);
+ mWindows.get(changeFocusedDisplayId).get(newFocusedWindowIndex).getWindowInfo();
// Sets the focus of new focused window.
focusedWindowInfo.focused = true;
// Gets the old focused window of old top focused display.
focusedWindowInfo =
- mWindowInfos.get(oldTopFocusedDisplayId).get(oldFocusedWindowIndex);
+ mWindows.get(oldTopFocusedDisplayId).get(oldFocusedWindowIndex).getWindowInfo();
// Resets the focus of old focused window.
focusedWindowInfo.focused = false;
// Changes the top focused display and window.
@@ -1171,6 +1171,42 @@
}
}
+ private AccessibilityWindow createMockAccessibilityWindow(IWindow windowToken, int displayId) {
+ final WindowInfo windowInfo = WindowInfo.obtain();
+ // TODO(b/325341171): add tests with various kinds of windows such as
+ // changing window types, touchable or not, trusted or not, etc.
+ windowInfo.type = WindowManager.LayoutParams.TYPE_APPLICATION;
+ windowInfo.token = windowToken.asBinder();
+
+ final AccessibilityWindow window = Mockito.mock(AccessibilityWindow.class);
+ when(window.getWindowInfo()).thenReturn(windowInfo);
+ when(window.ignoreRecentsAnimationForAccessibility()).thenReturn(false);
+ when(window.isFocused()).thenAnswer(invocation -> windowInfo.focused);
+ when(window.isTouchable()).thenReturn(true);
+ when(window.getType()).thenReturn(windowInfo.type);
+
+ setRegionForMockAccessibilityWindow(window, nextToucableRegion(displayId));
+ return window;
+ }
+
+ private void setRegionForMockAccessibilityWindow(AccessibilityWindow window, Region region) {
+ doAnswer(invocation -> {
+ ((Region) invocation.getArgument(0)).set(region);
+ return null;
+ }).when(window).getTouchableRegionInScreen(any(Region.class));
+ doAnswer(invocation -> {
+ ((Region) invocation.getArgument(0)).set(region);
+ return null;
+ }).when(window).getTouchableRegionInWindow(any(Region.class));
+ }
+
+ private Region nextToucableRegion(int displayId) {
+ final int topLeft = mNextRegionOffsets.get(displayId, 0);
+ final int bottomRight = topLeft + 100;
+ mNextRegionOffsets.put(displayId, topLeft + 10);
+ return new Region(topLeft, topLeft, bottomRight, bottomRight);
+ }
+
@Nullable
private static String toString(@Nullable CharSequence cs) {
return cs == null ? null : cs.toString();
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 8c0d44c..7fbd521 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -189,6 +189,8 @@
FullScreenMagnificationVibrationHelper mMockFullScreenMagnificationVibrationHelper;
@Mock
FullScreenMagnificationGestureHandler.MagnificationLogger mMockMagnificationLogger;
+ @Mock
+ OneFingerPanningSettingsProvider mMockOneFingerPanningSettingsProvider;
@Rule
public final TestableContext mContext = new TestableContext(getInstrumentation().getContext());
@@ -266,6 +268,7 @@
mMgh.onDestroy();
mFullScreenMagnificationController.unregister(DISPLAY_0);
verify(mWindowMagnificationPromptController).onDestroy();
+ verify(mMockOneFingerPanningSettingsProvider).unregister();
Settings.Secure.putFloatForUser(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
mOriginalMagnificationPersistedScale,
@@ -288,11 +291,10 @@
DISPLAY_0,
mMockFullScreenMagnificationVibrationHelper,
mMockMagnificationLogger,
- ViewConfiguration.get(mContext));
+ ViewConfiguration.get(mContext),
+ mMockOneFingerPanningSettingsProvider);
if (isWatch()) {
- h.setSinglePanningEnabled(true);
- } else {
- h.setSinglePanningEnabled(false);
+ enableOneFingerPanning(true);
}
mHandler = new TestHandler(h.mDetectingState, mClock) {
@Override
@@ -607,8 +609,8 @@
@Test
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
public void testTwoFingerTap_StateIsActivated_shouldInDelegating() {
- assumeTrue(mMgh.mIsSinglePanningEnabled);
- mMgh.setSinglePanningEnabled(false);
+ assumeTrue(isWatch());
+ enableOneFingerPanning(false);
goFromStateIdleTo(STATE_ACTIVATED);
allowEventDelegation();
@@ -623,8 +625,8 @@
@Test
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
public void testTwoFingerTap_StateIsIdle_shouldInDelegating() {
- assumeTrue(mMgh.mIsSinglePanningEnabled);
- mMgh.setSinglePanningEnabled(false);
+ assumeTrue(isWatch());
+ enableOneFingerPanning(false);
goFromStateIdleTo(STATE_IDLE);
allowEventDelegation();
@@ -830,7 +832,7 @@
@Test
public void testActionUpNotAtEdge_singlePanningState_detectingState() {
- assumeTrue(mMgh.mIsSinglePanningEnabled);
+ assumeTrue(isWatch());
goFromStateIdleTo(STATE_SINGLE_PANNING);
send(upEvent());
@@ -841,8 +843,8 @@
@Test
public void testScroll_SinglePanningDisabled_delegatingState() {
- assumeTrue(mMgh.mIsSinglePanningEnabled);
- mMgh.setSinglePanningEnabled(false);
+ assumeTrue(isWatch());
+ enableOneFingerPanning(false);
goFromStateIdleTo(STATE_ACTIVATED);
allowEventDelegation();
@@ -854,7 +856,7 @@
@Test
@FlakyTest
public void testScroll_singleHorizontalPanningAndAtEdge_leftEdgeOverscroll() {
- assumeTrue(mMgh.mIsSinglePanningEnabled);
+ assumeTrue(isWatch());
goFromStateIdleTo(STATE_SINGLE_PANNING);
float centerY =
(INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.bottom) / 2.0f;
@@ -878,7 +880,7 @@
@Test
@FlakyTest
public void testScroll_singleHorizontalPanningAndAtEdge_rightEdgeOverscroll() {
- assumeTrue(mMgh.mIsSinglePanningEnabled);
+ assumeTrue(isWatch());
goFromStateIdleTo(STATE_SINGLE_PANNING);
float centerY =
(INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.bottom) / 2.0f;
@@ -902,7 +904,7 @@
@Test
@FlakyTest
public void testScroll_singleVerticalPanningAndAtEdge_verticalOverscroll() {
- assumeTrue(mMgh.mIsSinglePanningEnabled);
+ assumeTrue(isWatch());
goFromStateIdleTo(STATE_SINGLE_PANNING);
float centerX =
(INITIAL_MAGNIFICATION_BOUNDS.right + INITIAL_MAGNIFICATION_BOUNDS.left) / 2.0f;
@@ -924,7 +926,7 @@
@Test
public void testScroll_singlePanningAndAtEdge_noOverscroll() {
- assumeTrue(mMgh.mIsSinglePanningEnabled);
+ assumeTrue(isWatch());
goFromStateIdleTo(STATE_SINGLE_PANNING);
float centerY =
(INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.bottom) / 2.0f;
@@ -946,7 +948,7 @@
@Test
public void testScroll_singleHorizontalPanningAndAtEdge_vibrate() {
- assumeTrue(mMgh.mIsSinglePanningEnabled);
+ assumeTrue(isWatch());
goFromStateIdleTo(STATE_SINGLE_PANNING);
mFullScreenMagnificationController.setCenter(
DISPLAY_0,
@@ -970,7 +972,7 @@
@Test
public void testScroll_singleVerticalPanningAndAtEdge_doNotVibrate() {
- assumeTrue(mMgh.mIsSinglePanningEnabled);
+ assumeTrue(isWatch());
goFromStateIdleTo(STATE_SINGLE_PANNING);
mFullScreenMagnificationController.setCenter(
DISPLAY_0,
@@ -993,8 +995,9 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FULLSCREEN_FLING_GESTURE)
public void singleFinger_testScrollAfterMagnified_startsFling() {
- assumeTrue(mMgh.mIsSinglePanningEnabled);
+ assumeTrue(isWatch());
goFromStateIdleTo(STATE_ACTIVATED);
swipeAndHold();
@@ -1274,6 +1277,10 @@
mFullScreenMagnificationController.reset(DISPLAY_0, /* animate= */ false);
}
+ private void enableOneFingerPanning(boolean enable) {
+ when(mMockOneFingerPanningSettingsProvider.isOneFingerPanningEnabled()).thenReturn(enable);
+ }
+
private void assertActionsInOrder(List<MotionEvent> actualEvents,
List<Integer> expectedActions) {
assertTrue(actualEvents.size() == expectedActions.size());
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/OneFingerPanningSettingsProviderTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/OneFingerPanningSettingsProviderTest.java
new file mode 100644
index 0000000..ac46ef9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/OneFingerPanningSettingsProviderTest.java
@@ -0,0 +1,153 @@
+/*
+ * 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.server.accessibility.magnification;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import android.provider.Settings;
+import android.testing.TestableContext;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.accessibility.magnification.OneFingerPanningSettingsProvider.State;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class OneFingerPanningSettingsProviderTest {
+
+ @Rule
+ public final TestableContext mContext = new TestableContext(getInstrumentation().getContext());
+
+ private boolean mDefaultValue;
+ private boolean mOriginalIsOneFingerPanningEnabled;
+
+ private OneFingerPanningSettingsProvider mProvider;
+
+ @Before
+ public void setup() {
+ mDefaultValue = OneFingerPanningSettingsProvider.isOneFingerPanningEnabledDefault(mContext);
+ mOriginalIsOneFingerPanningEnabled = isSecureSettingsEnabled();
+ }
+
+ @After
+ public void tearDown() {
+ enableSecureSettings(mOriginalIsOneFingerPanningEnabled);
+ if (mProvider != null) {
+ mProvider.unregister();
+ }
+ }
+
+ @Test
+ public void isOneFingerPanningEnabled_flagDisabled_matchesDefault() {
+ mProvider = new OneFingerPanningSettingsProvider(mContext, /* featureFlagEnabled */ false);
+
+ assertThat(mProvider.isOneFingerPanningEnabled()).isEqualTo(mDefaultValue);
+ }
+
+ @Test
+ public void isOneFingerPanningEnabled_flagEnabledSettingEnabled_true() {
+ enableSecureSettings(true);
+ mProvider = new OneFingerPanningSettingsProvider(mContext, /* featureFlagEnabled */ true);
+
+ assertTrue(mProvider.isOneFingerPanningEnabled());
+ }
+
+ @Test
+ public void isOneFingerPanningEnabled_flagEnabledSettingDisabled_false() {
+ enableSecureSettings(false);
+ mProvider = new OneFingerPanningSettingsProvider(mContext, /* featureFlagEnabled */ true);
+
+ assertFalse(mProvider.isOneFingerPanningEnabled());
+ }
+
+ @Test
+ public void isOneFingerPanningEnabled_flagEnabledSettingsFalse_false() {
+ mProvider = new OneFingerPanningSettingsProvider(mContext, /* featureFlagEnabled */ true);
+
+ // Simulate observer triggered.
+ enableSecureSettings(false);
+ mProvider.mObserver.onChange(/* selfChange= */ false);
+
+ assertFalse(mProvider.isOneFingerPanningEnabled());
+ }
+
+ @Test
+ public void isOneFingerPanningEnabled_flagEnabledSettingsTrue_true() {
+ mProvider = new OneFingerPanningSettingsProvider(mContext, /* featureFlagEnabled */ true);
+
+ // Simulate observer triggered.
+ enableSecureSettings(true);
+ mProvider.mObserver.onChange(/* selfChange= */ false);
+
+ assertTrue(mProvider.isOneFingerPanningEnabled());
+ }
+
+ @Test
+ public void isOneFingerPanningEnabled_flagDisabledSettingsChanges_valueUnchanged() {
+ mProvider = new OneFingerPanningSettingsProvider(mContext, /* featureFlagEnabled */ false);
+ var previousValue = mProvider.isOneFingerPanningEnabled();
+
+ enableSecureSettings(!previousValue);
+
+ assertThat(mProvider.isOneFingerPanningEnabled()).isEqualTo(previousValue);
+ assertThat(mProvider.isOneFingerPanningEnabled()).isEqualTo(mDefaultValue);
+ }
+
+ @Test
+ public void unregister_featureEnabled_contentResolverNull() {
+ var provider = new OneFingerPanningSettingsProvider(
+ mContext, /* featureFlagEnabled */ true);
+
+ provider.unregister();
+
+ assertThat(provider.mContentResolver).isNull();
+ }
+
+ @Test
+ public void unregister_featureDisabled_noError() {
+ var provider = new OneFingerPanningSettingsProvider(
+ mContext, /* featureFlagEnabled */ false);
+
+ provider.unregister();
+ }
+
+ private void enableSecureSettings(boolean enable) {
+ Settings.Secure.putIntForUser(
+ mContext.getContentResolver(),
+ OneFingerPanningSettingsProvider.KEY,
+ enable ? State.ON : State.OFF,
+ mContext.getUserId());
+ }
+
+ private boolean isSecureSettingsEnabled() {
+ return State.ON == Settings.Secure.getIntForUser(
+ mContext.getContentResolver(),
+ OneFingerPanningSettingsProvider.KEY,
+ mDefaultValue ? State.ON : State.OFF,
+ mContext.getUserId());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
index bb00634..fa1fd90 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
@@ -344,6 +344,21 @@
verifyNoMoreInteractions(mSensorManager);
}
+ @Test
+ public void testAwaitLuxWhenNoLightSensor() {
+ when(mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT)).thenReturn(null);
+ mProbe = new ALSProbe(mSensorManager, new Handler(mLooper.getLooper()), TIMEOUT_MS - 1);
+
+ AtomicInteger lux = new AtomicInteger(-5);
+ mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
+
+ // Verify that no light sensor will be registered.
+ verify(mSensorManager, times(0)).registerListener(
+ mSensorEventListenerCaptor.capture(), any(), anyInt());
+
+ assertThat(lux.get()).isEqualTo(-1);
+ }
+
private void moveTimeBy(long millis) {
mLooper.moveTimeForward(millis);
mLooper.processAllMessages();
diff --git a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
index e0ef035..a6f2196 100644
--- a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
@@ -30,6 +30,7 @@
import android.app.role.RoleManager;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
import android.os.Binder;
import android.os.BugreportManager.BugreportCallback;
import android.os.BugreportParams;
@@ -37,6 +38,7 @@
import android.os.IDumpstateListener;
import android.os.Process;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
@@ -67,6 +69,9 @@
@RunWith(AndroidJUnit4.class)
public class BugreportManagerServiceImplTest {
+ private static final UserInfo ADMIN_USER_INFO =
+ new UserInfo(/* id= */ 5678, "adminUser", UserInfo.FLAG_ADMIN);
+
@Rule
public final CheckFlagsRule mCheckFlagsRule =
DeviceFlagsValueProvider.createCheckFlagsRule();
@@ -82,6 +87,8 @@
@Mock
private DevicePolicyManager mMockDevicePolicyManager;
+ private TestInjector mInjector;
+
private int mCallingUid = 1234;
private String mCallingPackage = "test.package";
private AtomicFile mMappingFile;
@@ -96,9 +103,9 @@
mMappingFile = new AtomicFile(mContext.getFilesDir(), "bugreport-mapping.xml");
ArraySet<String> mAllowlistedPackages = new ArraySet<>();
mAllowlistedPackages.add(mContext.getPackageName());
- mService = new BugreportManagerServiceImpl(
- new TestInjector(mContext, mAllowlistedPackages, mMappingFile,
- mMockUserManager, mMockDevicePolicyManager));
+ mInjector = new TestInjector(mContext, mAllowlistedPackages, mMappingFile,
+ mMockUserManager, mMockDevicePolicyManager);
+ mService = new BugreportManagerServiceImpl(mInjector);
mBugreportFileManager = new BugreportManagerServiceImpl.BugreportFileManager(mMappingFile);
when(mPackageManager.getPackageUidAsUser(anyString(), anyInt())).thenReturn(mCallingUid);
// The calling user is an admin user by default.
@@ -190,6 +197,33 @@
}
@Test
+ public void testStartBugreport() throws Exception {
+ mService.startBugreport(mCallingUid, mContext.getPackageName(),
+ new FileDescriptor(), /* screenshotFd= */ null,
+ BugreportParams.BUGREPORT_MODE_FULL,
+ /* flags= */ 0, new Listener(new CountDownLatch(1)),
+ /* isScreenshotRequested= */ false);
+
+ assertThat(mInjector.isBugreportStarted()).isTrue();
+ }
+
+ @Test
+ public void testStartBugreport_nonAdminProfileOfAdminCurrentUser() throws Exception {
+ int callingUid = Binder.getCallingUid();
+ int callingUserId = UserHandle.getUserId(callingUid);
+ when(mMockUserManager.isUserAdmin(callingUserId)).thenReturn(false);
+ when(mMockUserManager.getProfileParent(callingUserId)).thenReturn(ADMIN_USER_INFO);
+
+ mService.startBugreport(mCallingUid, mContext.getPackageName(),
+ new FileDescriptor(), /* screenshotFd= */ null,
+ BugreportParams.BUGREPORT_MODE_FULL,
+ /* flags= */ 0, new Listener(new CountDownLatch(1)),
+ /* isScreenshotRequested= */ false);
+
+ assertThat(mInjector.isBugreportStarted()).isTrue();
+ }
+
+ @Test
public void testStartBugreport_throwsForNonAdminUser() throws Exception {
when(mMockUserManager.isUserAdmin(anyInt())).thenReturn(false);
@@ -317,8 +351,12 @@
private static class TestInjector extends BugreportManagerServiceImpl.Injector {
+ private static final String SYSTEM_PROPERTY_BUGREPORT_START = "ctl.start";
+ private static final String SYSTEM_PROPERTY_BUGREPORT_STOP = "ctl.stop";
+
private final UserManager mUserManager;
private final DevicePolicyManager mDevicePolicyManager;
+ private boolean mBugreportStarted = false;
TestInjector(Context context, ArraySet<String> allowlistedPackages, AtomicFile mappingFile,
UserManager um, DevicePolicyManager dpm) {
@@ -336,5 +374,20 @@
public DevicePolicyManager getDevicePolicyManager() {
return mDevicePolicyManager;
}
+
+ @Override
+ public void setSystemProperty(String key, String value) {
+ // Calling SystemProperties.set() will throw a RuntimeException due to permission error.
+ // Instead, we are just marking a flag to store the state for testing.
+ if (SYSTEM_PROPERTY_BUGREPORT_START.equals(key)) {
+ mBugreportStarted = true;
+ } else if (SYSTEM_PROPERTY_BUGREPORT_STOP.equals(key)) {
+ mBugreportStarted = false;
+ }
+ }
+
+ public boolean isBugreportStarted() {
+ return mBugreportStarted;
+ }
}
}
diff --git a/services/tests/servicestests/utils/com/android/server/testutils/StubTransaction.java b/services/tests/servicestests/utils/com/android/server/testutils/StubTransaction.java
index b9ece93..e27bb4c 100644
--- a/services/tests/servicestests/utils/com/android/server/testutils/StubTransaction.java
+++ b/services/tests/servicestests/utils/com/android/server/testutils/StubTransaction.java
@@ -40,19 +40,12 @@
public class StubTransaction extends SurfaceControl.Transaction {
private HashSet<Runnable> mWindowInfosReportedListeners = new HashSet<>();
- private HashSet<SurfaceControl.TransactionCommittedListener> mTransactionCommittedListeners =
- new HashSet<>();
@Override
public void apply() {
for (Runnable listener : mWindowInfosReportedListeners) {
listener.run();
}
- for (SurfaceControl.TransactionCommittedListener listener
- : mTransactionCommittedListeners) {
- listener.onTransactionCommitted();
- }
- mTransactionCommittedListeners.clear();
}
@Override
@@ -246,9 +239,6 @@
@Override
public SurfaceControl.Transaction addTransactionCommittedListener(Executor executor,
SurfaceControl.TransactionCommittedListener listener) {
- SurfaceControl.TransactionCommittedListener listenerInner =
- () -> executor.execute(listener::onTransactionCommitted);
- mTransactionCommittedListeners.add(listenerInner);
return this;
}
diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp
index 2f29d10..515898a 100644
--- a/services/tests/uiservicestests/Android.bp
+++ b/services/tests/uiservicestests/Android.bp
@@ -48,6 +48,8 @@
"notification_flags_lib",
"platform-test-rules",
"SettingsLib",
+ "libprotobuf-java-lite",
+ "platformprotoslite",
],
libs: [
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index 4dded1d..05b6c90 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -37,6 +37,7 @@
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -885,6 +886,7 @@
return true;
});
+ mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
service.addApprovedList("a", 0, true);
service.reregisterService(cn, 0);
@@ -915,6 +917,7 @@
return true;
});
+ mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
service.addApprovedList("a", 0, false);
service.reregisterService(cn, 0);
@@ -945,6 +948,7 @@
return true;
});
+ mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
service.addApprovedList("a/a", 0, true);
service.reregisterService(cn, 0);
@@ -975,6 +979,7 @@
return true;
});
+ mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
service.addApprovedList("a/a", 0, false);
service.reregisterService(cn, 0);
@@ -1152,6 +1157,58 @@
}
@Test
+ public void testUpgradeAppNoPermissionNoRebind() throws Exception {
+ Context context = spy(getContext());
+ doReturn(true).when(context).bindServiceAsUser(any(), any(), anyInt(), any());
+
+ ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles,
+ mIpm,
+ APPROVAL_BY_COMPONENT);
+
+ List<String> packages = new ArrayList<>();
+ packages.add("package");
+ addExpectedServices(service, packages, 0);
+
+ final ComponentName unapprovedComponent = ComponentName.unflattenFromString("package/C1");
+ final ComponentName approvedComponent = ComponentName.unflattenFromString("package/C2");
+
+ // Both components are approved initially
+ mExpectedPrimaryComponentNames.clear();
+ mExpectedPrimaryPackages.clear();
+ mExpectedPrimaryComponentNames.put(0, "package/C1:package/C2");
+ mExpectedSecondaryComponentNames.clear();
+ mExpectedSecondaryPackages.clear();
+
+ loadXml(service);
+
+ //Component package/C1 loses bind permission
+ when(mIpm.getServiceInfo(any(), anyLong(), anyInt())).thenAnswer(
+ (Answer<ServiceInfo>) invocation -> {
+ ComponentName invocationCn = invocation.getArgument(0);
+ if (invocationCn != null) {
+ ServiceInfo serviceInfo = new ServiceInfo();
+ serviceInfo.packageName = invocationCn.getPackageName();
+ serviceInfo.name = invocationCn.getClassName();
+ if (invocationCn.equals(unapprovedComponent)) {
+ serviceInfo.permission = "none";
+ } else {
+ serviceInfo.permission = service.getConfig().bindPermission;
+ }
+ serviceInfo.metaData = null;
+ return serviceInfo;
+ }
+ return null;
+ }
+ );
+
+ // Trigger package update
+ service.onPackagesChanged(false, new String[]{"package"}, new int[]{0});
+
+ assertFalse(service.isComponentEnabledForCurrentProfiles(unapprovedComponent));
+ assertTrue(service.isComponentEnabledForCurrentProfiles(approvedComponent));
+ }
+
+ @Test
public void testSetPackageOrComponentEnabled() throws Exception {
for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
index 33ca5c2..a45b102 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
@@ -20,6 +20,7 @@
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
@@ -32,6 +33,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.ActivityManager;
import android.app.INotificationManager;
import android.content.ComponentName;
import android.content.Context;
@@ -47,9 +49,12 @@
import android.util.ArraySet;
import android.util.IntArray;
import android.util.Xml;
+import android.Manifest;
+import com.android.internal.util.CollectionUtils;
import com.android.internal.util.function.TriPredicate;
import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.UiServiceTestCase;
import com.android.server.notification.NotificationManagerService.NotificationAssistants;
@@ -59,7 +64,9 @@
import org.mockito.MockitoAnnotations;
import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -89,11 +96,15 @@
UserInfo mZero = new UserInfo(0, "zero", 0);
UserInfo mTen = new UserInfo(10, "ten", 0);
+ ComponentName mCn = new ComponentName("a", "b");
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mContext.setMockPackageManager(mPm);
mContext.addMockSystemService(Context.USER_SERVICE, mUm);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.string.config_defaultAssistantAccessComponent, "a/a");
mAssistants = spy(mNm.new NotificationAssistants(mContext, mLock, mUserProfiles, miPm));
when(mNm.getBinderService()).thenReturn(mINm);
mContext.ensureTestableResources();
@@ -102,8 +113,9 @@
ResolveInfo resolve = new ResolveInfo();
approved.add(resolve);
ServiceInfo info = new ServiceInfo();
- info.packageName = "a";
- info.name="a";
+ info.packageName = mCn.getPackageName();
+ info.name = mCn.getClassName();
+ info.permission = Manifest.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE;
resolve.serviceInfo = info;
when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt()))
.thenReturn(approved);
@@ -137,6 +149,51 @@
}
@Test
+ public void testWriteXml_userTurnedOffNAS() throws Exception {
+ int userId = ActivityManager.getCurrentUser();
+
+ mAssistants.loadDefaultsFromConfig(true);
+
+ mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true,
+ true, true);
+
+ ComponentName current = CollectionUtils.firstOrNull(
+ mAssistants.getAllowedComponents(userId));
+ assertNotNull(current);
+ mAssistants.setUserSet(userId, true);
+ mAssistants.setPackageOrComponentEnabled(current.flattenToString(), userId, true, false,
+ true);
+
+ TypedXmlSerializer serializer = Xml.newFastSerializer();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+ serializer.startDocument(null, true);
+ mAssistants.writeXml(serializer, true, userId);
+ serializer.endDocument();
+ serializer.flush();
+
+ //fail(baos.toString("UTF-8"));
+
+ final TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new BufferedInputStream(
+ new ByteArrayInputStream(baos.toByteArray())), null);
+ TriPredicate<String, Integer, String> allowedManagedServicePackages =
+ mNm::canUseManagedServices;
+
+ parser.nextTag();
+ mAssistants = spy(mNm.new NotificationAssistants(mContext, mLock, mUserProfiles, miPm));
+ mAssistants.readXml(parser, allowedManagedServicePackages, false, UserHandle.USER_ALL);
+
+ ArrayMap<Boolean, ArraySet<String>> approved = mAssistants.mApproved.get(0);
+ // approved should not be null
+ assertNotNull(approved);
+ assertEquals(new ArraySet<>(), approved.get(true));
+
+ // user set is maintained
+ assertTrue(mAssistants.mIsUserChanged.get(ActivityManager.getCurrentUser()));
+ }
+
+ @Test
public void testReadXml_userDisabled() throws Exception {
String xml = "<enabled_assistants version=\"4\" defaults=\"b/b\">"
+ "<service_listing approved=\"\" user=\"0\" primary=\"true\""
@@ -160,6 +217,33 @@
}
@Test
+ public void testReadXml_userDisabled_restore() throws Exception {
+ String xml = "<enabled_assistants version=\"4\" defaults=\"b/b\">"
+ + "<service_listing approved=\"\" user=\"0\" primary=\"true\""
+ + "user_changed=\"true\"/>"
+ + "</enabled_assistants>";
+
+ final TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new BufferedInputStream(
+ new ByteArrayInputStream(xml.toString().getBytes())), null);
+ TriPredicate<String, Integer, String> allowedManagedServicePackages =
+ mNm::canUseManagedServices;
+
+ parser.nextTag();
+ mAssistants.readXml(parser, allowedManagedServicePackages, true,
+ ActivityManager.getCurrentUser());
+
+ ArrayMap<Boolean, ArraySet<String>> approved = mAssistants.mApproved.get(0);
+
+ // approved should not be null
+ assertNotNull(approved);
+ assertEquals(new ArraySet<>(), approved.get(true));
+
+ // user set is maintained
+ assertTrue(mAssistants.mIsUserChanged.get(ActivityManager.getCurrentUser()));
+ }
+
+ @Test
public void testReadXml_upgradeUserSet() throws Exception {
String xml = "<enabled_assistants version=\"3\" defaults=\"b/b\">"
+ "<service_listing approved=\"\" user=\"0\" primary=\"true\""
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java
index 3499a12..bf8cfa5c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java
@@ -20,8 +20,12 @@
import static junit.framework.Assert.assertTrue;
import static junit.framework.TestCase.assertFalse;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -70,10 +74,11 @@
@Before
public void setUp() throws Exception {
- mJobService = new NotificationHistoryJobService();
+ mJobService = spy(new NotificationHistoryJobService());
mJobService.attachBaseContext(mContext);
mJobService.onCreate();
mJobService.onBind(/* intent= */ null); // Create JobServiceEngine within JobService.
+ doNothing().when(mJobService).jobFinished(any(), eq(false));
mContext.addMockSystemService(JobScheduler.class, mMockJobScheduler);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 78fb570..bfc47fd 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -324,7 +324,11 @@
when(mPermissionHelper.getNotificationPermissionValues(USER_SYSTEM))
.thenReturn(appPermissions);
- when(mUserProfiles.getCurrentProfileIds()).thenReturn(IntArray.wrap(new int[] {0}));
+ IntArray currentProfileIds = IntArray.wrap(new int[]{0});
+ if (UserManager.isHeadlessSystemUserMode()) {
+ currentProfileIds.add(UserHandle.getUserId(UID_HEADLESS));
+ }
+ when(mUserProfiles.getCurrentProfileIds()).thenReturn(currentProfileIds);
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
index 99d5a6d..75552bc 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
@@ -16,7 +16,7 @@
package com.android.server.notification;
-import static com.android.server.notification.ZenAdapters.notificationPolicyToZenPolicy;
+import static android.service.notification.ZenAdapters.notificationPolicyToZenPolicy;
import static com.google.common.truth.Truth.assertThat;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenEnumTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenEnumTest.java
new file mode 100644
index 0000000..f724510
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenEnumTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.AutomaticZenRule;
+import android.provider.Settings;
+import android.service.notification.ZenPolicy;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.os.dnd.ActiveRuleType;
+import com.android.os.dnd.ChannelPolicy;
+import com.android.os.dnd.ConversationType;
+import com.android.os.dnd.PeopleType;
+import com.android.os.dnd.State;
+import com.android.os.dnd.ZenMode;
+
+import com.google.protobuf.Internal;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/** Test to validate that logging enums used in Zen classes match their API definitions. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ZenEnumTest {
+
+ @Test
+ public void testEnum_zenMode() {
+ testEnum(Settings.Global.class, "ZEN_MODE", ZenMode.class, "ZEN_MODE");
+ }
+
+ @Test
+ public void testEnum_activeRuleType() {
+ testEnum(AutomaticZenRule.class, "TYPE", ActiveRuleType.class, "TYPE");
+ }
+
+ @Test
+ public void testEnum_zenPolicyState() {
+ testEnum(ZenPolicy.class, "STATE", State.class, "STATE");
+ }
+
+ @Test
+ public void testEnum_zenPolicyChannelPolicy() {
+ testEnum(ZenPolicy.class, "CHANNEL_POLICY", ChannelPolicy.class, "CHANNEL_POLICY");
+ }
+
+ @Test
+ public void testEnum_zenPolicyConversationType() {
+ testEnum(ZenPolicy.class, "CONVERSATION_SENDERS", ConversationType.class, "CONV");
+ }
+
+ @Test
+ public void testEnum_zenPolicyPeopleType() {
+ testEnum(ZenPolicy.class, "PEOPLE_TYPE", PeopleType.class, "PEOPLE");
+ }
+
+ /**
+ * Verifies that any constants (i.e. {@code public static final int} fields) named {@code
+ * <apiPrefix>_SOMETHING} in {@code apiClass} are present and have the same numerical value
+ * in the enum values defined in {@code loggingProtoEnumClass}.
+ *
+ * <p>Note that <em>extra</em> values in the logging enum are accepted (since we have one of
+ * those, and the main goal of this test is that we don't forget to update the logging enum
+ * if new API enum values are added).
+ */
+ private static void testEnum(Class<?> apiClass, String apiPrefix,
+ Class<? extends Internal.EnumLite> loggingProtoEnumClass,
+ String loggingPrefix) {
+ Map<String, Integer> apiConstants =
+ Arrays.stream(apiClass.getDeclaredFields())
+ .filter(f -> Modifier.isPublic(f.getModifiers()))
+ .filter(f -> Modifier.isStatic(f.getModifiers()))
+ .filter(f -> Modifier.isFinal(f.getModifiers()))
+ .filter(f -> f.getType().equals(int.class))
+ .filter(f -> f.getName().startsWith(apiPrefix + "_"))
+ .collect(Collectors.toMap(
+ Field::getName,
+ ZenEnumTest::getStaticFieldIntValue));
+
+ Map<String, Integer> loggingConstants =
+ Arrays.stream(loggingProtoEnumClass.getEnumConstants())
+ .collect(Collectors.toMap(
+ v -> v.toString(),
+ v -> v.getNumber()));
+
+ Map<String, Integer> renamedApiConstants = apiConstants.entrySet().stream()
+ .collect(Collectors.toMap(
+ entry -> entry.getKey().replace(apiPrefix + "_", loggingPrefix + "_"),
+ Map.Entry::getValue));
+
+ assertThat(loggingConstants).containsAtLeastEntriesIn(renamedApiConstants);
+ }
+
+ private static int getStaticFieldIntValue(Field f) {
+ try {
+ return f.getInt(null);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 495e01a..7c1adbc 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -136,6 +136,7 @@
import android.provider.Settings.Global;
import android.service.notification.Condition;
import android.service.notification.DeviceEffectsApplier;
+import android.service.notification.ZenAdapters;
import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenModeConfig.ConfigChangeOrigin;
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index 1a1fe95..0c1fbf3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -94,7 +94,6 @@
public void setUp() throws Exception {
assumeFalse(WindowManagerService.sEnableShellTransitions);
mAppTransitionController = new AppTransitionController(mWm, mDisplayContent);
- mWm.mAnimator.ready();
}
@Test
@@ -856,7 +855,7 @@
// Prepare and start transition.
prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
// Animation run by the remote handler.
assertTrue(remoteAnimationRunner.isAnimationStarted());
@@ -887,7 +886,7 @@
// Prepare and start transition.
prepareAndTriggerAppTransition(openingActivity, closingActivity,
null /* changingTaskFragment */);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
// Animation is not run by the remote handler because the activity is filling the Task.
assertFalse(remoteAnimationRunner.isAnimationStarted());
@@ -922,7 +921,7 @@
// Prepare and start transition.
prepareAndTriggerAppTransition(openingActivity, closingActivity,
null /* changingTaskFragment */);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
// Animation run by the remote handler.
assertTrue(remoteAnimationRunner.isAnimationStarted());
@@ -947,7 +946,7 @@
// Prepare and start transition.
prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
// Animation run by the remote handler.
assertTrue(remoteAnimationRunner.isAnimationStarted());
@@ -974,7 +973,7 @@
// Prepare and start transition.
prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment1);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
// Animation run by the remote handler.
assertTrue(remoteAnimationRunner.isAnimationStarted());
@@ -998,7 +997,7 @@
// Prepare and start transition.
prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
// Animation not run by the remote handler.
assertFalse(remoteAnimationRunner.isAnimationStarted());
@@ -1025,7 +1024,7 @@
// Prepare and start transition.
prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
// Animation should not run by the remote handler when there are non-embedded activities of
// different UID.
@@ -1052,7 +1051,7 @@
// Prepare and start transition.
prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
// Animation should not run by the remote handler when there is wallpaper in the transition.
assertFalse(remoteAnimationRunner.isAnimationStarted());
@@ -1086,7 +1085,7 @@
// Prepare and start transition.
prepareAndTriggerAppTransition(activity1, null /* closingActivity */, taskFragment);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
// The animation will be animated remotely by client and all activities are input disabled
// for untrusted animation.
@@ -1137,7 +1136,7 @@
// Prepare and start transition.
prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
// The animation will be animated remotely by client and all activities are input disabled
// for untrusted animation.
@@ -1179,7 +1178,7 @@
// Prepare and start transition.
prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
// The animation will be animated remotely by client, but input should not be dropped for
// fully trusted.
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 1f15ec3..2085d61 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -371,7 +371,6 @@
mDisplayContent.getInsetsPolicy().updateBarControlTarget(app);
mDisplayContent.getInsetsPolicy().showTransient(statusBars(),
true /* isGestureOnSystemBar */);
- mWm.mAnimator.ready();
waitUntilWindowAnimatorIdle();
assertTrue(mDisplayContent.getInsetsPolicy().isTransient(statusBars()));
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index a163801..11d9629 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -43,6 +43,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -112,7 +113,6 @@
runWithScissors(mWm.mH, () -> mHandler = new TestHandler(null, mClock), 0);
mController = new RemoteAnimationController(mWm, mDisplayContent, mAdapter,
mHandler, false /*isActivityEmbedding*/);
- mWm.mAnimator.ready();
}
private WindowState createAppOverlayWindow() {
@@ -136,7 +136,7 @@
adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
mFinishedCallback);
mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
@@ -168,7 +168,7 @@
adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
mFinishedCallback);
mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
@@ -290,7 +290,7 @@
adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
mFinishedCallback);
mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
@@ -336,7 +336,7 @@
task.applyAnimationUnchecked(null /* lp */, true /* enter */, TRANSIT_OLD_TASK_OPEN,
false /* isVoiceInteraction */, null /* sources */);
mController.goodToGo(TRANSIT_OLD_TASK_OPEN);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
try {
@@ -363,7 +363,7 @@
((AnimationAdapter) record.mThumbnailAdapter).startAnimation(mMockThumbnailLeash,
mMockTransaction, ANIMATION_TYPE_WINDOW_ANIMATION, mThumbnailFinishedCallback);
mController.goodToGo(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
@@ -417,7 +417,7 @@
((AnimationAdapter) record.mThumbnailAdapter).startAnimation(mMockThumbnailLeash,
mMockTransaction, ANIMATION_TYPE_WINDOW_ANIMATION, mThumbnailFinishedCallback);
mController.goodToGo(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
@@ -471,7 +471,7 @@
((AnimationAdapter) record.mThumbnailAdapter).startAnimation(mMockThumbnailLeash,
mMockTransaction, ANIMATION_TYPE_WINDOW_ANIMATION, mThumbnailFinishedCallback);
mController.goodToGo(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
@@ -526,7 +526,7 @@
adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
mFinishedCallback);
mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
@@ -559,7 +559,7 @@
adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
mFinishedCallback);
mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
@@ -595,7 +595,7 @@
adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
mFinishedCallback);
mController.goodToGo(TRANSIT_OLD_KEYGUARD_GOING_AWAY);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
@@ -645,7 +645,7 @@
adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
mFinishedCallback);
mController.goodToGo(TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
@@ -782,7 +782,7 @@
mDisplayContent.applySurfaceChangesTransaction();
mController.goodToGo(TRANSIT_OLD_TASK_OPEN);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_TASK_OPEN),
any(), any(), any(), any());
@@ -810,7 +810,7 @@
adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
mFinishedCallback);
mController.goodToGo(transit);
- waitUntilWindowAnimatorIdle();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
return adapter;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 7ab093d..a8f6fe8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -546,7 +546,7 @@
// This makes sure all previous messages in the handler are fully processed vs. just popping
// them from the message queue.
final AtomicBoolean currentMessagesProcessed = new AtomicBoolean(false);
- wm.mAnimator.addAfterPrepareSurfacesRunnable(() -> {
+ wm.mAnimator.getChoreographer().postFrameCallback(time -> {
synchronized (currentMessagesProcessed) {
currentMessagesProcessed.set(true);
currentMessagesProcessed.notifyAll();
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 01bd96b..5360a10 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -896,6 +896,11 @@
assertFalse(mWm.moveFocusToTopEmbeddedWindow(winRightTop));
// The focus should NOT change.
assertEquals(winRightTop, mDisplayContent.mCurrentFocus);
+
+ // Do not move focus if the dim is boosted.
+ taskFragmentLeft.mDimmerSurfaceBoosted = true;
+ assertFalse(mWm.moveFocusToTopEmbeddedWindow(winLeftTop));
+ assertEquals(winRightTop, mDisplayContent.mCurrentFocus);
}
}
diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java
index 2fc6a22..8b503f2 100644
--- a/telecomm/java/android/telecom/RemoteConnectionService.java
+++ b/telecomm/java/android/telecom/RemoteConnectionService.java
@@ -28,6 +28,8 @@
import com.android.internal.telecom.IConnectionServiceAdapter;
import com.android.internal.telecom.IVideoProvider;
import com.android.internal.telecom.RemoteServiceCallback;
+import com.android.server.telecom.flags.FeatureFlags;
+import com.android.server.telecom.flags.FeatureFlagsImpl;
import java.util.ArrayList;
import java.util.HashMap;
@@ -550,6 +552,9 @@
private final Map<String, RemoteConference> mConferenceById = new HashMap<>();
private final Set<RemoteConnection> mPendingConnections = new HashSet<>();
+ /** Telecom feature flags **/
+ private final FeatureFlags mTelecomFeatureFlags = new FeatureFlagsImpl();
+
RemoteConnectionService(
IConnectionService outgoingConnectionServiceRpc,
ConnectionService ourConnectionServiceImpl) throws RemoteException {
@@ -578,6 +583,14 @@
extras.putString(Connection.EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME,
mOurConnectionServiceImpl.getApplicationContext().getOpPackageName());
+ // Defaulted ConnectionRequest params
+ String telecomCallId = "";
+ boolean shouldShowIncomingUI = false;
+ if (mTelecomFeatureFlags.setRemoteConnectionCallId()) {
+ telecomCallId = id;
+ shouldShowIncomingUI = request.shouldShowIncomingCallUi();
+ }
+
final ConnectionRequest newRequest = new ConnectionRequest.Builder()
.setAccountHandle(request.getAccountHandle())
.setAddress(request.getAddress())
@@ -585,6 +598,9 @@
.setVideoState(request.getVideoState())
.setRttPipeFromInCall(request.getRttPipeFromInCall())
.setRttPipeToInCall(request.getRttPipeToInCall())
+ // Flagged changes
+ .setTelecomCallId(telecomCallId)
+ .setShouldShowIncomingCallUi(shouldShowIncomingUI)
.build();
try {
if (mConnectionById.isEmpty()) {
@@ -626,10 +642,28 @@
mOutgoingConnectionServiceRpc.addConnectionServiceAdapter(mServant.getStub(),
null /*Session.Info*/);
}
+
+ // Set telecom call id to what's being tracked by base ConnectionService.
+ String telecomCallId = mTelecomFeatureFlags.setRemoteConnectionCallId()
+ ? id : request.getTelecomCallId();
+
+ final ConnectionRequest newRequest = new ConnectionRequest.Builder()
+ .setAccountHandle(request.getAccountHandle())
+ .setAddress(request.getAddress())
+ .setExtras(request.getExtras())
+ .setVideoState(request.getVideoState())
+ .setShouldShowIncomingCallUi(request.shouldShowIncomingCallUi())
+ .setRttPipeFromInCall(request.getRttPipeFromInCall())
+ .setRttPipeToInCall(request.getRttPipeToInCall())
+ .setParticipants(request.getParticipants())
+ .setIsAdhocConferenceCall(request.isAdhocConferenceCall())
+ .setTelecomCallId(telecomCallId)
+ .build();
+
RemoteConference conference = new RemoteConference(id, mOutgoingConnectionServiceRpc);
mOutgoingConnectionServiceRpc.createConference(connectionManagerPhoneAccount,
id,
- request,
+ newRequest,
isIncoming,
false /* isUnknownCall */,
null /*Session.info*/);
@@ -640,7 +674,7 @@
maybeDisconnectAdapter();
}
});
- conference.putExtras(request.getExtras());
+ conference.putExtras(newRequest.getExtras());
return conference;
} catch (RemoteException e) {
return RemoteConference.failure(
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 5d99acd..2150b5d 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -5307,6 +5307,19 @@
KEY_PREFIX + "enable_presence_group_subscribe_bool";
/**
+ * SIP SUBSCRIBE retry duration used when device doesn't receive a response to SIP
+ * SUBSCRIBE request.
+ * If this value is not defined or defined as negative value, the device does not retry
+ * the SIP SUBSCRIBE.
+ * If the value is 0 then device retries immediately upon timeout.
+ * If the value is > 0 then device waits for configured duration and retries after timeout
+ * is detected
+ * @hide
+ */
+ public static final String KEY_SUBSCRIBE_RETRY_DURATION_MILLIS_LONG =
+ KEY_PREFIX + "subscribe_retry_duration_millis_long";
+
+ /**
* Flag indicating whether or not to use SIP URI when send a presence subscribe.
* When {@code true}, the device sets the To and Contact header to be SIP URI using
* the TelephonyManager#getIsimDomain" API.
@@ -5982,6 +5995,7 @@
defaults.putBoolean(KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL, false);
defaults.putBoolean(KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL, false);
defaults.putBoolean(KEY_ENABLE_PRESENCE_GROUP_SUBSCRIBE_BOOL, false);
+ defaults.putInt(KEY_SUBSCRIBE_RETRY_DURATION_MILLIS_LONG, -1);
defaults.putBoolean(KEY_USE_SIP_URI_FOR_PRESENCE_SUBSCRIBE_BOOL, false);
defaults.putInt(KEY_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC_INT, 30 * 24 * 60 * 60);
defaults.putBoolean(KEY_RCS_REQUEST_FORBIDDEN_BY_SIP_489_BOOL, false);
diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp
index 1d71f95..d658d59 100644
--- a/tests/FlickerTests/Android.bp
+++ b/tests/FlickerTests/Android.bp
@@ -63,17 +63,20 @@
],
}
-android_library_import {
- name: "wm-flicker-window-extensions_nodeps",
- aars: ["libs/window-extensions-release.aar"],
- sdk_version: "current",
-}
-
java_library {
name: "wm-flicker-window-extensions",
sdk_version: "current",
static_libs: [
- "wm-flicker-window-extensions_nodeps",
+ "androidx.window.extensions_extensions-nodeps",
+ ],
+ installable: false,
+}
+
+java_library {
+ name: "wm-flicker-window-extensions-core",
+ sdk_version: "current",
+ static_libs: [
+ "androidx.window.extensions.core_core-nodeps",
],
installable: false,
}
diff --git a/tests/FlickerTests/libs/window-extensions-release.aar b/tests/FlickerTests/libs/window-extensions-release.aar
deleted file mode 100644
index 918e514..0000000
--- a/tests/FlickerTests/libs/window-extensions-release.aar
+++ /dev/null
Binary files differ
diff --git a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
index a64996c..d9a4c26 100644
--- a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
@@ -59,6 +59,7 @@
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.LinkedList;
+import java.util.TreeMap;
/**
* Test class for {@link ProtoLogImpl}.
@@ -89,7 +90,7 @@
//noinspection ResultOfMethodCallIgnored
mFile.delete();
mProtoLog = new LegacyProtoLogImpl(mFile, mViewerConfigFilename,
- 1024 * 1024, mReader, 1024);
+ 1024 * 1024, mReader, 1024, new TreeMap<>());
}
@After
diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
index 270f595..548adef 100644
--- a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
@@ -64,6 +64,7 @@
import java.io.IOException;
import java.util.List;
import java.util.Random;
+import java.util.TreeMap;
import perfetto.protos.Protolog;
import perfetto.protos.ProtologCommon;
@@ -152,7 +153,8 @@
.thenAnswer(it -> new ProtoInputStream(mViewerConfigBuilder.build().toByteArray()));
mReader = Mockito.spy(new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider));
- mProtoLog = new PerfettoProtoLogImpl(viewerConfigInputStreamProvider, mReader);
+ mProtoLog =
+ new PerfettoProtoLogImpl(viewerConfigInputStreamProvider, mReader, new TreeMap<>());
}
@After
diff --git a/tests/graphics/HwAccelerationTest/jni/native-lib.cpp b/tests/graphics/HwAccelerationTest/jni/native-lib.cpp
index 407d4bf..2977c21 100644
--- a/tests/graphics/HwAccelerationTest/jni/native-lib.cpp
+++ b/tests/graphics/HwAccelerationTest/jni/native-lib.cpp
@@ -30,7 +30,7 @@
void setBuffer(AHardwareBuffer* buffer) {
ASurfaceTransaction* transaction = ASurfaceTransaction_create();
- ASurfaceTransaction_setBuffer(transaction, surfaceControl, buffer);
+ ASurfaceTransaction_setBuffer(transaction, surfaceControl, buffer, -1);
ASurfaceTransaction_setVisibility(transaction, surfaceControl,
ASURFACE_TRANSACTION_VISIBILITY_SHOW);
ASurfaceTransaction_apply(transaction);
diff --git a/tools/protologtool/src/com/android/protolog/tool/CommandOptions.kt b/tools/protologtool/src/com/android/protolog/tool/CommandOptions.kt
index a359155..2690bc5 100644
--- a/tools/protologtool/src/com/android/protolog/tool/CommandOptions.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/CommandOptions.kt
@@ -25,6 +25,7 @@
const val READ_LOG_CMD = "read-log"
private val commands = setOf(TRANSFORM_CALLS_CMD, GENERATE_CONFIG_CMD, READ_LOG_CMD)
+ // TODO: This is always the same. I don't think it's required
private const val PROTOLOG_CLASS_PARAM = "--protolog-class"
private const val PROTOLOGGROUP_CLASS_PARAM = "--loggroups-class"
private const val PROTOLOGGROUP_JAR_PARAM = "--loggroups-jar"
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
index 1381847..837dae9 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
@@ -24,11 +24,17 @@
import com.github.javaparser.ParserConfiguration
import com.github.javaparser.StaticJavaParser
import com.github.javaparser.ast.CompilationUnit
+import com.github.javaparser.ast.NodeList
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
+import com.github.javaparser.ast.body.InitializerDeclaration
+import com.github.javaparser.ast.expr.FieldAccessExpr
import com.github.javaparser.ast.expr.MethodCallExpr
+import com.github.javaparser.ast.expr.NameExpr
import com.github.javaparser.ast.expr.NullLiteralExpr
+import com.github.javaparser.ast.expr.ObjectCreationExpr
import com.github.javaparser.ast.expr.SimpleName
import com.github.javaparser.ast.expr.StringLiteralExpr
+import com.github.javaparser.ast.stmt.BlockStmt
import java.io.File
import java.io.FileInputStream
import java.io.FileNotFoundException
@@ -39,8 +45,7 @@
import java.util.concurrent.Executors
import java.util.jar.JarOutputStream
import java.util.zip.ZipEntry
-import kotlin.math.abs
-import kotlin.random.Random
+import kotlin.math.absoluteValue
import kotlin.system.exitProcess
object ProtoLogTool {
@@ -72,7 +77,11 @@
}
private fun processClasses(command: CommandOptions) {
- val generationHash = abs(Random.nextInt())
+ // A deterministic hash based on the group jar path and the source files we are processing.
+ // The hash is required to make sure different ProtoLogImpls don't conflict.
+ val generationHash = (command.javaSourceArgs.toTypedArray() + command.protoLogGroupsJarArg)
+ .contentHashCode().absoluteValue
+
// Need to generate a new impl class to inject static constants into the class.
val generatedProtoLogImplClass =
"com.android.internal.protolog.ProtoLogImpl_$generationHash"
@@ -93,7 +102,8 @@
outJar.putNextEntry(zipEntry(protologImplPath))
outJar.write(generateProtoLogImpl(protologImplName, command.viewerConfigFilePathArg,
- command.legacyViewerConfigFilePathArg, command.legacyOutputFilePath).toByteArray())
+ command.legacyViewerConfigFilePathArg, command.legacyOutputFilePath,
+ groups, command.protoLogGroupsClassNameArg).toByteArray())
val executor = newThreadPool()
@@ -137,6 +147,8 @@
viewerConfigFilePath: String,
legacyViewerConfigFilePath: String?,
legacyOutputFilePath: String?,
+ groups: Map<String, LogGroup>,
+ protoLogGroupsClassName: String,
): String {
val file = File(PROTOLOG_IMPL_SRC_PATH)
@@ -157,7 +169,8 @@
classNameNode.setId(protoLogImplGenName)
injectConstants(classDeclaration,
- viewerConfigFilePath, legacyViewerConfigFilePath, legacyOutputFilePath)
+ viewerConfigFilePath, legacyViewerConfigFilePath, legacyOutputFilePath, groups,
+ protoLogGroupsClassName)
return code.toString()
}
@@ -166,7 +179,9 @@
classDeclaration: ClassOrInterfaceDeclaration,
viewerConfigFilePath: String,
legacyViewerConfigFilePath: String?,
- legacyOutputFilePath: String?
+ legacyOutputFilePath: String?,
+ groups: Map<String, LogGroup>,
+ protoLogGroupsClassName: String
) {
classDeclaration.fields.forEach { field ->
field.getAnnotationByClass(ProtoLogToolInjected::class.java)
@@ -194,6 +209,35 @@
StringLiteralExpr(it)
} ?: NullLiteralExpr())
}
+ ProtoLogToolInjected.Value.LOG_GROUPS.name -> {
+ val initializerBlockStmt = BlockStmt()
+ for (group in groups) {
+ initializerBlockStmt.addStatement(
+ MethodCallExpr()
+ .setName("put")
+ .setArguments(
+ NodeList(StringLiteralExpr(group.key),
+ FieldAccessExpr()
+ .setScope(
+ NameExpr(
+ protoLogGroupsClassName
+ ))
+ .setName(group.value.name)))
+ )
+ group.key
+ }
+
+ val treeMapCreation = ObjectCreationExpr()
+ .setType("TreeMap<String, IProtoLogGroup>")
+ .setAnonymousClassBody(NodeList(
+ InitializerDeclaration().setBody(
+ initializerBlockStmt
+ )
+ ))
+
+ field.setFinal(true)
+ field.variables.first().setInitializer(treeMapCreation)
+ }
else -> error("Unhandled ProtoLogToolInjected value: $valueName.")
}
}
diff --git a/tools/streaming_proto/Android.bp b/tools/streaming_proto/Android.bp
index b18bdff..b1b314fc 100644
--- a/tools/streaming_proto/Android.bp
+++ b/tools/streaming_proto/Android.bp
@@ -17,6 +17,7 @@
// ==========================================================
// Build the host executable: protoc-gen-javastream
// ==========================================================
+
package {
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
@@ -41,6 +42,32 @@
static_libs: ["libprotoc"],
}
+// ==========================================================
+// Build the host static library: java_streaming_proto_lib
+// ==========================================================
+
+cc_library_host_static {
+ name: "java_streaming_proto_lib",
+ defaults: ["protoc-gen-stream-defaults"],
+ target: {
+ darwin: {
+ cflags: ["-D_DARWIN_UNLIMITED_STREAMS"],
+ },
+ },
+ cflags: [
+ "-Wno-format-y2k",
+ "-DSTATIC_ANDROIDFW_FOR_TOOLS",
+ ],
+
+ srcs: [
+ "java/java_proto_stream_code_generator.cpp",
+ ],
+}
+
+// ==========================================================
+// Build the host executable: protoc-gen-javastream
+// ==========================================================
+
cc_binary_host {
name: "protoc-gen-javastream",
srcs: [
@@ -48,8 +75,13 @@
],
defaults: ["protoc-gen-stream-defaults"],
+ static_libs: ["java_streaming_proto_lib"],
}
+// ==========================================================
+// Build the host executable: protoc-gen-cppstream
+// ==========================================================
+
cc_binary_host {
name: "protoc-gen-cppstream",
srcs: [
@@ -60,13 +92,31 @@
}
// ==========================================================
+// Build the host tests: StreamingProtoTest
+// ==========================================================
+
+cc_test_host {
+ name: "StreamingProtoTest",
+ defaults: ["protoc-gen-stream-defaults"],
+ srcs: [
+ "test/unit/**/*.cpp",
+ ],
+ static_libs: [
+ "java_streaming_proto_lib",
+ "libgmock",
+ "libgtest",
+ ],
+}
+
+// ==========================================================
// Build the java test
// ==========================================================
+
java_library {
- name: "StreamingProtoTest",
+ name: "StreamingProtoJavaIntegrationTest",
srcs: [
- "test/**/*.java",
- "test/**/*.proto",
+ "test/integration/**/*.java",
+ "test/integration/**/*.proto",
],
proto: {
type: "stream",
diff --git a/tools/streaming_proto/java/java_proto_stream_code_generator.cpp b/tools/streaming_proto/java/java_proto_stream_code_generator.cpp
new file mode 100644
index 0000000..9d61111
--- /dev/null
+++ b/tools/streaming_proto/java/java_proto_stream_code_generator.cpp
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "java_proto_stream_code_generator.h"
+
+#include <stdio.h>
+
+#include <iomanip>
+#include <iostream>
+#include <map>
+#include <sstream>
+#include <string>
+
+#include "Errors.h"
+
+using namespace android::stream_proto;
+using namespace google::protobuf::io;
+using namespace std;
+
+/**
+ * If the descriptor gives us a class name, use that. Otherwise make one up from
+ * the filename of the .proto file.
+ */
+static string make_outer_class_name(const FileDescriptorProto& file_descriptor) {
+ string name = file_descriptor.options().java_outer_classname();
+ if (name.size() == 0) {
+ name = to_camel_case(file_base_name(file_descriptor.name()));
+ if (name.size() == 0) {
+ ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE,
+ "Unable to make an outer class name for file: %s",
+ file_descriptor.name().c_str());
+ name = "Unknown";
+ }
+ }
+ return name;
+}
+
+/**
+ * Figure out the package name that we are generating.
+ */
+static string make_java_package(const FileDescriptorProto& file_descriptor) {
+ if (file_descriptor.options().has_java_package()) {
+ return file_descriptor.options().java_package();
+ } else {
+ return file_descriptor.package();
+ }
+}
+
+/**
+ * Figure out the name of the file we are generating.
+ */
+static string make_file_name(const FileDescriptorProto& file_descriptor, const string& class_name) {
+ string const package = make_java_package(file_descriptor);
+ string result;
+ if (package.size() > 0) {
+ result = replace_string(package, '.', '/');
+ result += '/';
+ }
+
+ result += class_name;
+ result += ".java";
+
+ return result;
+}
+
+static string indent_more(const string& indent) {
+ return indent + INDENT;
+}
+
+/**
+ * Write the constants for an enum.
+ */
+static void write_enum(stringstream& text, const EnumDescriptorProto& enu, const string& indent) {
+ const int N = enu.value_size();
+ text << indent << "// enum " << enu.name() << endl;
+ for (int i = 0; i < N; i++) {
+ const EnumValueDescriptorProto& value = enu.value(i);
+ text << indent << "public static final int " << make_constant_name(value.name()) << " = "
+ << value.number() << ";" << endl;
+ }
+ text << endl;
+}
+
+/**
+ * Write a field.
+ */
+static void write_field(stringstream& text, const FieldDescriptorProto& field,
+ const string& indent) {
+ string optional_comment =
+ field.label() == FieldDescriptorProto::LABEL_OPTIONAL ? "optional " : "";
+ string repeated_comment =
+ field.label() == FieldDescriptorProto::LABEL_REPEATED ? "repeated " : "";
+ string proto_type = get_proto_type(field);
+ string packed_comment = field.options().packed() ? " [packed=true]" : "";
+ text << indent << "// " << optional_comment << repeated_comment << proto_type << ' '
+ << field.name() << " = " << field.number() << packed_comment << ';' << endl;
+
+ text << indent << "public static final long " << make_constant_name(field.name()) << " = 0x";
+
+ ios::fmtflags fmt(text.flags());
+ text << setfill('0') << setw(16) << hex << get_field_id(field);
+ text.flags(fmt);
+
+ text << "L;" << endl;
+
+ text << endl;
+}
+
+/**
+ * Write a Message constants class.
+ */
+static void write_message(stringstream& text, const DescriptorProto& message,
+ const string& indent) {
+ int N;
+ const string indented = indent_more(indent);
+
+ text << indent << "// message " << message.name() << endl;
+ text << indent << "public final class " << message.name() << " {" << endl;
+ text << endl;
+
+ // Enums
+ N = message.enum_type_size();
+ for (int i = 0; i < N; i++) {
+ write_enum(text, message.enum_type(i), indented);
+ }
+
+ // Nested classes
+ N = message.nested_type_size();
+ for (int i = 0; i < N; i++) {
+ write_message(text, message.nested_type(i), indented);
+ }
+
+ // Fields
+ N = message.field_size();
+ for (int i = 0; i < N; i++) {
+ write_field(text, message.field(i), indented);
+ }
+
+ text << indent << "}" << endl;
+ text << endl;
+}
+
+/**
+ * Write the contents of a file.
+ *
+ * If there are enums and generate_outer is false, invalid java code will be generated.
+ */
+static void write_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor,
+ const string& filename, bool generate_outer,
+ const vector<EnumDescriptorProto>& enums,
+ const vector<DescriptorProto>& messages) {
+ stringstream text;
+
+ string const package_name = make_java_package(file_descriptor);
+ string const outer_class_name = make_outer_class_name(file_descriptor);
+
+ text << "// Generated by protoc-gen-javastream. DO NOT MODIFY." << endl;
+ text << "// source: " << file_descriptor.name() << endl << endl;
+
+ if (package_name.size() > 0) {
+ if (package_name.size() > 0) {
+ text << "package " << package_name << ";" << endl;
+ text << endl;
+ }
+ }
+
+ // This bit of policy is android api rules specific: Raw proto classes
+ // must never be in the API
+ text << "/** @hide */" << endl;
+ // text << "@android.annotation.TestApi" << endl;
+
+ if (generate_outer) {
+ text << "public final class " << outer_class_name << " {" << endl;
+ text << endl;
+ }
+
+ size_t N;
+ const string indented = generate_outer ? indent_more("") : string();
+
+ N = enums.size();
+ for (size_t i = 0; i < N; i++) {
+ write_enum(text, enums[i], indented);
+ }
+
+ N = messages.size();
+ for (size_t i = 0; i < N; i++) {
+ write_message(text, messages[i], indented);
+ }
+
+ if (generate_outer) {
+ text << "}" << endl;
+ }
+
+ CodeGeneratorResponse::File* file_response = response->add_file();
+ file_response->set_name(filename);
+ file_response->set_content(text.str());
+}
+
+/**
+ * Write one file per class. Put all of the enums into the "outer" class.
+ */
+static void write_multiple_files(CodeGeneratorResponse* response,
+ const FileDescriptorProto& file_descriptor,
+ set<string> messages_to_compile) {
+ // If there is anything to put in the outer class file, create one
+ if (file_descriptor.enum_type_size() > 0) {
+ vector<EnumDescriptorProto> enums;
+ int N = file_descriptor.enum_type_size();
+ for (int i = 0; i < N; i++) {
+ auto enum_full_name =
+ file_descriptor.package() + "." + file_descriptor.enum_type(i).name();
+ if (!messages_to_compile.empty() && !messages_to_compile.count(enum_full_name)) {
+ continue;
+ }
+ enums.push_back(file_descriptor.enum_type(i));
+ }
+
+ vector<DescriptorProto> messages;
+
+ if (messages_to_compile.empty() || !enums.empty()) {
+ write_file(response, file_descriptor,
+ make_file_name(file_descriptor, make_outer_class_name(file_descriptor)),
+ true, enums, messages);
+ }
+ }
+
+ // For each of the message types, make a file
+ int N = file_descriptor.message_type_size();
+ for (int i = 0; i < N; i++) {
+ vector<EnumDescriptorProto> enums;
+
+ vector<DescriptorProto> messages;
+
+ auto message_full_name =
+ file_descriptor.package() + "." + file_descriptor.message_type(i).name();
+ if (!messages_to_compile.empty() && !messages_to_compile.count(message_full_name)) {
+ continue;
+ }
+ messages.push_back(file_descriptor.message_type(i));
+
+ if (messages_to_compile.empty() || !messages.empty()) {
+ write_file(response, file_descriptor,
+ make_file_name(file_descriptor, file_descriptor.message_type(i).name()),
+ false, enums, messages);
+ }
+ }
+}
+
+static void write_single_file(CodeGeneratorResponse* response,
+ const FileDescriptorProto& file_descriptor,
+ set<string> messages_to_compile) {
+ int N;
+
+ vector<EnumDescriptorProto> enums;
+ N = file_descriptor.enum_type_size();
+ for (int i = 0; i < N; i++) {
+ auto enum_full_name = file_descriptor.package() + "." + file_descriptor.enum_type(i).name();
+ if (!messages_to_compile.empty() && !messages_to_compile.count(enum_full_name)) {
+ continue;
+ }
+
+ enums.push_back(file_descriptor.enum_type(i));
+ }
+
+ vector<DescriptorProto> messages;
+ N = file_descriptor.message_type_size();
+ for (int i = 0; i < N; i++) {
+ auto message_full_name =
+ file_descriptor.package() + "." + file_descriptor.message_type(i).name();
+
+ if (!messages_to_compile.empty() && !messages_to_compile.count(message_full_name)) {
+ continue;
+ }
+
+ messages.push_back(file_descriptor.message_type(i));
+ }
+
+ if (messages_to_compile.empty() || !enums.empty() || !messages.empty()) {
+ write_file(response, file_descriptor,
+ make_file_name(file_descriptor, make_outer_class_name(file_descriptor)), true,
+ enums, messages);
+ }
+}
+
+static void parse_args_string(stringstream args_string_stream,
+ set<string>* messages_to_compile_out) {
+ string line;
+ while (getline(args_string_stream, line, ';')) {
+ stringstream line_ss(line);
+ string arg_name;
+ getline(line_ss, arg_name, ':');
+ if (arg_name == "include_filter") {
+ string full_message_name;
+ while (getline(line_ss, full_message_name, ',')) {
+ messages_to_compile_out->insert(full_message_name);
+ }
+ } else {
+ ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE, "Unexpected argument '%s'.", arg_name.c_str());
+ }
+ }
+}
+
+CodeGeneratorResponse generate_java_protostream_code(CodeGeneratorRequest request) {
+ CodeGeneratorResponse response;
+
+ set<string> messages_to_compile;
+ auto request_params = request.parameter();
+ if (!request_params.empty()) {
+ parse_args_string(stringstream(request_params), &messages_to_compile);
+ }
+
+ // Build the files we need.
+ const int N = request.proto_file_size();
+ for (int i = 0; i < N; i++) {
+ const FileDescriptorProto& file_descriptor = request.proto_file(i);
+ if (should_generate_for_file(request, file_descriptor.name())) {
+ if (file_descriptor.options().java_multiple_files()) {
+ write_multiple_files(&response, file_descriptor, messages_to_compile);
+ } else {
+ write_single_file(&response, file_descriptor, messages_to_compile);
+ }
+ }
+ }
+
+ return response;
+}
diff --git a/tools/streaming_proto/java/java_proto_stream_code_generator.h b/tools/streaming_proto/java/java_proto_stream_code_generator.h
new file mode 100644
index 0000000..d2492f7
--- /dev/null
+++ b/tools/streaming_proto/java/java_proto_stream_code_generator.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AOSP_MAIN_FRAMEWORKS_BASE_JAVAPROTOSTREAMCODEGENERATOR_H
+#define AOSP_MAIN_FRAMEWORKS_BASE_JAVAPROTOSTREAMCODEGENERATOR_H
+
+#include "stream_proto_utils.h"
+#include "string_utils.h"
+
+using namespace android::stream_proto;
+using namespace google::protobuf::io;
+using namespace std;
+
+CodeGeneratorResponse generate_java_protostream_code(CodeGeneratorRequest request);
+
+#endif // AOSP_MAIN_FRAMEWORKS_BASE_JAVAPROTOSTREAMCODEGENERATOR_H
\ No newline at end of file
diff --git a/tools/streaming_proto/java/main.cpp b/tools/streaming_proto/java/main.cpp
index c9c50a5..5b35504 100644
--- a/tools/streaming_proto/java/main.cpp
+++ b/tools/streaming_proto/java/main.cpp
@@ -1,268 +1,21 @@
-#include "Errors.h"
-#include "stream_proto_utils.h"
-#include "string_utils.h"
-
#include <stdio.h>
+
#include <iomanip>
#include <iostream>
-#include <sstream>
#include <map>
+#include <sstream>
+#include <string>
+
+#include "Errors.h"
+#include "java_proto_stream_code_generator.h"
+#include "stream_proto_utils.h"
using namespace android::stream_proto;
using namespace google::protobuf::io;
using namespace std;
/**
- * If the descriptor gives us a class name, use that. Otherwise make one up from
- * the filename of the .proto file.
- */
-static string
-make_outer_class_name(const FileDescriptorProto& file_descriptor)
-{
- string name = file_descriptor.options().java_outer_classname();
- if (name.size() == 0) {
- name = to_camel_case(file_base_name(file_descriptor.name()));
- if (name.size() == 0) {
- ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE,
- "Unable to make an outer class name for file: %s",
- file_descriptor.name().c_str());
- name = "Unknown";
- }
- }
- return name;
-}
-
-/**
- * Figure out the package name that we are generating.
- */
-static string
-make_java_package(const FileDescriptorProto& file_descriptor) {
- if (file_descriptor.options().has_java_package()) {
- return file_descriptor.options().java_package();
- } else {
- return file_descriptor.package();
- }
-}
-
-/**
- * Figure out the name of the file we are generating.
- */
-static string
-make_file_name(const FileDescriptorProto& file_descriptor, const string& class_name)
-{
- string const package = make_java_package(file_descriptor);
- string result;
- if (package.size() > 0) {
- result = replace_string(package, '.', '/');
- result += '/';
- }
-
- result += class_name;
- result += ".java";
-
- return result;
-}
-
-static string
-indent_more(const string& indent)
-{
- return indent + INDENT;
-}
-
-/**
- * Write the constants for an enum.
- */
-static void
-write_enum(stringstream& text, const EnumDescriptorProto& enu, const string& indent)
-{
- const int N = enu.value_size();
- text << indent << "// enum " << enu.name() << endl;
- for (int i=0; i<N; i++) {
- const EnumValueDescriptorProto& value = enu.value(i);
- text << indent << "public static final int "
- << make_constant_name(value.name())
- << " = " << value.number() << ";" << endl;
- }
- text << endl;
-}
-
-/**
- * Write a field.
- */
-static void
-write_field(stringstream& text, const FieldDescriptorProto& field, const string& indent)
-{
- string optional_comment = field.label() == FieldDescriptorProto::LABEL_OPTIONAL
- ? "optional " : "";
- string repeated_comment = field.label() == FieldDescriptorProto::LABEL_REPEATED
- ? "repeated " : "";
- string proto_type = get_proto_type(field);
- string packed_comment = field.options().packed()
- ? " [packed=true]" : "";
- text << indent << "// " << optional_comment << repeated_comment << proto_type << ' '
- << field.name() << " = " << field.number() << packed_comment << ';' << endl;
-
- text << indent << "public static final long " << make_constant_name(field.name()) << " = 0x";
-
- ios::fmtflags fmt(text.flags());
- text << setfill('0') << setw(16) << hex << get_field_id(field);
- text.flags(fmt);
-
- text << "L;" << endl;
-
- text << endl;
-}
-
-/**
- * Write a Message constants class.
- */
-static void
-write_message(stringstream& text, const DescriptorProto& message, const string& indent)
-{
- int N;
- const string indented = indent_more(indent);
-
- text << indent << "// message " << message.name() << endl;
- text << indent << "public final class " << message.name() << " {" << endl;
- text << endl;
-
- // Enums
- N = message.enum_type_size();
- for (int i=0; i<N; i++) {
- write_enum(text, message.enum_type(i), indented);
- }
-
- // Nested classes
- N = message.nested_type_size();
- for (int i=0; i<N; i++) {
- write_message(text, message.nested_type(i), indented);
- }
-
- // Fields
- N = message.field_size();
- for (int i=0; i<N; i++) {
- write_field(text, message.field(i), indented);
- }
-
- text << indent << "}" << endl;
- text << endl;
-}
-
-/**
- * Write the contents of a file.
*
- * If there are enums and generate_outer is false, invalid java code will be generated.
- */
-static void
-write_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor,
- const string& filename, bool generate_outer,
- const vector<EnumDescriptorProto>& enums, const vector<DescriptorProto>& messages)
-{
- stringstream text;
-
- string const package_name = make_java_package(file_descriptor);
- string const outer_class_name = make_outer_class_name(file_descriptor);
-
- text << "// Generated by protoc-gen-javastream. DO NOT MODIFY." << endl;
- text << "// source: " << file_descriptor.name() << endl << endl;
-
- if (package_name.size() > 0) {
- if (package_name.size() > 0) {
- text << "package " << package_name << ";" << endl;
- text << endl;
- }
- }
-
- // This bit of policy is android api rules specific: Raw proto classes
- // must never be in the API
- text << "/** @hide */" << endl;
-// text << "@android.annotation.TestApi" << endl;
-
- if (generate_outer) {
- text << "public final class " << outer_class_name << " {" << endl;
- text << endl;
- }
-
- size_t N;
- const string indented = generate_outer ? indent_more("") : string();
-
- N = enums.size();
- for (size_t i=0; i<N; i++) {
- write_enum(text, enums[i], indented);
- }
-
- N = messages.size();
- for (size_t i=0; i<N; i++) {
- write_message(text, messages[i], indented);
- }
-
- if (generate_outer) {
- text << "}" << endl;
- }
-
- CodeGeneratorResponse::File* file_response = response->add_file();
- file_response->set_name(filename);
- file_response->set_content(text.str());
-}
-
-/**
- * Write one file per class. Put all of the enums into the "outer" class.
- */
-static void
-write_multiple_files(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor)
-{
- // If there is anything to put in the outer class file, create one
- if (file_descriptor.enum_type_size() > 0) {
- vector<EnumDescriptorProto> enums;
- int N = file_descriptor.enum_type_size();
- for (int i=0; i<N; i++) {
- enums.push_back(file_descriptor.enum_type(i));
- }
-
- vector<DescriptorProto> messages;
-
- write_file(response, file_descriptor,
- make_file_name(file_descriptor, make_outer_class_name(file_descriptor)),
- true, enums, messages);
- }
-
- // For each of the message types, make a file
- int N = file_descriptor.message_type_size();
- for (int i=0; i<N; i++) {
- vector<EnumDescriptorProto> enums;
-
- vector<DescriptorProto> messages;
- messages.push_back(file_descriptor.message_type(i));
-
- write_file(response, file_descriptor,
- make_file_name(file_descriptor, file_descriptor.message_type(i).name()),
- false, enums, messages);
- }
-}
-
-static void
-write_single_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor)
-{
- int N;
-
- vector<EnumDescriptorProto> enums;
- N = file_descriptor.enum_type_size();
- for (int i=0; i<N; i++) {
- enums.push_back(file_descriptor.enum_type(i));
- }
-
- vector<DescriptorProto> messages;
- N = file_descriptor.message_type_size();
- for (int i=0; i<N; i++) {
- messages.push_back(file_descriptor.message_type(i));
- }
-
- write_file(response, file_descriptor,
- make_file_name(file_descriptor, make_outer_class_name(file_descriptor)),
- true, enums, messages);
-}
-
-/**
* Main.
*/
int
@@ -273,24 +26,11 @@
GOOGLE_PROTOBUF_VERIFY_VERSION;
- CodeGeneratorRequest request;
- CodeGeneratorResponse response;
-
// Read the request
+ CodeGeneratorRequest request;
request.ParseFromIstream(&cin);
- // Build the files we need.
- const int N = request.proto_file_size();
- for (int i=0; i<N; i++) {
- const FileDescriptorProto& file_descriptor = request.proto_file(i);
- if (should_generate_for_file(request, file_descriptor.name())) {
- if (file_descriptor.options().java_multiple_files()) {
- write_multiple_files(&response, file_descriptor);
- } else {
- write_single_file(&response, file_descriptor);
- }
- }
- }
+ CodeGeneratorResponse response = generate_java_protostream_code(request);
// If we had errors, don't write the response. Print the errors and exit.
if (ERRORS.HasErrors()) {
diff --git a/tools/streaming_proto/test/imported.proto b/tools/streaming_proto/test/integration/imported.proto
similarity index 100%
rename from tools/streaming_proto/test/imported.proto
rename to tools/streaming_proto/test/integration/imported.proto
diff --git a/core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl b/tools/streaming_proto/test/integration/src/com/android/streaming_proto_test/Main.java
similarity index 66%
rename from core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl
rename to tools/streaming_proto/test/integration/src/com/android/streaming_proto_test/Main.java
index 838e41e..2a7001b 100644
--- a/core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl
+++ b/tools/streaming_proto/test/integration/src/com/android/streaming_proto_test/Main.java
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2024, The Android Open Source Project
+/*
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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,
@@ -14,11 +14,10 @@
* limitations under the License.
*/
-package android.hardware;
+package com.android.streaming_proto_test;
-/** @hide */
-parcelable CameraPrivacyAllowlistEntry {
- String packageName;
- boolean isMandatory;
+public class Main {
+ public void main(String[] argv) {
+ System.out.println("hello world");
+ }
}
-
diff --git a/tools/streaming_proto/test/test.proto b/tools/streaming_proto/test/integration/test.proto
similarity index 97%
rename from tools/streaming_proto/test/test.proto
rename to tools/streaming_proto/test/integration/test.proto
index de80ed6..3cf81b4 100644
--- a/tools/streaming_proto/test/test.proto
+++ b/tools/streaming_proto/test/integration/test.proto
@@ -16,7 +16,7 @@
syntax = "proto2";
-import "frameworks/base/tools/streaming_proto/test/imported.proto";
+import "frameworks/base/tools/streaming_proto/test/integration/imported.proto";
package com.android.streaming_proto_test;
diff --git a/tools/streaming_proto/test/src/com/android/streaming_proto_test/Main.java b/tools/streaming_proto/test/src/com/android/streaming_proto_test/Main.java
deleted file mode 100644
index 1246f53..0000000
--- a/tools/streaming_proto/test/src/com/android/streaming_proto_test/Main.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.android.streaming_proto_test;
-
-public class Main {
- public void main(String[] argv) {
- System.out.println("hello world");
- }
-}
diff --git a/tools/streaming_proto/test/unit/streaming_proto_java.cpp b/tools/streaming_proto/test/unit/streaming_proto_java.cpp
new file mode 100644
index 0000000..8df9716
--- /dev/null
+++ b/tools/streaming_proto/test/unit/streaming_proto_java.cpp
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "java/java_proto_stream_code_generator.h"
+
+using ::testing::HasSubstr;
+using ::testing::Not;
+
+static void add_my_test_proto_file(CodeGeneratorRequest* request) {
+ request->add_file_to_generate("MyTestProtoFile");
+
+ FileDescriptorProto* file_desc = request->add_proto_file();
+ file_desc->set_name("MyTestProtoFile");
+ file_desc->set_package("test.package");
+
+ auto* file_options = file_desc->mutable_options();
+ file_options->set_java_multiple_files(false);
+
+ auto* message = file_desc->add_message_type();
+ message->set_name("MyTestMessage");
+
+ auto* field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("my_test_field");
+
+ field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("my_other_test_field");
+
+ field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("my_other_test_message");
+}
+
+static void add_my_other_test_proto_file(CodeGeneratorRequest* request) {
+ request->add_file_to_generate("MyOtherTestProtoFile");
+
+ FileDescriptorProto* file_desc = request->add_proto_file();
+ file_desc->set_name("MyOtherTestProtoFile");
+ file_desc->set_package("test.package");
+
+ auto* file_options = file_desc->mutable_options();
+ file_options->set_java_multiple_files(false);
+
+ auto* message = file_desc->add_message_type();
+ message->set_name("MyOtherTestMessage");
+
+ auto* field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("a_test_field");
+
+ field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("another_test_field");
+}
+
+static CodeGeneratorRequest create_simple_two_file_request() {
+ CodeGeneratorRequest request;
+
+ add_my_test_proto_file(&request);
+ add_my_other_test_proto_file(&request);
+
+ return request;
+}
+
+static CodeGeneratorRequest create_simple_multi_file_request() {
+ CodeGeneratorRequest request;
+
+ request.add_file_to_generate("MyMultiMessageTestProtoFile");
+
+ FileDescriptorProto* file_desc = request.add_proto_file();
+ file_desc->set_name("MyMultiMessageTestProtoFile");
+ file_desc->set_package("test.package");
+
+ auto* file_options = file_desc->mutable_options();
+ file_options->set_java_multiple_files(true);
+
+ auto* message = file_desc->add_message_type();
+ message->set_name("MyTestMessage");
+
+ auto* field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("my_test_field");
+
+ field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("my_other_test_field");
+
+ field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("my_other_test_message");
+
+ message = file_desc->add_message_type();
+ message->set_name("MyOtherTestMessage");
+
+ field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("a_test_field");
+
+ field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("another_test_field");
+
+ return request;
+}
+
+TEST(StreamingProtoJavaTest, NoFilter) {
+ CodeGeneratorRequest request = create_simple_two_file_request();
+ CodeGeneratorResponse response = generate_java_protostream_code(request);
+
+ auto generated_file_count = response.file_size();
+ EXPECT_EQ(generated_file_count, 2);
+
+ EXPECT_EQ(response.file(0).name(), "test/package/MyTestProtoFile.java");
+ EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestProtoFile"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestMessage"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_TEST_FIELD"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_OTHER_TEST_FIELD"));
+
+ EXPECT_EQ(response.file(1).name(), "test/package/MyOtherTestProtoFile.java");
+ EXPECT_THAT(response.file(1).content(), HasSubstr("class MyOtherTestProtoFile"));
+ EXPECT_THAT(response.file(1).content(), HasSubstr("class MyOtherTestMessage"));
+ EXPECT_THAT(response.file(1).content(), HasSubstr("long A_TEST_FIELD"));
+ EXPECT_THAT(response.file(1).content(), HasSubstr("long ANOTHER_TEST_FIELD"));
+}
+
+TEST(StreamingProtoJavaTest, WithFilter) {
+ CodeGeneratorRequest request = create_simple_two_file_request();
+ request.set_parameter("include_filter:test.package.MyTestMessage");
+ CodeGeneratorResponse response = generate_java_protostream_code(request);
+
+ auto generated_file_count = response.file_size();
+ EXPECT_EQ(generated_file_count, 1);
+
+ EXPECT_EQ(response.file(0).name(), "test/package/MyTestProtoFile.java");
+ EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestProtoFile"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestMessage"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_TEST_FIELD"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_OTHER_TEST_FIELD"));
+}
+
+TEST(StreamingProtoJavaTest, WithoutFilter_MultipleJavaFiles) {
+ CodeGeneratorRequest request = create_simple_multi_file_request();
+ CodeGeneratorResponse response = generate_java_protostream_code(request);
+
+ auto generated_file_count = response.file_size();
+ EXPECT_EQ(generated_file_count, 2);
+
+ EXPECT_EQ(response.file(0).name(), "test/package/MyTestMessage.java");
+ EXPECT_THAT(response.file(0).content(), Not(HasSubstr("class MyTestProtoFile")));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestMessage"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_TEST_FIELD"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_OTHER_TEST_FIELD"));
+
+ EXPECT_EQ(response.file(1).name(), "test/package/MyOtherTestMessage.java");
+ EXPECT_THAT(response.file(1).content(), Not(HasSubstr("class MyOtherTestProtoFile")));
+ EXPECT_THAT(response.file(1).content(), HasSubstr("class MyOtherTestMessage"));
+ EXPECT_THAT(response.file(1).content(), HasSubstr("long A_TEST_FIELD"));
+ EXPECT_THAT(response.file(1).content(), HasSubstr("long ANOTHER_TEST_FIELD"));
+}
+
+TEST(StreamingProtoJavaTest, WithFilter_MultipleJavaFiles) {
+ CodeGeneratorRequest request = create_simple_multi_file_request();
+ request.set_parameter("include_filter:test.package.MyTestMessage");
+ CodeGeneratorResponse response = generate_java_protostream_code(request);
+
+ auto generated_file_count = response.file_size();
+ EXPECT_EQ(generated_file_count, 1);
+
+ EXPECT_EQ(response.file(0).name(), "test/package/MyTestMessage.java");
+ EXPECT_THAT(response.file(0).content(), Not(HasSubstr("class MyTestProtoFile")));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestMessage"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_TEST_FIELD"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_OTHER_TEST_FIELD"));
+}
diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
index 58638e8..45ab986 100644
--- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
+++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
@@ -718,6 +718,9 @@
} catch (RemoteException e1) {
Log.e(TAG, "Failed to get IClientInterface due to remote exception");
return false;
+ } catch (NullPointerException e2) {
+ Log.e(TAG, "setupInterfaceForClientMode NullPointerException");
+ return false;
}
if (clientInterface == null) {
@@ -785,6 +788,9 @@
} catch (RemoteException e1) {
Log.e(TAG, "Failed to teardown client interface due to remote exception");
return false;
+ } catch (NullPointerException e2) {
+ Log.e(TAG, "tearDownClientInterface NullPointerException");
+ return false;
}
if (!success) {
Log.e(TAG, "Failed to teardown client interface");
@@ -816,6 +822,9 @@
} catch (RemoteException e1) {
Log.e(TAG, "Failed to get IApInterface due to remote exception");
return false;
+ } catch (NullPointerException e2) {
+ Log.e(TAG, "setupInterfaceForSoftApMode NullPointerException");
+ return false;
}
if (apInterface == null) {
@@ -854,6 +863,9 @@
} catch (RemoteException e1) {
Log.e(TAG, "Failed to teardown AP interface due to remote exception");
return false;
+ } catch (NullPointerException e2) {
+ Log.e(TAG, "tearDownSoftApInterface NullPointerException");
+ return false;
}
if (!success) {
Log.e(TAG, "Failed to teardown AP interface");
@@ -1328,6 +1340,8 @@
}
} catch (RemoteException e1) {
Log.e(TAG, "Failed to request getChannelsForBand due to remote exception");
+ } catch (NullPointerException e2) {
+ Log.e(TAG, "getChannelsMhzForBand NullPointerException");
}
if (result == null) {
result = new int[0];
@@ -1352,7 +1366,8 @@
*/
@Nullable public DeviceWiphyCapabilities getDeviceWiphyCapabilities(@NonNull String ifaceName) {
if (mWificond == null) {
- Log.e(TAG, "getDeviceWiphyCapabilities: mWificond binder is null! Did wificond die?");
+ Log.e(TAG, "getDeviceWiphyCapabilities: mWificond binder is null! "
+ + "Did wificond die?");
return null;
}
@@ -1360,6 +1375,9 @@
return mWificond.getDeviceWiphyCapabilities(ifaceName);
} catch (RemoteException e) {
return null;
+ } catch (NullPointerException e2) {
+ Log.e(TAG, "getDeviceWiphyCapabilities NullPointerException");
+ return null;
}
}
@@ -1409,6 +1427,8 @@
Log.i(TAG, "Receive country code change to " + newCountryCode);
} catch (RemoteException re) {
re.rethrowFromSystemServer();
+ } catch (NullPointerException e) {
+ new RemoteException("Wificond service doesn't exist!").rethrowFromSystemServer();
}
}