Merge "Clarify InputMethodSettings doesn't require ImfLock" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index 1091706..20b9b0e 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -12319,7 +12319,6 @@
method public void registerCallback(android.content.pm.LauncherApps.Callback, android.os.Handler);
method public void registerPackageInstallerSessionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.content.pm.PackageInstaller.SessionCallback);
method public android.content.pm.LauncherActivityInfo resolveActivity(android.content.Intent, android.os.UserHandle);
- method @FlaggedApi("android.content.pm.archiving") public void setArchiveCompatibilityOptions(boolean, boolean);
method public boolean shouldHideFromSuggestions(@NonNull String, @NonNull android.os.UserHandle);
method public void startAppDetailsActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
method public void startMainActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
@@ -44954,7 +44953,7 @@
method public void addOnSubscriptionsChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void addSubscriptionsIntoGroup(@NonNull java.util.List<java.lang.Integer>, @NonNull android.os.ParcelUuid);
method public boolean canManageSubscription(android.telephony.SubscriptionInfo);
- method @FlaggedApi("com.android.internal.telephony.flags.enforce_subscription_user_filter") @NonNull public android.telephony.SubscriptionManager createForAllUserProfiles();
+ method @FlaggedApi("com.android.internal.telephony.flags.enforce_subscription_user_filter") @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_PROFILES) public android.telephony.SubscriptionManager createForAllUserProfiles();
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.os.ParcelUuid createSubscriptionGroup(@NonNull java.util.List<java.lang.Integer>);
method @Deprecated public static android.telephony.SubscriptionManager from(android.content.Context);
method public java.util.List<android.telephony.SubscriptionInfo> getAccessibleSubscriptionInfoList();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index debf1bf..fe32bad 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3959,6 +3959,7 @@
method public void setInstallAsInstantApp(boolean);
method public void setInstallAsVirtualPreload();
method public void setRequestDowngrade(boolean);
+ method @FlaggedApi("android.content.pm.recoverability_detection") @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void setRollbackImpactLevel(int);
method @FlaggedApi("android.content.pm.rollback_lifetime") @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void setRollbackLifetimeMillis(long);
method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setStaged();
}
@@ -4127,6 +4128,9 @@
field public static final int ROLLBACK_DATA_POLICY_RESTORE = 0; // 0x0
field public static final int ROLLBACK_DATA_POLICY_RETAIN = 2; // 0x2
field public static final int ROLLBACK_DATA_POLICY_WIPE = 1; // 0x1
+ field @FlaggedApi("android.content.pm.recoverability_detection") public static final int ROLLBACK_USER_IMPACT_HIGH = 1; // 0x1
+ field @FlaggedApi("android.content.pm.recoverability_detection") public static final int ROLLBACK_USER_IMPACT_LOW = 0; // 0x0
+ field @FlaggedApi("android.content.pm.recoverability_detection") public static final int ROLLBACK_USER_IMPACT_ONLY_MANUAL = 2; // 0x2
field public static final int SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN = 0; // 0x0
field public static final int SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_VISIBLE = 1; // 0x1
field public static final int SYSTEM_APP_STATE_INSTALLED = 2; // 0x2
@@ -14712,7 +14716,7 @@
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEmergencyNumberDbVersion();
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimDomain();
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimIst();
- method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @Nullable @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, "com.android.phone.permission.ACCESS_LAST_KNOWN_CELL_ID"}) public android.telephony.CellIdentity getLastKnownCellIdentity();
+ method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @Nullable @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_LAST_KNOWN_CELL_ID}) public android.telephony.CellIdentity getLastKnownCellIdentity();
method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.Map<java.lang.Integer,java.lang.Integer> getLogicalToPhysicalSlotMapping();
method public int getMaxNumberOfSimultaneouslyActiveSims();
method public static long getMaxNumberVerificationTimeoutMillis();
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 5d271cc..b8b98a3 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -377,6 +377,7 @@
public class NotificationManager {
method @FlaggedApi("android.app.modes_api") @NonNull public String addAutomaticZenRule(@NonNull android.app.AutomaticZenRule, boolean);
method public void cleanUpCallersAfter(long);
+ method @FlaggedApi("android.app.modes_api") @NonNull public android.service.notification.ZenPolicy getDefaultZenPolicy();
method public android.content.ComponentName getEffectsSuppressor();
method public boolean isNotificationPolicyAccessGrantedForPackage(@NonNull String);
method @FlaggedApi("android.app.modes_api") public boolean removeAutomaticZenRule(@NonNull String, boolean);
@@ -1210,6 +1211,10 @@
package android.content.rollback {
+ public final class RollbackInfo implements android.os.Parcelable {
+ method @FlaggedApi("android.content.pm.recoverability_detection") public int getRollbackImpactLevel();
+ }
+
public final class RollbackManager {
method @RequiresPermission(android.Manifest.permission.TEST_MANAGE_ROLLBACKS) public void blockRollbackManager(long);
method @RequiresPermission(android.Manifest.permission.TEST_MANAGE_ROLLBACKS) public void expireRollbackForPackage(@NonNull String);
@@ -3023,6 +3028,10 @@
method @Deprecated public boolean isBound();
}
+ public final class ZenPolicy implements android.os.Parcelable {
+ method @FlaggedApi("android.app.modes_api") @NonNull public android.service.notification.ZenPolicy overwrittenWith(@Nullable android.service.notification.ZenPolicy);
+ }
+
public static final class ZenPolicy.Builder {
ctor public ZenPolicy.Builder(@Nullable android.service.notification.ZenPolicy);
}
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index ea37e7f..d8d136a 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1548,24 +1548,9 @@
public static final int OP_READ_SYSTEM_GRAMMATICAL_GENDER =
AppProtoEnums.APP_OP_READ_SYSTEM_GRAMMATICAL_GENDER;
- /**
- * Whether the app has enabled to receive the icon overlay for fetching archived apps.
- *
- * @hide
- */
- public static final int OP_ARCHIVE_ICON_OVERLAY = AppProtoEnums.APP_OP_ARCHIVE_ICON_OVERLAY;
-
- /**
- * Whether the app has enabled compatibility support for unarchival.
- *
- * @hide
- */
- public static final int OP_UNARCHIVAL_CONFIRMATION =
- AppProtoEnums.APP_OP_UNARCHIVAL_CONFIRMATION;
-
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int _NUM_OP = 146;
+ public static final int _NUM_OP = 144;
/**
* All app ops represented as strings.
@@ -1715,8 +1700,6 @@
OPSTR_ENABLE_MOBILE_DATA_BY_USER,
OPSTR_RESERVED_FOR_TESTING,
OPSTR_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER,
- OPSTR_ARCHIVE_ICON_OVERLAY,
- OPSTR_UNARCHIVAL_CONFIRMATION,
})
public @interface AppOpString {}
@@ -2057,20 +2040,6 @@
public static final String OPSTR_MEDIA_ROUTING_CONTROL = "android:media_routing_control";
/**
- * Whether the app has enabled to receive the icon overlay for fetching archived apps.
- *
- * @hide
- */
- public static final String OPSTR_ARCHIVE_ICON_OVERLAY = "android:archive_icon_overlay";
-
- /**
- * Whether the app has enabled compatibility support for unarchival.
- *
- * @hide
- */
- public static final String OPSTR_UNARCHIVAL_CONFIRMATION = "android:unarchival_support";
-
- /**
* AppOp granted to apps that we are started via {@code am instrument -e --no-isolated-storage}
*
* <p>MediaProvider is the only component (outside of system server) that should care about this
@@ -2535,8 +2504,6 @@
OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA,
OP_MEDIA_ROUTING_CONTROL,
OP_READ_SYSTEM_GRAMMATICAL_GENDER,
- OP_ARCHIVE_ICON_OVERLAY,
- OP_UNARCHIVAL_CONFIRMATION,
};
static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{
@@ -2993,12 +2960,6 @@
OPSTR_READ_SYSTEM_GRAMMATICAL_GENDER, "READ_SYSTEM_GRAMMATICAL_GENDER")
.setPermission(Manifest.permission.READ_SYSTEM_GRAMMATICAL_GENDER)
.build(),
- new AppOpInfo.Builder(OP_ARCHIVE_ICON_OVERLAY, OPSTR_ARCHIVE_ICON_OVERLAY,
- "ARCHIVE_ICON_OVERLAY")
- .setDefaultMode(MODE_ALLOWED).build(),
- new AppOpInfo.Builder(OP_UNARCHIVAL_CONFIRMATION, OPSTR_UNARCHIVAL_CONFIRMATION,
- "UNARCHIVAL_CONFIRMATION")
- .setDefaultMode(MODE_ALLOWED).build(),
};
// The number of longs needed to form a full bitmask of app ops
@@ -3133,7 +3094,7 @@
/**
* Retrieve the permission associated with an operation, or null if there is not one.
-
+ *
* @param op The operation name.
*
* @hide
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 4f1db7d..34c44f9 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -4032,8 +4032,7 @@
private Drawable getArchivedAppIcon(String packageName) {
try {
return new BitmapDrawable(null,
- mPM.getArchivedAppIcon(packageName, new UserHandle(getUserId()),
- mContext.getPackageName()));
+ mPM.getArchivedAppIcon(packageName, new UserHandle(getUserId())));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index c3adbc3..578105f 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -38,6 +38,7 @@
import android.service.notification.INotificationListener;
import android.service.notification.NotificationListenerFilter;
import android.service.notification.StatusBarNotification;
+import android.service.notification.ZenPolicy;
import android.app.AutomaticZenRule;
import android.service.notification.ZenModeConfig;
@@ -213,6 +214,7 @@
boolean isNotificationPolicyAccessGrantedForPackage(String pkg);
void setNotificationPolicyAccessGranted(String pkg, boolean granted);
void setNotificationPolicyAccessGrantedForUser(String pkg, int userId, boolean granted);
+ ZenPolicy getDefaultZenPolicy();
AutomaticZenRule getAutomaticZenRule(String id);
Map<String, AutomaticZenRule> getAutomaticZenRules();
// TODO: b/310620812 - Remove getZenRules() when MODES_API is inlined.
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 0b6e24c..366b45b 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -1750,6 +1750,20 @@
@NonNull ComponentName listener, boolean granted) {
setNotificationListenerAccessGranted(listener, granted, true);
}
+ /**
+ * Gets the device-default notification policy as a ZenPolicy.
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public @NonNull ZenPolicy getDefaultZenPolicy() {
+ INotificationManager service = getService();
+ try {
+ return service.getDefaultZenPolicy();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
/**
* For apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above, the
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index 62db65f..a97de63 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -128,6 +128,4 @@
/** Unregister a callback, so that it won't be called when LauncherApps dumps. */
void unRegisterDumpCallback(IDumpCallback cb);
-
- void setArchiveCompatibilityOptions(boolean enableIconOverlay, boolean enableUnarchivalConfirmation);
}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 380de96..6dc8d47 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -840,7 +840,7 @@
ArchivedPackageParcel getArchivedPackage(in String packageName, int userId);
- Bitmap getArchivedAppIcon(String packageName, in UserHandle user, String callingPackageName);
+ Bitmap getArchivedAppIcon(String packageName, in UserHandle user);
boolean isAppArchivable(String packageName, in UserHandle user);
}
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 50be983..1d2b1af 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -1801,31 +1801,6 @@
}
}
- /**
- * Enable or disable different archive compatibility options of the launcher.
- *
- * @param enableIconOverlay Provides a cloud overlay for archived apps to ensure users are aware
- * that a certain app is archived. True by default.
- * Launchers might want to disable this operation if they want to provide custom user experience
- * to differentiate archived apps.
- * @param enableUnarchivalConfirmation If true, the user is shown a confirmation dialog when
- * they click an archived app, which explains that the app will be downloaded and restored in
- * the background. True by default.
- * Launchers might want to disable this operation if they provide sufficient, alternative user
- * guidance to highlight that an unarchival is starting and ongoing once an archived app is
- * tapped. E.g., this could be achieved by showing the unarchival progress around the icon.
- */
- @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING)
- public void setArchiveCompatibilityOptions(boolean enableIconOverlay,
- boolean enableUnarchivalConfirmation) {
- try {
- mService.setArchiveCompatibilityOptions(enableIconOverlay,
- enableUnarchivalConfirmation);
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
- }
-
/** @return position in mCallbacks for callback or -1 if not present. */
private int findCallbackLocked(Callback callback) {
if (callback == null) {
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index f0efed9..22926fe 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -2693,6 +2693,8 @@
/** @hide */
public long rollbackLifetimeMillis = 0;
/** {@hide} */
+ public int rollbackImpactLevel = PackageManager.ROLLBACK_USER_IMPACT_LOW;
+ /** {@hide} */
public boolean forceQueryableOverride;
/** {@hide} */
public int requireUserAction = USER_ACTION_UNSPECIFIED;
@@ -2749,6 +2751,7 @@
}
rollbackDataPolicy = source.readInt();
rollbackLifetimeMillis = source.readLong();
+ rollbackImpactLevel = source.readInt();
requireUserAction = source.readInt();
packageSource = source.readInt();
applicationEnabledSettingPersistent = source.readBoolean();
@@ -2783,6 +2786,7 @@
ret.dataLoaderParams = dataLoaderParams;
ret.rollbackDataPolicy = rollbackDataPolicy;
ret.rollbackLifetimeMillis = rollbackLifetimeMillis;
+ ret.rollbackImpactLevel = rollbackImpactLevel;
ret.requireUserAction = requireUserAction;
ret.packageSource = packageSource;
ret.applicationEnabledSettingPersistent = applicationEnabledSettingPersistent;
@@ -3121,6 +3125,28 @@
}
/**
+ * rollbackImpactLevel is a measure of impact a rollback has on the user. This can take one
+ * of 3 values:
+ * <ul>
+ * <li>{@link PackageManager#ROLLBACK_USER_IMPACT_LOW} (default)</li>
+ * <li>{@link PackageManager#ROLLBACK_USER_IMPACT_HIGH} (1)</li>
+ * <li>{@link PackageManager#ROLLBACK_USER_IMPACT_ONLY_MANUAL} (2)</li>
+ * </ul>
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS)
+ @FlaggedApi(Flags.FLAG_RECOVERABILITY_DETECTION)
+ public void setRollbackImpactLevel(@PackageManager.RollbackImpactLevel int impactLevel) {
+ if ((installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) == 0) {
+ throw new IllegalArgumentException(
+ "Can't set rollbackImpactLevel when rollback is not enabled");
+ }
+ rollbackImpactLevel = impactLevel;
+ }
+
+ /**
* @deprecated use {@link #setRequestDowngrade(boolean)}.
* {@hide}
*/
@@ -3493,6 +3519,7 @@
pw.printPair("dataLoaderParams", dataLoaderParams);
pw.printPair("rollbackDataPolicy", rollbackDataPolicy);
pw.printPair("rollbackLifetimeMillis", rollbackLifetimeMillis);
+ pw.printPair("rollbackImpactLevel", rollbackImpactLevel);
pw.printPair("applicationEnabledSettingPersistent",
applicationEnabledSettingPersistent);
pw.printHexPair("developmentInstallFlags", developmentInstallFlags);
@@ -3536,6 +3563,7 @@
}
dest.writeInt(rollbackDataPolicy);
dest.writeLong(rollbackLifetimeMillis);
+ dest.writeInt(rollbackImpactLevel);
dest.writeInt(requireUserAction);
dest.writeInt(packageSource);
dest.writeBoolean(applicationEnabledSettingPersistent);
@@ -3734,6 +3762,9 @@
public long rollbackLifetimeMillis;
/** {@hide} */
+ public int rollbackImpactLevel;
+
+ /** {@hide} */
public int requireUserAction;
/** {@hide} */
@@ -3801,6 +3832,7 @@
isPreapprovalRequested = source.readBoolean();
rollbackDataPolicy = source.readInt();
rollbackLifetimeMillis = source.readLong();
+ rollbackImpactLevel = source.readInt();
createdMillis = source.readLong();
requireUserAction = source.readInt();
installerUid = source.readInt();
@@ -4438,6 +4470,7 @@
dest.writeBoolean(isPreapprovalRequested);
dest.writeInt(rollbackDataPolicy);
dest.writeLong(rollbackLifetimeMillis);
+ dest.writeInt(rollbackImpactLevel);
dest.writeLong(createdMillis);
dest.writeInt(requireUserAction);
dest.writeInt(installerUid);
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index aabbe69..4724e86 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1501,6 +1501,44 @@
public static final int ROLLBACK_DATA_POLICY_RETAIN = 2;
/** @hide */
+ @IntDef(prefix = {"ROLLBACK_USER_IMPACT_"}, value = {
+ ROLLBACK_USER_IMPACT_LOW,
+ ROLLBACK_USER_IMPACT_HIGH,
+ ROLLBACK_USER_IMPACT_ONLY_MANUAL,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RollbackImpactLevel {}
+
+ /**
+ * Rollback will be performed automatically in response to native crashes on startup or
+ * persistent service crashes. More suitable for apps that do not store any user data.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(android.content.pm.Flags.FLAG_RECOVERABILITY_DETECTION)
+ public static final int ROLLBACK_USER_IMPACT_LOW = 0;
+
+ /**
+ * Rollback will be performed automatically only when the device is found to be unrecoverable.
+ * More suitable for apps that store user data and have higher impact on user.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(android.content.pm.Flags.FLAG_RECOVERABILITY_DETECTION)
+ public static final int ROLLBACK_USER_IMPACT_HIGH = 1;
+
+ /**
+ * Rollback will not be performed automatically. It can be triggered externally.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(android.content.pm.Flags.FLAG_RECOVERABILITY_DETECTION)
+ public static final int ROLLBACK_USER_IMPACT_ONLY_MANUAL = 2;
+
+ /** @hide */
@IntDef(flag = true, prefix = { "INSTALL_" }, value = {
INSTALL_REPLACE_EXISTING,
INSTALL_ALLOW_TEST,
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index a2cd3e1..e4e9fba 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -146,3 +146,10 @@
bug: "281848623"
}
+flag {
+ name: "recoverability_detection"
+ namespace: "package_manager_service"
+ description: "Feature flag to enable recoverability detection feature. It includes GMS core rollback and improvements to rescue party."
+ bug: "291135724"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/content/rollback/RollbackInfo.java b/core/java/android/content/rollback/RollbackInfo.java
index a363718..d128055 100644
--- a/core/java/android/content/rollback/RollbackInfo.java
+++ b/core/java/android/content/rollback/RollbackInfo.java
@@ -16,8 +16,12 @@
package android.content.rollback;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.content.pm.Flags;
+import android.content.pm.PackageManager;
import android.content.pm.VersionedPackage;
import android.os.Parcel;
import android.os.Parcelable;
@@ -25,17 +29,14 @@
import java.util.List;
/**
- * Information about a set of packages that can be, or already have been
- * rolled back together.
+ * Information about a set of packages that can be, or already have been rolled back together.
*
* @hide
*/
@SystemApi
public final class RollbackInfo implements Parcelable {
- /**
- * A unique identifier for the rollback.
- */
+ /** A unique identifier for the rollback. */
private final int mRollbackId;
private final List<PackageRollbackInfo> mPackages;
@@ -44,15 +45,39 @@
private final boolean mIsStaged;
private int mCommittedSessionId;
+ private int mRollbackImpactLevel;
/** @hide */
- public RollbackInfo(int rollbackId, List<PackageRollbackInfo> packages, boolean isStaged,
- List<VersionedPackage> causePackages, int committedSessionId) {
+ public RollbackInfo(
+ int rollbackId,
+ List<PackageRollbackInfo> packages,
+ boolean isStaged,
+ List<VersionedPackage> causePackages,
+ int committedSessionId,
+ @PackageManager.RollbackImpactLevel int rollbackImpactLevel) {
this.mRollbackId = rollbackId;
this.mPackages = packages;
this.mIsStaged = isStaged;
this.mCausePackages = causePackages;
this.mCommittedSessionId = committedSessionId;
+ this.mRollbackImpactLevel = rollbackImpactLevel;
+ }
+
+ /** @hide */
+ public RollbackInfo(
+ int rollbackId,
+ List<PackageRollbackInfo> packages,
+ boolean isStaged,
+ List<VersionedPackage> causePackages,
+ int committedSessionId) {
+ // If impact level is not set default to 0
+ this(
+ rollbackId,
+ packages,
+ isStaged,
+ causePackages,
+ committedSessionId,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
}
private RollbackInfo(Parcel in) {
@@ -61,34 +86,28 @@
mIsStaged = in.readBoolean();
mCausePackages = in.createTypedArrayList(VersionedPackage.CREATOR);
mCommittedSessionId = in.readInt();
+ mRollbackImpactLevel = in.readInt();
}
- /**
- * Returns a unique identifier for this rollback.
- */
+ /** Returns a unique identifier for this rollback. */
public int getRollbackId() {
return mRollbackId;
}
- /**
- * Returns the list of package that are rolled back.
- */
+ /** Returns the list of package that are rolled back. */
@NonNull
public List<PackageRollbackInfo> getPackages() {
return mPackages;
}
- /**
- * Returns true if this rollback requires reboot to take effect after
- * being committed.
- */
+ /** Returns true if this rollback requires reboot to take effect after being committed. */
public boolean isStaged() {
return mIsStaged;
}
/**
- * Returns the session ID for the committed rollback for staged rollbacks.
- * Only applicable for rollbacks that have been committed.
+ * Returns the session ID for the committed rollback for staged rollbacks. Only applicable for
+ * rollbacks that have been committed.
*/
public int getCommittedSessionId() {
return mCommittedSessionId;
@@ -96,6 +115,7 @@
/**
* Sets the session ID for the committed rollback for staged rollbacks.
+ *
* @hide
*/
public void setCommittedSessionId(int sessionId) {
@@ -103,15 +123,40 @@
}
/**
- * Gets the list of package versions that motivated this rollback.
- * As provided to {@link #commitRollback} when the rollback was committed.
- * This is only applicable for rollbacks that have been committed.
+ * Gets the list of package versions that motivated this rollback. As provided to {@link
+ * #commitRollback} when the rollback was committed. This is only applicable for rollbacks that
+ * have been committed.
*/
@NonNull
public List<VersionedPackage> getCausePackages() {
return mCausePackages;
}
+ /**
+ * Get rollback impact level. Refer {@link
+ * android.content.pm.PackageInstaller.SessionParams#setRollbackImpactLevel(int)} for more info
+ * on impact level.
+ *
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(Flags.FLAG_RECOVERABILITY_DETECTION)
+ public @PackageManager.RollbackImpactLevel int getRollbackImpactLevel() {
+ return mRollbackImpactLevel;
+ }
+
+ /**
+ * Set rollback impact level. Refer {@link
+ * android.content.pm.PackageInstaller.SessionParams#setRollbackImpactLevel(int)} for more info
+ * on impact level.
+ *
+ * @hide
+ */
+ public void setRollbackImpactLevel(
+ @PackageManager.RollbackImpactLevel int rollbackImpactLevel) {
+ mRollbackImpactLevel = rollbackImpactLevel;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -124,16 +169,17 @@
out.writeBoolean(mIsStaged);
out.writeTypedList(mCausePackages);
out.writeInt(mCommittedSessionId);
+ out.writeInt(mRollbackImpactLevel);
}
public static final @android.annotation.NonNull Parcelable.Creator<RollbackInfo> CREATOR =
new Parcelable.Creator<RollbackInfo>() {
- public RollbackInfo createFromParcel(Parcel in) {
- return new RollbackInfo(in);
- }
+ public RollbackInfo createFromParcel(Parcel in) {
+ return new RollbackInfo(in);
+ }
- public RollbackInfo[] newArray(int size) {
- return new RollbackInfo[size];
- }
- };
+ public RollbackInfo[] newArray(int size) {
+ return new RollbackInfo[size];
+ }
+ };
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ab98c94..d946430 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3243,9 +3243,17 @@
private static final String NAME_EQ_PLACEHOLDER = "name=?";
+ // Cached values of queried settings.
+ // Key is the setting's name, value is the setting's value.
// Must synchronize on 'this' to access mValues and mValuesVersion.
private final ArrayMap<String, String> mValues = new ArrayMap<>();
+ // Cached values for queried prefixes.
+ // Key is the prefix, value is all of the settings under the prefix, mapped from a setting's
+ // name to a setting's value. The name string doesn't include the prefix.
+ // Must synchronize on 'this' to access.
+ private final ArrayMap<String, ArrayMap<String, String>> mPrefixToValues = new ArrayMap<>();
+
private final Uri mUri;
@UnsupportedAppUsage
private final ContentProviderHolder mProviderHolder;
@@ -3592,15 +3600,13 @@
|| applicationInfo.isSignedWithPlatformKey();
}
- private ArrayMap<String, String> getStringsForPrefixStripPrefix(
- ContentResolver cr, String prefix, String[] names) {
+ private Map<String, String> getStringsForPrefixStripPrefix(
+ ContentResolver cr, String prefix, List<String> names) {
String namespace = prefix.substring(0, prefix.length() - 1);
ArrayMap<String, String> keyValues = new ArrayMap<>();
int substringLength = prefix.length();
-
int currentGeneration = -1;
boolean needsGenerationTracker = false;
-
synchronized (NameValueCache.this) {
final GenerationTracker generationTracker = mGenerationTrackers.get(prefix);
if (generationTracker != null) {
@@ -3614,40 +3620,24 @@
// generation tracker and request a new one
generationTracker.destroy();
mGenerationTrackers.remove(prefix);
- for (int i = mValues.size() - 1; i >= 0; i--) {
- String key = mValues.keyAt(i);
- if (key.startsWith(prefix)) {
- mValues.remove(key);
- }
- }
+ mPrefixToValues.remove(prefix);
needsGenerationTracker = true;
} else {
- boolean prefixCached = mValues.containsKey(prefix);
- if (prefixCached) {
- if (DEBUG) {
- Log.i(TAG, "Cache hit for prefix:" + prefix);
- }
- if (names.length > 0) {
+ final ArrayMap<String, String> cachedSettings = mPrefixToValues.get(prefix);
+ if (cachedSettings != null) {
+ if (!names.isEmpty()) {
for (String name : names) {
- // mValues can contain "null" values, need to use containsKey.
- if (mValues.containsKey(name)) {
+ // The cache can contain "null" values, need to use containsKey.
+ if (cachedSettings.containsKey(name)) {
keyValues.put(
- name.substring(substringLength),
- mValues.get(name));
+ name,
+ cachedSettings.get(name));
}
}
} else {
- for (int i = 0; i < mValues.size(); ++i) {
- String key = mValues.keyAt(i);
- // Explicitly exclude the prefix as it is only there to
- // signal that the prefix has been cached.
- if (key.startsWith(prefix) && !key.equals(prefix)) {
- String value = mValues.valueAt(i);
- keyValues.put(
- key.substring(substringLength),
- value);
- }
- }
+ keyValues.putAll(cachedSettings);
+ // Remove the hack added for the legacy behavior.
+ keyValues.remove("");
}
return keyValues;
}
@@ -3657,7 +3647,6 @@
needsGenerationTracker = true;
}
}
-
if (mCallListCommand == null) {
// No list command specified, return empty map
return keyValues;
@@ -3702,20 +3691,23 @@
}
// All flags for the namespace
- Map<String, String> flagsToValues =
+ HashMap<String, String> flagsToValues =
(HashMap) b.getSerializable(Settings.NameValueTable.VALUE, java.util.HashMap.class);
+ if (flagsToValues == null) {
+ return keyValues;
+ }
// Only the flags requested by the caller
- if (names.length > 0) {
+ if (!names.isEmpty()) {
for (String name : names) {
// flagsToValues can contain "null" values, need to use containsKey.
- if (flagsToValues.containsKey(name)) {
+ final String key = Config.createCompositeName(namespace, name);
+ if (flagsToValues.containsKey(key)) {
keyValues.put(
- name.substring(substringLength),
- flagsToValues.get(name));
+ name,
+ flagsToValues.get(key));
}
}
} else {
- keyValues.ensureCapacity(keyValues.size() + flagsToValues.size());
for (Map.Entry<String, String> flag : flagsToValues.entrySet()) {
keyValues.put(
flag.getKey().substring(substringLength),
@@ -3751,10 +3743,18 @@
if (DEBUG) {
Log.i(TAG, "Updating cache for prefix:" + prefix);
}
- // cache the complete list of flags for the namespace
- mValues.putAll(flagsToValues);
- // Adding the prefix as a signal that the prefix is cached.
- mValues.put(prefix, null);
+ // Cache the complete list of flags for the namespace for bulk queries.
+ // In this cached list, the setting's name doesn't include the prefix.
+ ArrayMap<String, String> namesToValues =
+ new ArrayMap<>(flagsToValues.size() + 1);
+ for (Map.Entry<String, String> flag : flagsToValues.entrySet()) {
+ namesToValues.put(
+ flag.getKey().substring(substringLength),
+ flag.getValue());
+ }
+ // Legacy behavior, we return <"", null> when queried with name = ""
+ namesToValues.put("", null);
+ mPrefixToValues.put(prefix, namesToValues);
}
}
return keyValues;
@@ -19945,16 +19945,9 @@
@RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
public static Map<String, String> getStrings(@NonNull ContentResolver resolver,
@NonNull String namespace, @NonNull List<String> names) {
- String[] compositeNames = new String[names.size()];
- for (int i = 0, size = names.size(); i < size; ++i) {
- compositeNames[i] = createCompositeName(namespace, names.get(i));
- }
-
String prefix = createPrefix(namespace);
- ArrayMap<String, String> keyValues = sNameValueCache.getStringsForPrefixStripPrefix(
- resolver, prefix, compositeNames);
- return keyValues;
+ return sNameValueCache.getStringsForPrefixStripPrefix(resolver, prefix, names);
}
/**
@@ -20276,7 +20269,7 @@
}
}
- private static String createCompositeName(@NonNull String namespace, @NonNull String name) {
+ static String createCompositeName(@NonNull String namespace, @NonNull String name) {
Preconditions.checkNotNull(namespace);
Preconditions.checkNotNull(name);
var sb = new StringBuilder(namespace.length() + 1 + name.length());
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index c479877..9895551 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -185,7 +185,13 @@
SUPPRESSED_EFFECT_SCREEN_OFF | SUPPRESSED_EFFECT_FULL_SCREEN_INTENT
| SUPPRESSED_EFFECT_LIGHTS | SUPPRESSED_EFFECT_PEEK | SUPPRESSED_EFFECT_AMBIENT;
- public static final int XML_VERSION = 8;
+ // ZenModeConfig XML versions distinguishing key changes.
+ public static final int XML_VERSION_ZEN_UPGRADE = 8;
+ public static final int XML_VERSION_MODES_API = 11;
+
+ // TODO: b/310620812 - Update XML_VERSION and update default_zen_config.xml accordingly when
+ // modes_api is inlined.
+ private static final int XML_VERSION = 10;
public static final String ZEN_TAG = "zen";
private static final String ZEN_ATT_VERSION = "version";
private static final String ZEN_ATT_USER = "user";
@@ -586,6 +592,10 @@
}
}
+ public static int getCurrentXmlVersion() {
+ return Flags.modesApi() ? XML_VERSION_MODES_API : XML_VERSION;
+ }
+
public static ZenModeConfig readXml(TypedXmlPullParser parser)
throws XmlPullParserException, IOException {
int type = parser.getEventType();
@@ -593,7 +603,7 @@
String tag = parser.getName();
if (!ZEN_TAG.equals(tag)) return null;
final ZenModeConfig rt = new ZenModeConfig();
- rt.version = safeInt(parser, ZEN_ATT_VERSION, XML_VERSION);
+ rt.version = safeInt(parser, ZEN_ATT_VERSION, getCurrentXmlVersion());
rt.user = safeInt(parser, ZEN_ATT_USER, rt.user);
boolean readSuppressedEffects = false;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
@@ -707,14 +717,16 @@
/**
* Writes XML of current ZenModeConfig
* @param out serializer
- * @param version uses XML_VERSION if version is null
+ * @param version uses the current XML version if version is null
* @throws IOException
*/
+
public void writeXml(TypedXmlSerializer out, Integer version, boolean forBackup)
throws IOException {
+ int xmlVersion = getCurrentXmlVersion();
out.startTag(null, ZEN_TAG);
out.attribute(null, ZEN_ATT_VERSION, version == null
- ? Integer.toString(XML_VERSION) : Integer.toString(version));
+ ? Integer.toString(xmlVersion) : Integer.toString(version));
out.attributeInt(null, ZEN_ATT_USER, user);
out.startTag(null, ALLOW_TAG);
out.attributeBoolean(null, ALLOW_ATT_CALLS, allowCalls);
diff --git a/core/java/android/service/notification/ZenPolicy.aidl b/core/java/android/service/notification/ZenPolicy.aidl
new file mode 100644
index 0000000..b56f5c6
--- /dev/null
+++ b/core/java/android/service/notification/ZenPolicy.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.notification;
+
+parcelable ZenPolicy;
\ No newline at end of file
diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java
index fb491d0..d8318a6 100644
--- a/core/java/android/service/notification/ZenPolicy.java
+++ b/core/java/android/service/notification/ZenPolicy.java
@@ -1422,6 +1422,54 @@
}
/**
+ * Overwrites any policy values in this ZenPolicy with set values from newPolicy and
+ * returns a copy of the resulting ZenPolicy.
+ * Unlike apply(), values set in newPolicy will always be kept over pre-existing
+ * fields. Any values in newPolicy that are not set keep their currently set values.
+ *
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public @NonNull ZenPolicy overwrittenWith(@Nullable ZenPolicy newPolicy) {
+ ZenPolicy result = this.copy();
+
+ if (newPolicy == null) {
+ return result;
+ }
+
+ // set priority categories
+ for (int category = 0; category < mPriorityCategories.size(); category++) {
+ @State int newState = newPolicy.mPriorityCategories.get(category);
+ if (newState != STATE_UNSET) {
+ result.mPriorityCategories.set(category, newState);
+
+ if (category == PRIORITY_CATEGORY_MESSAGES) {
+ result.mPriorityMessages = newPolicy.mPriorityMessages;
+ } else if (category == PRIORITY_CATEGORY_CALLS) {
+ result.mPriorityCalls = newPolicy.mPriorityCalls;
+ } else if (category == PRIORITY_CATEGORY_CONVERSATIONS) {
+ result.mConversationSenders = newPolicy.mConversationSenders;
+ }
+ }
+ }
+
+ // set visual effects
+ for (int visualEffect = 0; visualEffect < mVisualEffects.size(); visualEffect++) {
+ if (newPolicy.mVisualEffects.get(visualEffect) != STATE_UNSET) {
+ result.mVisualEffects.set(visualEffect, newPolicy.mVisualEffects.get(visualEffect));
+ }
+ }
+
+ // set allowed channels
+ if (newPolicy.mAllowChannels != CHANNEL_POLICY_UNSET) {
+ result.mAllowChannels = newPolicy.mAllowChannels;
+ }
+
+ return result;
+ }
+
+ /**
* @hide
*/
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 8935ab3..0a813a3 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -38,6 +38,7 @@
import android.telephony.Annotation.RadioPowerState;
import android.telephony.Annotation.SimActivationState;
import android.telephony.Annotation.SrvccState;
+import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
import android.telephony.TelephonyManager.CarrierPrivilegesCallback;
import android.telephony.emergency.EmergencyNumber;
import android.telephony.ims.ImsCallSession;
@@ -116,14 +117,15 @@
}
/**
- * Register for changes to the list of active {@link SubscriptionInfo} records or to the
- * individual records themselves. When a change occurs the onSubscriptionsChanged method of
- * the listener will be invoked immediately if there has been a notification. The
- * onSubscriptionChanged method will also be triggered once initially when calling this
- * function.
+ * Register for changes to the list of {@link SubscriptionInfo} records or to the
+ * individual records (active or inactive) themselves. When a change occurs, the
+ * {@link OnSubscriptionsChangedListener#onSubscriptionsChanged()} method of
+ * the listener will be invoked immediately. The
+ * {@link OnSubscriptionsChangedListener#onSubscriptionsChanged()} method will also be invoked
+ * once initially when calling this method.
*
- * @param listener an instance of {@link SubscriptionManager.OnSubscriptionsChangedListener}
- * with onSubscriptionsChanged overridden.
+ * @param listener an instance of {@link OnSubscriptionsChangedListener} with
+ * {@link OnSubscriptionsChangedListener#onSubscriptionsChanged()} overridden.
* @param executor the executor that will execute callbacks.
* @hide
*/
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index efae57c..a11ac7c 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -94,6 +94,13 @@
}
flag {
+ name: "skip_accessibility_warning_dialog_for_trusted_services"
+ namespace: "accessibility"
+ description: "Skips showing the accessibility warning dialog for trusted services."
+ bug: "303511250"
+}
+
+flag {
namespace: "accessibility"
name: "update_always_on_a11y_service"
description: "Updates the Always-On A11yService state when the user changes the enablement of the shortcut."
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 9e14006..5e2aacd 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4497,6 +4497,16 @@
<!-- URI for default Accessibility notification sound when to enable accessibility shortcut. -->
<string name="config_defaultAccessibilityNotificationSound" translatable="false"></string>
+ <!-- Array of component names, each flattened to a string, for accessibility services that
+ can be enabled by the user without showing a warning prompt. These services must be
+ preinstalled. -->
+ <string-array translatable="false" name="config_trustedAccessibilityServices">
+ <!--
+ <item>com.example.package.first/com.example.class.FirstService</item>
+ <item>com.example.package.second/com.example.class.SecondService</item>
+ -->
+ </string-array>
+
<!-- Warning: This API can be dangerous when not implemented properly. In particular,
escrow token must NOT be retrievable from device storage. In other words, either
escrow token is not stored on device or its ciphertext is stored on device while
@@ -6891,4 +6901,8 @@
<!-- Defines suitability of the built-in speaker route.
Refer to {@link MediaRoute2Info} to see supported values. -->
<integer name="config_mediaRouter_builtInSpeakerSuitability">0</integer>
+
+ <!-- Whether to show a percentage text next to the progressbar while preparing to update the
+ device -->
+ <bool name="config_showPercentageTextDuringRebootToUpdate">true</bool>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index ef12d8f..ba14103 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3630,6 +3630,7 @@
<java-symbol type="string" name="config_defaultAccessibilityService" />
<java-symbol type="string" name="config_defaultAccessibilityNotificationSound" />
<java-symbol type="string" name="accessibility_shortcut_spoken_feedback" />
+ <java-symbol type="array" name="config_trustedAccessibilityServices" />
<java-symbol type="string" name="accessibility_select_shortcut_menu_title" />
<java-symbol type="string" name="accessibility_edit_shortcut_menu_button_title" />
@@ -5312,4 +5313,7 @@
<!-- Android MediaRouter framework configs. -->
<java-symbol type="integer" name="config_mediaRouter_builtInSpeakerSuitability" />
+
+ <!-- Shutdown thread config flags -->
+ <java-symbol type="bool" name="config_showPercentageTextDuringRebootToUpdate" />
</resources>
diff --git a/media/java/android/media/tv/BroadcastInfoRequest.java b/media/java/android/media/tv/BroadcastInfoRequest.java
index cbd8c1f..694756c 100644
--- a/media/java/android/media/tv/BroadcastInfoRequest.java
+++ b/media/java/android/media/tv/BroadcastInfoRequest.java
@@ -32,7 +32,8 @@
public abstract class BroadcastInfoRequest implements Parcelable {
/** @hide */
@Retention(RetentionPolicy.SOURCE)
- @IntDef({REQUEST_OPTION_REPEAT, REQUEST_OPTION_AUTO_UPDATE})
+ @IntDef({REQUEST_OPTION_REPEAT, REQUEST_OPTION_AUTO_UPDATE,
+ REQUEST_OPTION_ONEWAY, REQUEST_OPTION_ONESHOT})
public @interface RequestOption {}
/**
@@ -47,6 +48,18 @@
* first time, new values are detected.
*/
public static final int REQUEST_OPTION_AUTO_UPDATE = 1;
+ /**
+ * Request option: one-way
+ * <p> With this option, no response is expected after sending the request.
+ * @hide
+ */
+ public static final int REQUEST_OPTION_ONEWAY = 2;
+ /**
+ * Request option: one-shot
+ * <p> With this option, only one response will be given per request.
+ * @hide
+ */
+ public static final int REQUEST_OPTION_ONESHOT = 3;
public static final @NonNull Parcelable.Creator<BroadcastInfoRequest> CREATOR =
new Parcelable.Creator<BroadcastInfoRequest>() {
diff --git a/media/java/android/media/tv/ITvInputClient.aidl b/media/java/android/media/tv/ITvInputClient.aidl
index 0f8a00a..8978277 100644
--- a/media/java/android/media/tv/ITvInputClient.aidl
+++ b/media/java/android/media/tv/ITvInputClient.aidl
@@ -44,6 +44,7 @@
void onTrackSelected(int type, in String trackId, int seq);
void onVideoAvailable(int seq);
void onVideoUnavailable(int reason, int seq);
+ void onVideoFreezeUpdated(boolean isFrozen, int seq);
void onContentAllowed(int seq);
void onContentBlocked(in String rating, int seq);
void onLayoutSurface(int left, int top, int right, int bottom, int seq);
diff --git a/media/java/android/media/tv/ITvInputSessionCallback.aidl b/media/java/android/media/tv/ITvInputSessionCallback.aidl
index a52e9a5..8e2702a 100644
--- a/media/java/android/media/tv/ITvInputSessionCallback.aidl
+++ b/media/java/android/media/tv/ITvInputSessionCallback.aidl
@@ -41,6 +41,7 @@
void onTrackSelected(int type, in String trackId);
void onVideoAvailable();
void onVideoUnavailable(int reason);
+ void onVideoFreezeUpdated(boolean isFrozen);
void onContentAllowed();
void onContentBlocked(in String rating);
void onLayoutSurface(int left, int top, int right, int bottom);
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 51b2542..caddd8a 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -740,6 +740,15 @@
}
/**
+ * This is called when the video freeze state has been updated.
+ * If {@code true}, the video is frozen on the last frame while audio playback continues.
+ * @param session A {@link TvInputManager.Session} associated with this callback.
+ * @param isFrozen Whether the video is frozen
+ */
+ public void onVideoFreezeUpdated(Session session, boolean isFrozen) {
+ }
+
+ /**
* This is called when the current program content turns out to be allowed to watch since
* its content rating is not blocked by parental controls.
*
@@ -1030,6 +1039,19 @@
});
}
+ void postVideoFreezeUpdated(boolean isFrozen) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onVideoFreezeUpdated(mSession, isFrozen);
+ if (mSession.mIAppNotificationEnabled
+ && mSession.getInteractiveAppSession() != null) {
+ mSession.getInteractiveAppSession().notifyVideoFreezeUpdated(isFrozen);
+ }
+ }
+ });
+ }
+
void postContentAllowed() {
mHandler.post(new Runnable() {
@Override
@@ -1546,6 +1568,18 @@
}
@Override
+ public void onVideoFreezeUpdated(boolean isFrozen, int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postVideoFreezeUpdated(isFrozen);
+ }
+ }
+
+ @Override
public void onContentAllowed(int seq) {
synchronized (mSessionCallbackRecordMap) {
SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 76d8e50..6301a27 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -763,6 +763,34 @@
}
/**
+ * Informs the application that the video freeze state has been updated.
+ *
+ * When {@code true}, the video is frozen on the last frame but audio playback remains
+ * active.
+ *
+ * @param isFrozen Whether or not the video is frozen
+ * @hide
+ */
+ public void notifyVideoFreezeUpdated(boolean isFrozen) {
+ executeOrPostRunnableOnMainThread(new Runnable() {
+ @MainThread
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "notifyVideoFreezeUpdated");
+ }
+ if (mSessionCallback != null) {
+ mSessionCallback.onVideoFreezeUpdated(isFrozen);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in notifyVideoFreezeUpdated", e);
+ }
+ }
+ });
+ }
+
+ /**
* Sends an updated list of all audio presentations available from a Next Generation Audio
* service. This is used by the framework to maintain the audio presentation information for
* a given track of {@link TvTrackInfo#TYPE_AUDIO}, which in turn is used by
diff --git a/media/java/android/media/tv/ad/ITvAdManager.aidl b/media/java/android/media/tv/ad/ITvAdManager.aidl
index a747e49..c4806fb 100644
--- a/media/java/android/media/tv/ad/ITvAdManager.aidl
+++ b/media/java/android/media/tv/ad/ITvAdManager.aidl
@@ -16,6 +16,7 @@
package android.media.tv.ad;
+import android.graphics.Rect;
import android.media.tv.ad.ITvAdClient;
import android.media.tv.ad.ITvAdManagerCallback;
import android.media.tv.ad.TvAdServiceInfo;
@@ -37,4 +38,9 @@
void registerCallback(in ITvAdManagerCallback callback, int userId);
void unregisterCallback(in ITvAdManagerCallback callback, int userId);
+
+ void createMediaView(in IBinder sessionToken, in IBinder windowToken, in Rect frame,
+ int userId);
+ void relayoutMediaView(in IBinder sessionToken, in Rect frame, int userId);
+ void removeMediaView(in IBinder sessionToken, int userId);
}
diff --git a/media/java/android/media/tv/ad/ITvAdSession.aidl b/media/java/android/media/tv/ad/ITvAdSession.aidl
index 751257c..3ca0198 100644
--- a/media/java/android/media/tv/ad/ITvAdSession.aidl
+++ b/media/java/android/media/tv/ad/ITvAdSession.aidl
@@ -16,6 +16,7 @@
package android.media.tv.ad;
+import android.graphics.Rect;
import android.view.Surface;
/**
@@ -27,4 +28,8 @@
void startAdService();
void setSurface(in Surface surface);
void dispatchSurfaceChanged(int format, int width, int height);
+
+ void createMediaView(in IBinder windowToken, in Rect frame);
+ void relayoutMediaView(in Rect frame);
+ void removeMediaView();
}
diff --git a/media/java/android/media/tv/ad/ITvAdSessionWrapper.java b/media/java/android/media/tv/ad/ITvAdSessionWrapper.java
index 4df2783..3d5bc89 100644
--- a/media/java/android/media/tv/ad/ITvAdSessionWrapper.java
+++ b/media/java/android/media/tv/ad/ITvAdSessionWrapper.java
@@ -17,6 +17,8 @@
package android.media.tv.ad;
import android.content.Context;
+import android.graphics.Rect;
+import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
@@ -43,6 +45,9 @@
private static final int DO_RELEASE = 1;
private static final int DO_SET_SURFACE = 2;
private static final int DO_DISPATCH_SURFACE_CHANGED = 3;
+ private static final int DO_CREATE_MEDIA_VIEW = 4;
+ private static final int DO_RELAYOUT_MEDIA_VIEW = 5;
+ private static final int DO_REMOVE_MEDIA_VIEW = 6;
private final HandlerCaller mCaller;
private TvAdService.Session mSessionImpl;
@@ -61,6 +66,7 @@
@Override
public void release() {
+ mSessionImpl.scheduleMediaViewCleanup();
mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_RELEASE));
}
@@ -97,6 +103,20 @@
args.recycle();
break;
}
+ case DO_CREATE_MEDIA_VIEW: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ mSessionImpl.createMediaView((IBinder) args.arg1, (Rect) args.arg2);
+ args.recycle();
+ break;
+ }
+ case DO_RELAYOUT_MEDIA_VIEW: {
+ mSessionImpl.relayoutMediaView((Rect) msg.obj);
+ break;
+ }
+ case DO_REMOVE_MEDIA_VIEW: {
+ mSessionImpl.removeMediaView(true);
+ break;
+ }
default: {
Log.w(TAG, "Unhandled message code: " + msg.what);
break;
@@ -129,6 +149,22 @@
mCaller.obtainMessageIIII(DO_DISPATCH_SURFACE_CHANGED, format, width, height, 0));
}
+ @Override
+ public void createMediaView(IBinder windowToken, Rect frame) {
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageOO(DO_CREATE_MEDIA_VIEW, windowToken, frame));
+ }
+
+ @Override
+ public void relayoutMediaView(Rect frame) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_RELAYOUT_MEDIA_VIEW, frame));
+ }
+
+ @Override
+ public void removeMediaView() {
+ mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_REMOVE_MEDIA_VIEW));
+ }
+
private final class TvAdEventReceiver extends InputEventReceiver {
TvAdEventReceiver(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
diff --git a/media/java/android/media/tv/ad/TvAdManager.java b/media/java/android/media/tv/ad/TvAdManager.java
index 9c75051..b2ea00d 100644
--- a/media/java/android/media/tv/ad/TvAdManager.java
+++ b/media/java/android/media/tv/ad/TvAdManager.java
@@ -21,6 +21,7 @@
import android.annotation.Nullable;
import android.annotation.SystemService;
import android.content.Context;
+import android.graphics.Rect;
import android.media.tv.TvInputManager;
import android.media.tv.flags.Flags;
import android.os.Handler;
@@ -35,6 +36,7 @@
import android.view.InputEvent;
import android.view.InputEventSender;
import android.view.Surface;
+import android.view.View;
import com.android.internal.util.Preconditions;
@@ -286,6 +288,67 @@
}
/**
+ * Creates a media view. Once the media view is created, {@link #relayoutMediaView}
+ * should be called whenever the layout of its containing view is changed.
+ * {@link #removeMediaView()} should be called to remove the media view.
+ * Since a session can have only one media view, this method should be called only once
+ * or it can be called again after calling {@link #removeMediaView()}.
+ *
+ * @param view A view for AD service.
+ * @param frame A position of the media view.
+ * @throws IllegalStateException if {@code view} is not attached to a window.
+ */
+ void createMediaView(@NonNull View view, @NonNull Rect frame) {
+ Preconditions.checkNotNull(view);
+ Preconditions.checkNotNull(frame);
+ if (view.getWindowToken() == null) {
+ throw new IllegalStateException("view must be attached to a window");
+ }
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.createMediaView(mToken, view.getWindowToken(), frame, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Relayouts the current media view.
+ *
+ * @param frame A new position of the media view.
+ */
+ void relayoutMediaView(@NonNull Rect frame) {
+ Preconditions.checkNotNull(frame);
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.relayoutMediaView(mToken, frame, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Removes the current media view.
+ */
+ void removeMediaView() {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.removeMediaView(mToken, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Notifies of any structural changes (format or size) of the surface passed in
* {@link #setSurface}.
*
diff --git a/media/java/android/media/tv/ad/TvAdService.java b/media/java/android/media/tv/ad/TvAdService.java
index 6995703..6f7f67d 100644
--- a/media/java/android/media/tv/ad/TvAdService.java
+++ b/media/java/android/media/tv/ad/TvAdService.java
@@ -20,19 +20,25 @@
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.Px;
import android.annotation.SdkConstant;
import android.annotation.SuppressLint;
+import android.app.ActivityManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
+import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;
+import android.view.Gravity;
import android.view.InputChannel;
import android.view.InputDevice;
import android.view.InputEvent;
@@ -42,6 +48,7 @@
import android.view.Surface;
import android.view.View;
import android.view.WindowManager;
+import android.widget.FrameLayout;
import com.android.internal.os.SomeArgs;
@@ -56,6 +63,8 @@
private static final boolean DEBUG = false;
private static final String TAG = "TvAdService";
+ private static final int DETACH_MEDIA_VIEW_TIMEOUT_MS = 5000;
+
/**
* Name under which a TvAdService component publishes information about itself. This meta-data
* must reference an XML resource containing an
@@ -151,7 +160,14 @@
private final Context mContext;
final Handler mHandler;
private final WindowManager mWindowManager;
+ private WindowManager.LayoutParams mWindowParams;
private Surface mSurface;
+ private FrameLayout mMediaViewContainer;
+ private View mMediaView;
+ private MediaViewCleanUpTask mMediaViewCleanUpTask;
+ private boolean mMediaViewEnabled;
+ private IBinder mWindowToken;
+ private Rect mMediaFrame;
/**
@@ -166,6 +182,48 @@
}
/**
+ * Enables or disables the media view.
+ *
+ * <p>By default, the media view is disabled. Must be called explicitly after the
+ * session is created to enable the media view.
+ *
+ * <p>The TV AD service can disable its media view when needed.
+ *
+ * @param enable {@code true} if you want to enable the media view. {@code false}
+ * otherwise.
+ * @hide
+ */
+ @CallSuper
+ public void setMediaViewEnabled(final boolean enable) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (enable == mMediaViewEnabled) {
+ return;
+ }
+ mMediaViewEnabled = enable;
+ if (enable) {
+ if (mWindowToken != null) {
+ createMediaView(mWindowToken, mMediaFrame);
+ }
+ } else {
+ removeMediaView(false);
+ }
+ }
+ });
+ }
+
+ /**
+ * Returns {@code true} if media view is enabled, {@code false} otherwise.
+ *
+ * @see #setMediaViewEnabled(boolean)
+ * @hide
+ */
+ public boolean isMediaViewEnabled() {
+ return mMediaViewEnabled;
+ }
+
+ /**
* Releases TvAdService session.
*/
public abstract void onRelease();
@@ -180,6 +238,9 @@
mSessionCallback = null;
mPendingActions.clear();
}
+ // Removes the media view lastly so that any hanging on the main thread can be handled
+ // in {@link #scheduleMediaViewCleanup}.
+ removeMediaView(true);
}
/**
@@ -307,6 +368,33 @@
}
/**
+ * Called when the size of the media view is changed by the application.
+ *
+ * <p>This is always called at least once when the session is created regardless of whether
+ * the media view is enabled or not. The media view container size is the same as the
+ * containing {@link TvAdView}. Note that the size of the underlying surface can
+ * be different if the surface was changed by calling {@link #layoutSurface}.
+ *
+ * @param width The width of the media view, in pixels.
+ * @param height The height of the media view, in pixels.
+ * @hide
+ */
+ public void onMediaViewSizeChanged(@Px int width, @Px int height) {
+ }
+
+ /**
+ * Called when the application requests to create a media view. Each session
+ * implementation can override this method and return its own view.
+ *
+ * @return a view attached to the media window. {@code null} if no media view is created.
+ * @hide
+ */
+ @Nullable
+ public View onCreateMediaView() {
+ return null;
+ }
+
+ /**
* Takes care of dispatching incoming input events and tells whether the event was handled.
*/
int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
@@ -388,6 +476,137 @@
}
}
}
+
+ /**
+ * Creates a media view. This calls {@link #onCreateMediaView} to get a view to attach
+ * to the media window.
+ *
+ * @param windowToken A window token of the application.
+ * @param frame A position of the media view.
+ */
+ void createMediaView(IBinder windowToken, Rect frame) {
+ if (mMediaViewContainer != null) {
+ removeMediaView(false);
+ }
+ if (DEBUG) Log.d(TAG, "create media view(" + frame + ")");
+ mWindowToken = windowToken;
+ mMediaFrame = frame;
+ onMediaViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top);
+ if (!mMediaViewEnabled) {
+ return;
+ }
+ mMediaView = onCreateMediaView();
+ if (mMediaView == null) {
+ return;
+ }
+ if (mMediaViewCleanUpTask != null) {
+ mMediaViewCleanUpTask.cancel(true);
+ mMediaViewCleanUpTask = null;
+ }
+ // Creates a container view to check hanging on the media view detaching.
+ // Adding/removing the media view to/from the container make the view attach/detach
+ // logic run on the main thread.
+ mMediaViewContainer = new FrameLayout(mContext.getApplicationContext());
+ mMediaViewContainer.addView(mMediaView);
+
+ int type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
+ // We make the overlay view non-focusable and non-touchable so that
+ // the application that owns the window token can decide whether to consume or
+ // dispatch the input events.
+ int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+ if (ActivityManager.isHighEndGfx()) {
+ flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+ }
+ mWindowParams = new WindowManager.LayoutParams(
+ frame.right - frame.left, frame.bottom - frame.top,
+ frame.left, frame.top, type, flags, PixelFormat.TRANSPARENT);
+ mWindowParams.privateFlags |=
+ WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+ mWindowParams.gravity = Gravity.START | Gravity.TOP;
+ mWindowParams.token = windowToken;
+ mWindowManager.addView(mMediaViewContainer, mWindowParams);
+ }
+
+ /**
+ * Relayouts the current media view.
+ *
+ * @param frame A new position of the media view.
+ */
+ void relayoutMediaView(Rect frame) {
+ if (DEBUG) Log.d(TAG, "relayoutMediaView(" + frame + ")");
+ if (mMediaFrame == null || mMediaFrame.width() != frame.width()
+ || mMediaFrame.height() != frame.height()) {
+ // Note: relayoutMediaView is called whenever TvAdView's layout is
+ // changed regardless of setMediaViewEnabled.
+ onMediaViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top);
+ }
+ mMediaFrame = frame;
+ if (!mMediaViewEnabled || mMediaViewContainer == null) {
+ return;
+ }
+ mWindowParams.x = frame.left;
+ mWindowParams.y = frame.top;
+ mWindowParams.width = frame.right - frame.left;
+ mWindowParams.height = frame.bottom - frame.top;
+ mWindowManager.updateViewLayout(mMediaViewContainer, mWindowParams);
+ }
+
+ /**
+ * Removes the current media view.
+ */
+ void removeMediaView(boolean clearWindowToken) {
+ if (DEBUG) Log.d(TAG, "removeMediaView(" + mMediaViewContainer + ")");
+ if (clearWindowToken) {
+ mWindowToken = null;
+ mMediaFrame = null;
+ }
+ if (mMediaViewContainer != null) {
+ // Removes the media view from the view hierarchy in advance so that it can be
+ // cleaned up in the {@link MediaViewCleanUpTask} if the remove process is
+ // hanging.
+ mMediaViewContainer.removeView(mMediaView);
+ mMediaView = null;
+ mWindowManager.removeView(mMediaViewContainer);
+ mMediaViewContainer = null;
+ mWindowParams = null;
+ }
+ }
+
+ /**
+ * Schedules a task which checks whether the media view is detached and kills the process
+ * if it is not. Note that this method is expected to be called in a non-main thread.
+ */
+ void scheduleMediaViewCleanup() {
+ View mediaViewParent = mMediaViewContainer;
+ if (mediaViewParent != null) {
+ mMediaViewCleanUpTask = new MediaViewCleanUpTask();
+ mMediaViewCleanUpTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
+ mediaViewParent);
+ }
+ }
+ }
+
+ private static final class MediaViewCleanUpTask extends AsyncTask<View, Void, Void> {
+ @Override
+ protected Void doInBackground(View... views) {
+ View mediaViewParent = views[0];
+ try {
+ Thread.sleep(DETACH_MEDIA_VIEW_TIMEOUT_MS);
+ } catch (InterruptedException e) {
+ return null;
+ }
+ if (isCancelled()) {
+ return null;
+ }
+ if (mediaViewParent.isAttachedToWindow()) {
+ Log.e(TAG, "Time out on releasing media view. Killing "
+ + mediaViewParent.getContext().getPackageName());
+ android.os.Process.killProcess(Process.myPid());
+ }
+ return null;
+ }
}
diff --git a/media/java/android/media/tv/ad/TvAdView.java b/media/java/android/media/tv/ad/TvAdView.java
index 5e67fe9..5e4a70b 100644
--- a/media/java/android/media/tv/ad/TvAdView.java
+++ b/media/java/android/media/tv/ad/TvAdView.java
@@ -22,6 +22,8 @@
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.RectF;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
@@ -64,6 +66,9 @@
private int mSurfaceViewTop;
private int mSurfaceViewBottom;
+ private boolean mMediaViewCreated;
+ private Rect mMediaViewFrame;
+
private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
@@ -121,6 +126,20 @@
mTvAdManager = (TvAdManager) getContext().getSystemService(Context.TV_AD_SERVICE);
}
+ /** @hide */
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ createSessionMediaView();
+ }
+
+ /** @hide */
+ @Override
+ public void onDetachedFromWindow() {
+ removeSessionMediaView();
+ super.onDetachedFromWindow();
+ }
+
@Override
public void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (DEBUG) {
@@ -150,6 +169,11 @@
public void onVisibilityChanged(@NonNull View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
mSurfaceView.setVisibility(visibility);
+ if (visibility == View.VISIBLE) {
+ createSessionMediaView();
+ } else {
+ removeSessionMediaView();
+ }
}
private void resetSurfaceView() {
@@ -162,6 +186,7 @@
@Override
protected void updateSurface() {
super.updateSurface();
+ relayoutSessionMediaView();
}};
// The surface view's content should be treated as secure all the time.
mSurfaceView.setSecure(true);
@@ -174,6 +199,69 @@
addView(mSurfaceView);
}
+ /**
+ * Resets this TvAdView to release its resources.
+ *
+ * <p>It can be reused by call {@link #prepareAdService(String, String)}.
+ * @hide
+ */
+ public void reset() {
+ if (DEBUG) Log.d(TAG, "reset()");
+ resetInternal();
+ }
+
+ private void resetInternal() {
+ mSessionCallback = null;
+ if (mSession != null) {
+ setSessionSurface(null);
+ removeSessionMediaView();
+ mUseRequestedSurfaceLayout = false;
+ mSession.release();
+ mSession = null;
+ resetSurfaceView();
+ }
+ }
+
+ private void createSessionMediaView() {
+ // TODO: handle z-order
+ if (mSession == null || !isAttachedToWindow() || mMediaViewCreated) {
+ return;
+ }
+ mMediaViewFrame = getViewFrameOnScreen();
+ mSession.createMediaView(this, mMediaViewFrame);
+ mMediaViewCreated = true;
+ }
+
+ private void removeSessionMediaView() {
+ if (mSession == null || !mMediaViewCreated) {
+ return;
+ }
+ mSession.removeMediaView();
+ mMediaViewCreated = false;
+ mMediaViewFrame = null;
+ }
+
+ private void relayoutSessionMediaView() {
+ if (mSession == null || !isAttachedToWindow() || !mMediaViewCreated) {
+ return;
+ }
+ Rect viewFrame = getViewFrameOnScreen();
+ if (viewFrame.equals(mMediaViewFrame)) {
+ return;
+ }
+ mSession.relayoutMediaView(viewFrame);
+ mMediaViewFrame = viewFrame;
+ }
+
+ private Rect getViewFrameOnScreen() {
+ Rect frame = new Rect();
+ getGlobalVisibleRect(frame);
+ RectF frameF = new RectF(frame);
+ getMatrix().mapRect(frameF);
+ frameF.round(frame);
+ return frame;
+ }
+
private void setSessionSurface(Surface surface) {
if (mSession == null) {
return;
@@ -185,7 +273,7 @@
if (mSession == null) {
return;
}
- //mSession.dispatchSurfaceChanged(format, width, height);
+ mSession.dispatchSurfaceChanged(format, width, height);
}
/**
@@ -246,6 +334,7 @@
dispatchSurfaceChanged(mSurfaceFormat, mSurfaceWidth, mSurfaceHeight);
}
}
+ createSessionMediaView();
} else {
// Failed to create
// Todo: forward error to Tv App
@@ -262,6 +351,8 @@
Log.w(TAG, "onSessionReleased - session not created");
return;
}
+ mMediaViewCreated = false;
+ mMediaViewFrame = null;
mSessionCallback = null;
mSession = null;
}
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
index 4316d05..0f58b29 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
@@ -88,6 +88,7 @@
void notifyTracksChanged(in IBinder sessionToken, in List<TvTrackInfo> tracks, int userId);
void notifyVideoAvailable(in IBinder sessionToken, int userId);
void notifyVideoUnavailable(in IBinder sessionToken, int reason, int userId);
+ void notifyVideoFreezeUpdated(in IBinder sessionToken, boolean isFrozen, int userId);
void notifyContentAllowed(in IBinder sessionToken, int userId);
void notifyContentBlocked(in IBinder sessionToken, in String rating, int userId);
void notifySignalStrength(in IBinder sessionToken, int stength, int userId);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
index ba7cf13..06808c9 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
@@ -67,6 +67,7 @@
void notifyTracksChanged(in List<TvTrackInfo> tracks);
void notifyVideoAvailable();
void notifyVideoUnavailable(int reason);
+ void notifyVideoFreezeUpdated(boolean isFrozen);
void notifyContentAllowed();
void notifyContentBlocked(in String rating);
void notifySignalStrength(int strength);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
index 518b08a..77730aa 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
@@ -103,6 +103,7 @@
private static final int DO_SEND_TIME_SHIFT_MODE = 46;
private static final int DO_SEND_AVAILABLE_SPEEDS = 47;
private static final int DO_SEND_SELECTED_TRACK_INFO = 48;
+ private static final int DO_NOTIFY_VIDEO_FREEZE_UPDATED = 49;
private final HandlerCaller mCaller;
private Session mSessionImpl;
@@ -364,6 +365,10 @@
args.recycle();
break;
}
+ case DO_NOTIFY_VIDEO_FREEZE_UPDATED: {
+ mSessionImpl.notifyVideoFreezeUpdated((Boolean) msg.obj);
+ break;
+ }
default: {
Log.w(TAG, "Unhandled message code: " + msg.what);
break;
@@ -552,6 +557,12 @@
}
@Override
+ public void notifyVideoFreezeUpdated(boolean isFrozen) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_NOTIFY_VIDEO_FREEZE_UPDATED,
+ isFrozen));
+ }
+
+ @Override
public void notifyContentAllowed() {
mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_NOTIFY_CONTENT_ALLOWED));
}
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
index bf4379f..8a340f6 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
@@ -1731,6 +1731,22 @@
}
/**
+ * Notifies Interactive app session when the video freeze state is updated
+ * @param isFrozen Whether or not the video is frozen
+ */
+ public void notifyVideoFreezeUpdated(boolean isFrozen) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.notifyVideoFreezeUpdated(mToken, isFrozen, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Notifies Interactive APP session when content is allowed.
*/
public void notifyContentAllowed() {
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 7936403..5247a0e 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -882,6 +882,15 @@
}
/**
+ * Called when video becomes frozen or unfrozen. Audio playback will continue while
+ * video will be frozen to the last frame if {@code true}.
+ * @param isFrozen Whether or not the video is frozen.
+ * @hide
+ */
+ public void onVideoFreezeUpdated(boolean isFrozen) {
+ }
+
+ /**
* Called when content is allowed.
*/
public void onContentAllowed() {
@@ -1770,6 +1779,13 @@
onVideoUnavailable(reason);
}
+ void notifyVideoFreezeUpdated(boolean isFrozen) {
+ if (DEBUG) {
+ Log.d(TAG, "notifyVideoFreezeUpdated (isFrozen=" + isFrozen + ")");
+ }
+ onVideoFreezeUpdated(isFrozen);
+ }
+
void notifyContentAllowed() {
if (DEBUG) {
Log.d(TAG, "notifyContentAllowed");
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 40a12e4..5bb61c2 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -719,6 +719,22 @@
}
/**
+ * Alerts the TV Interactive app that the video freeze state has been updated.
+ * If {@code true}, the video is frozen on the last frame while audio playback continues.
+ *
+ * @param isFrozen Whether the video is frozen.
+ * @hide
+ */
+ public void notifyVideoFreezeUpdated(boolean isFrozen) {
+ if (DEBUG) {
+ Log.d(TAG, "notifyVideoFreezeUpdated");
+ }
+ if (mSession != null) {
+ mSession.notifyVideoFreezeUpdated(isFrozen);
+ }
+ }
+
+ /**
* Sends signing result to related TV interactive app.
*
* <p>This is used when the corresponding server of the broadcast-independent interactive
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index f0b941a..86279ef 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -18,6 +18,7 @@
package com.android.systemui.communal.domain.interactor
import android.app.smartspace.SmartspaceTarget
+import android.content.pm.UserInfo
import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
import android.widget.RemoteViews
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -46,6 +47,8 @@
import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -57,8 +60,10 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
/**
* This class of test cases assume that communal is enabled. For disabled cases, see
@@ -68,6 +73,9 @@
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
class CommunalInteractorTest : SysuiTestCase() {
+ @Mock private lateinit var mainUser: UserInfo
+ @Mock private lateinit var secondaryUser: UserInfo
+
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
@@ -76,6 +84,7 @@
private lateinit var mediaRepository: FakeCommunalMediaRepository
private lateinit var widgetRepository: FakeCommunalWidgetRepository
private lateinit var smartspaceRepository: FakeSmartspaceRepository
+ private lateinit var userRepository: FakeUserRepository
private lateinit var keyguardRepository: FakeKeyguardRepository
private lateinit var communalPrefsRepository: FakeCommunalPrefsRepository
private lateinit var editWidgetsActivityStarter: EditWidgetsActivityStarter
@@ -84,15 +93,22 @@
@Before
fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
tutorialRepository = kosmos.fakeCommunalTutorialRepository
communalRepository = kosmos.fakeCommunalRepository
mediaRepository = kosmos.fakeCommunalMediaRepository
widgetRepository = kosmos.fakeCommunalWidgetRepository
smartspaceRepository = kosmos.fakeSmartspaceRepository
+ userRepository = kosmos.fakeUserRepository
keyguardRepository = kosmos.fakeKeyguardRepository
editWidgetsActivityStarter = kosmos.editWidgetsActivityStarter
communalPrefsRepository = kosmos.fakeCommunalPrefsRepository
+ whenever(mainUser.isMain).thenReturn(true)
+ whenever(secondaryUser.isMain).thenReturn(false)
+ userRepository.setUserInfos(listOf(mainUser, secondaryUser))
+
underTest = kosmos.communalInteractor
}
@@ -101,36 +117,52 @@
testScope.runTest { assertThat(underTest.isCommunalEnabled).isTrue() }
@Test
- fun isCommunalAvailable_trueWhenStorageUnlock() =
+ fun isCommunalAvailable_storageUnlockedAndMainUser_true() =
testScope.runTest {
val isAvailable by collectLastValue(underTest.isCommunalAvailable)
assertThat(isAvailable).isFalse()
keyguardRepository.setIsEncryptedOrLockdown(false)
+ userRepository.setSelectedUserInfo(mainUser)
runCurrent()
assertThat(isAvailable).isTrue()
}
@Test
- fun isCommunalAvailable_whenStorageUnlock_true() =
+ fun isCommunalAvailable_storageLockedAndMainUser_false() =
+ testScope.runTest {
+ val isAvailable by collectLastValue(underTest.isCommunalAvailable)
+ assertThat(isAvailable).isFalse()
+
+ keyguardRepository.setIsEncryptedOrLockdown(true)
+ userRepository.setSelectedUserInfo(mainUser)
+ runCurrent()
+
+ assertThat(isAvailable).isFalse()
+ }
+
+ @Test
+ fun isCommunalAvailable_storageUnlockedAndSecondaryUser_false() =
testScope.runTest {
val isAvailable by collectLastValue(underTest.isCommunalAvailable)
assertThat(isAvailable).isFalse()
keyguardRepository.setIsEncryptedOrLockdown(false)
+ userRepository.setSelectedUserInfo(secondaryUser)
runCurrent()
- assertThat(isAvailable).isTrue()
+ assertThat(isAvailable).isFalse()
}
@Test
- fun updateAppWidgetHostActive_uponStorageUnlock_true() =
+ fun updateAppWidgetHostActive_uponStorageUnlockAsMainUser_true() =
testScope.runTest {
collectLastValue(underTest.isCommunalAvailable)
assertThat(widgetRepository.isHostActive()).isFalse()
keyguardRepository.setIsEncryptedOrLockdown(false)
+ userRepository.setSelectedUserInfo(mainUser)
runCurrent()
assertThat(widgetRepository.isHostActive()).isTrue()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
index 161356d..352463f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.communal.domain.interactor
+import android.content.pm.UserInfo
import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED
import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_STARTED
@@ -30,9 +31,9 @@
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.kosmos.testScope
-import com.android.systemui.settings.UserTracker
import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
@@ -45,15 +46,17 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class CommunalTutorialInteractorTest : SysuiTestCase() {
+ @Mock lateinit var user: UserInfo
+
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- @Mock private lateinit var userTracker: UserTracker
-
private lateinit var underTest: CommunalTutorialInteractor
private lateinit var keyguardRepository: FakeKeyguardRepository
private lateinit var communalTutorialRepository: FakeCommunalTutorialRepository
private lateinit var communalRepository: FakeCommunalRepository
+ private lateinit var communalInteractor: CommunalInteractor
+ private lateinit var userRepository: FakeUserRepository
@Before
fun setUp() {
@@ -62,16 +65,17 @@
keyguardRepository = kosmos.fakeKeyguardRepository
communalTutorialRepository = kosmos.fakeCommunalTutorialRepository
communalRepository = kosmos.fakeCommunalRepository
+ communalInteractor = kosmos.communalInteractor
+ userRepository = kosmos.fakeUserRepository
underTest = kosmos.communalTutorialInteractor
-
- whenever(userTracker.userHandle).thenReturn(mock())
}
@Test
fun tutorialUnavailable_whenKeyguardNotVisible() =
testScope.runTest {
val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable)
+ setCommunalAvailable(true)
communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
keyguardRepository.setKeyguardShowing(false)
assertThat(isTutorialAvailable).isFalse()
@@ -81,6 +85,7 @@
fun tutorialUnavailable_whenTutorialIsCompleted() =
testScope.runTest {
val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable)
+ setCommunalAvailable(true)
keyguardRepository.setKeyguardShowing(true)
keyguardRepository.setKeyguardOccluded(false)
communalRepository.setIsCommunalHubShowing(false)
@@ -89,9 +94,20 @@
}
@Test
+ fun tutorialUnavailable_whenCommunalNotAvailable() =
+ testScope.runTest {
+ val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable)
+ setCommunalAvailable(false)
+ communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
+ keyguardRepository.setKeyguardShowing(true)
+ assertThat(isTutorialAvailable).isFalse()
+ }
+
+ @Test
fun tutorialAvailable_whenTutorialNotStarted() =
testScope.runTest {
val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable)
+ setCommunalAvailable(true)
keyguardRepository.setKeyguardShowing(true)
keyguardRepository.setKeyguardOccluded(false)
communalRepository.setIsCommunalHubShowing(false)
@@ -103,6 +119,7 @@
fun tutorialAvailable_whenTutorialIsStarted() =
testScope.runTest {
val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable)
+ setCommunalAvailable(true)
keyguardRepository.setKeyguardShowing(true)
keyguardRepository.setKeyguardOccluded(false)
communalRepository.setIsCommunalHubShowing(true)
@@ -183,4 +200,16 @@
assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
}
+
+ private suspend fun setCommunalAvailable(available: Boolean) {
+ if (available) {
+ communalRepository.setIsCommunalEnabled(true)
+ keyguardRepository.setIsEncryptedOrLockdown(false)
+ whenever(user.isMain).thenReturn(true)
+ userRepository.setUserInfos(listOf(user))
+ userRepository.setSelectedUserInfo(user)
+ } else {
+ keyguardRepository.setIsEncryptedOrLockdown(true)
+ }
+ }
}
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 033dc6d..c814f3f 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
@@ -17,6 +17,7 @@
package com.android.systemui.communal.view.viewmodel
import android.app.smartspace.SmartspaceTarget
+import android.content.pm.UserInfo
import android.provider.Settings
import android.widget.RemoteViews
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -43,6 +44,8 @@
import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -62,6 +65,7 @@
@RunWith(AndroidJUnit4::class)
class CommunalViewModelTest : SysuiTestCase() {
@Mock private lateinit var mediaHost: MediaHost
+ @Mock private lateinit var user: UserInfo
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
@@ -71,6 +75,7 @@
private lateinit var widgetRepository: FakeCommunalWidgetRepository
private lateinit var smartspaceRepository: FakeSmartspaceRepository
private lateinit var mediaRepository: FakeCommunalMediaRepository
+ private lateinit var userRepository: FakeUserRepository
private lateinit var underTest: CommunalViewModel
@@ -83,6 +88,7 @@
widgetRepository = kosmos.fakeCommunalWidgetRepository
smartspaceRepository = kosmos.fakeSmartspaceRepository
mediaRepository = kosmos.fakeCommunalMediaRepository
+ userRepository = kosmos.fakeUserRepository
underTest =
CommunalViewModel(
@@ -103,9 +109,11 @@
@Test
fun tutorial_tutorialNotCompletedAndKeyguardVisible_showTutorialContent() =
testScope.runTest {
- // Keyguard showing, and tutorial not started.
+ // Keyguard showing, storage unlocked, main user, and tutorial not started.
keyguardRepository.setKeyguardShowing(true)
keyguardRepository.setKeyguardOccluded(false)
+ keyguardRepository.setIsEncryptedOrLockdown(false)
+ setIsMainUser(true)
tutorialRepository.setTutorialSettingState(
Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED
)
@@ -201,4 +209,10 @@
underTest.onHidePopupAfterDismissCta()
assertThat(isPopupOnDismissCtaShowing).isEqualTo(false)
}
+
+ private suspend fun setIsMainUser(isMainUser: Boolean) {
+ whenever(user.isMain).thenReturn(isMainUser)
+ userRepository.setUserInfos(listOf(user))
+ userRepository.setSelectedUserInfo(user)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index cceb767..6cc680b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -25,10 +25,14 @@
import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION
import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.communalRepository
+import com.android.systemui.communal.shared.model.CommunalSceneKey
+import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -43,6 +47,7 @@
import com.android.systemui.util.ui.value
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -55,7 +60,9 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val keyguardInteractor = kosmos.keyguardInteractor
private val keyguardRepository = kosmos.fakeKeyguardRepository
+ private val communalRepository = kosmos.communalRepository
private val screenOffAnimationController = kosmos.screenOffAnimationController
private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
private val notificationsKeyguardInteractor = kosmos.notificationsKeyguardInteractor
@@ -220,7 +227,25 @@
}
@Test
- fun alpha_glanceableHubOpen_isZero() =
+ fun alpha_idleOnHub_isZero() =
+ testScope.runTest {
+ val alpha by collectLastValue(underTest.alpha)
+
+ // Hub transition state is idle with hub open.
+ communalRepository.setTransitionState(
+ flowOf(ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal))
+ )
+ runCurrent()
+
+ // Set keyguard alpha to 1.0f.
+ keyguardInteractor.setAlpha(1.0f)
+
+ // Alpha property remains 0 regardless.
+ assertThat(alpha).isEqualTo(0f)
+ }
+
+ @Test
+ fun alpha_transitionToHub_isZero() =
testScope.runTest {
val alpha by collectLastValue(underTest.alpha)
@@ -234,7 +259,7 @@
}
@Test
- fun alpha_glanceableHubClosed_isOne() =
+ fun alpha_transitionFromHubToLockscreen_isOne() =
testScope.runTest {
val alpha by collectLastValue(underTest.alpha)
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index c630a7f..e8201ecb 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1728,6 +1728,10 @@
<dimen name="communal_grid_height">630dp</dimen>
<!-- Number of columns for each communal card -->
<integer name="communal_grid_columns_per_card">6</integer>
+ <!-- Width of area on right edge of screen in which swipes will open the communal hub -->
+ <dimen name="communal_right_edge_swipe_region_width">16dp</dimen>
+ <!-- Height of area at top of communal hub where swipes should open the notification shade -->
+ <dimen name="communal_top_edge_swipe_region_height">32dp</dimen>
<dimen name="drag_and_drop_icon_size">70dp</dimen>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index 7088829..d191a3c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -20,7 +20,6 @@
import android.graphics.Region;
import android.os.Bundle;
import android.view.MotionEvent;
-import android.view.SurfaceControl;
import com.android.systemui.shared.recents.ISystemUiProxy;
// Next ID: 29
@@ -99,11 +98,6 @@
void enterStageSplitFromRunningApp(boolean leftOrTop) = 25;
/**
- * Sent when the surface for navigation bar is created or changed
- */
- void onNavigationBarSurface(in SurfaceControl surface) = 26;
-
- /**
* Sent when the task bar stash state is toggled.
*/
void onTaskbarToggled() = 27;
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 92d01db..44b0383 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -36,6 +36,7 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.smartspace.data.repository.SmartspaceRepository
+import com.android.systemui.user.data.repository.UserRepository
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -62,6 +63,7 @@
private val communalPrefsRepository: CommunalPrefsRepository,
mediaRepository: CommunalMediaRepository,
smartspaceRepository: SmartspaceRepository,
+ userRepository: UserRepository,
keyguardInteractor: KeyguardInteractor,
private val appWidgetHost: CommunalAppWidgetHost,
private val editWidgetsActivityStarter: EditWidgetsActivityStarter
@@ -75,7 +77,14 @@
val isCommunalAvailable: StateFlow<Boolean> =
flowOf(isCommunalEnabled)
.flatMapLatest { enabled ->
- if (enabled) keyguardInteractor.isEncryptedOrLockdown.map { !it } else flowOf(false)
+ if (enabled)
+ combine(
+ keyguardInteractor.isEncryptedOrLockdown,
+ userRepository.selectedUserInfo,
+ ) { isEncryptedOrLockdown, selectedUserInfo ->
+ !isEncryptedOrLockdown && selectedUserInfo.isMain
+ }
+ else flowOf(false)
}
.distinctUntilChanged()
.onEach { available -> widgetRepository.updateAppWidgetHostActive(available) }
@@ -263,6 +272,12 @@
companion object {
/**
+ * The user activity timeout which should be used when the communal hub is opened. A value
+ * of -1 means that the user's chosen screen timeout will be used instead.
+ */
+ const val AWAKE_INTERVAL_MS = -1
+
+ /**
* Calculates the content size dynamically based on the total number of contents of that
* type.
*
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
index 5ca89f2..4e5be9b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
@@ -45,14 +45,17 @@
private val communalTutorialRepository: CommunalTutorialRepository,
keyguardInteractor: KeyguardInteractor,
private val communalRepository: CommunalRepository,
+ communalInteractor: CommunalInteractor,
) {
/** An observable for whether the tutorial is available. */
val isTutorialAvailable: Flow<Boolean> =
combine(
+ communalInteractor.isCommunalAvailable,
keyguardInteractor.isKeyguardVisible,
communalTutorialRepository.tutorialSettingState,
- ) { isKeyguardVisible, tutorialSettingState ->
- isKeyguardVisible &&
+ ) { isCommunalAvailable, isKeyguardVisible, tutorialSettingState ->
+ isCommunalAvailable &&
+ isKeyguardVisible &&
tutorialSettingState != Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
}
.distinctUntilChanged()
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 3292ea8..4df5d50 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
@@ -95,6 +95,7 @@
companion object {
const val TAG = "FromGlanceableHubTransitionInteractor"
- val DEFAULT_DURATION = 500.milliseconds
+ val DEFAULT_DURATION = 400.milliseconds
+ val TO_LOCKSCREEN_DURATION = DEFAULT_DURATION
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
index bc51821..6aa2eca 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
@@ -17,7 +17,7 @@
package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.FromGlanceableHubTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import javax.inject.Inject
@@ -37,7 +37,7 @@
) {
private val transitionAnimation =
animationFlow.setup(
- duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION,
+ duration = FromGlanceableHubTransitionInteractor.TO_LOCKSCREEN_DURATION,
from = KeyguardState.GLANCEABLE_HUB,
to = KeyguardState.LOCKSCREEN,
)
@@ -45,10 +45,20 @@
// TODO(b/315205222): implement full animation spec instead of just a simple fade.
val keyguardAlpha: Flow<Float> =
transitionAnimation.sharedFlow(
- duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION,
+ duration = FromGlanceableHubTransitionInteractor.TO_LOCKSCREEN_DURATION,
onStep = { it },
onFinish = { 1f },
onCancel = { 0f },
name = "GLANCEABLE_HUB->LOCKSCREEN: keyguardAlpha",
)
+
+ // TODO(b/315205216): implement full animation spec instead of just a simple fade.
+ val notificationAlpha: Flow<Float> =
+ transitionAnimation.sharedFlow(
+ duration = FromGlanceableHubTransitionInteractor.TO_LOCKSCREEN_DURATION,
+ onStep = { it },
+ onFinish = { 1f },
+ onCancel = { 0f },
+ name = "GLANCEABLE_HUB->LOCKSCREEN: notificationAlpha",
+ )
}
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 ea66702..709e184 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
@@ -21,6 +21,7 @@
import android.view.View.VISIBLE
import com.android.systemui.Flags.newAodTransition
import com.android.systemui.common.shared.model.NotificationContainerBounds
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -56,6 +57,7 @@
private val deviceEntryInteractor: DeviceEntryInteractor,
private val dozeParameters: DozeParameters,
private val keyguardInteractor: KeyguardInteractor,
+ communalInteractor: CommunalInteractor,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
@@ -88,11 +90,23 @@
/** An observable for the alpha level for the entire keyguard root view. */
val alpha: Flow<Float> =
- merge(
- aodAlphaViewModel.alpha,
- lockscreenToGlanceableHubTransitionViewModel.keyguardAlpha,
- glanceableHubToLockscreenTransitionViewModel.keyguardAlpha,
- )
+ combine(
+ communalInteractor.isIdleOnCommunal,
+ merge(
+ aodAlphaViewModel.alpha,
+ lockscreenToGlanceableHubTransitionViewModel.keyguardAlpha,
+ glanceableHubToLockscreenTransitionViewModel.keyguardAlpha,
+ )
+ ) { isIdleOnCommunal, alpha ->
+ if (isIdleOnCommunal) {
+ // Keyguard should not show while the communal hub is fully visible. This check
+ // is added since at the moment, closing the notification shade will cause the
+ // keyguard alpha to be set back to 1.
+ 0f
+ } else {
+ alpha
+ }
+ }
.distinctUntilChanged()
/** Specific alpha value for elements visible during [KeyguardState.LOCKSCREEN] */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
index 3ea83ae..3afa49e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
@@ -51,4 +51,14 @@
onCancel = { 1f },
name = "LOCKSCREEN->GLANCEABLE_HUB: keyguardAlpha",
)
+
+ // TODO(b/315205216): implement full animation spec instead of just a simple fade.
+ val notificationAlpha: Flow<Float> =
+ transitionAnimation.sharedFlow(
+ duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION,
+ onStep = { 1f - it },
+ onFinish = { 0f },
+ onCancel = { 1f },
+ name = "LOCKSCREEN->GLANCEABLE_HUB: notificationAlpha",
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 0a72a2f..068e5fd 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -504,24 +504,6 @@
}
};
- private final ViewRootImpl.SurfaceChangedCallback mSurfaceChangedCallback =
- new SurfaceChangedCallback() {
- @Override
- public void surfaceCreated(Transaction t) {
- notifyNavigationBarSurface();
- }
-
- @Override
- public void surfaceDestroyed() {
- notifyNavigationBarSurface();
- }
-
- @Override
- public void surfaceReplaced(Transaction t) {
- notifyNavigationBarSurface();
- }
- };
-
private boolean mScreenPinningActive = false;
private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
@Override
@@ -787,8 +769,6 @@
mView.getViewTreeObserver().addOnComputeInternalInsetsListener(
mOnComputeInternalInsetsListener);
- mView.getViewRootImpl().addSurfaceChangedCallback(mSurfaceChangedCallback);
- notifyNavigationBarSurface();
mPipOptional.ifPresent(mView::addPipExclusionBoundsChangeListener);
mBackAnimation.ifPresent(mView::registerBackAnimation);
@@ -860,13 +840,8 @@
mHandler.removeCallbacks(mEnableLayoutTransitions);
mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
mPipOptional.ifPresent(mView::removePipExclusionBoundsChangeListener);
- ViewRootImpl viewRoot = mView.getViewRootImpl();
- if (viewRoot != null) {
- viewRoot.removeSurfaceChangedCallback(mSurfaceChangedCallback);
- }
mFrame = null;
mOrientationHandle = null;
- notifyNavigationBarSurface();
}
// TODO: Remove this when we update nav bar recreation
@@ -1026,17 +1001,6 @@
}
}
- private void notifyNavigationBarSurface() {
- ViewRootImpl viewRoot = mView.getViewRootImpl();
- SurfaceControl surface = mView.getParent() != null
- && viewRoot != null
- && viewRoot.getSurfaceControl() != null
- && viewRoot.getSurfaceControl().isValid()
- ? viewRoot.getSurfaceControl()
- : null;
- mOverviewProxyService.onNavigationBarSurfaceChanged(surface);
- }
-
private int deltaRotation(int oldRotation, int newRotation) {
int delta = newRotation - oldRotation;
if (delta < 0) delta += 4;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index fd53423..cc53aab 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -169,7 +169,6 @@
private final DisplayTracker mDisplayTracker;
private Region mActiveNavBarRegion;
- private SurfaceControl mNavigationBarSurface;
private IOverviewProxy mOverviewProxy;
private int mConnectionBackoffAttempts;
@@ -488,7 +487,6 @@
Log.e(TAG_OPS, "Failed to call onInitialize()", e);
}
dispatchNavButtonBounds();
- dispatchNavigationBarSurface();
// Force-update the systemui state flags
updateSystemUiStateFlags();
@@ -679,28 +677,6 @@
.commitUpdate(mContext.getDisplayId());
}
- /**
- * Called when the navigation bar surface is created or changed
- */
- public void onNavigationBarSurfaceChanged(SurfaceControl navbarSurface) {
- mNavigationBarSurface = navbarSurface;
- dispatchNavigationBarSurface();
- }
-
- private void dispatchNavigationBarSurface() {
- try {
- if (mOverviewProxy != null) {
- // Catch all for cases where the surface is no longer valid
- if (mNavigationBarSurface != null && !mNavigationBarSurface.isValid()) {
- mNavigationBarSurface = null;
- }
- mOverviewProxy.onNavigationBarSurface(mNavigationBarSurface);
- }
- } catch (RemoteException e) {
- Log.e(TAG_OPS, "Failed to notify back action", e);
- }
- }
-
private void updateEnabledAndBinding() {
updateEnabledState();
startConnectionToCurrentUser();
@@ -1075,7 +1051,6 @@
pw.print(" mInputFocusTransferStartY="); pw.println(mInputFocusTransferStartY);
pw.print(" mInputFocusTransferStartMillis="); pw.println(mInputFocusTransferStartMillis);
pw.print(" mActiveNavBarRegion="); pw.println(mActiveNavBarRegion);
- pw.print(" mNavigationBarSurface="); pw.println(mNavigationBarSurface);
pw.print(" mNavBarMode="); pw.println(mNavBarMode);
mSysUiState.dump(pw, args);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index 782d651..3362ebc 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -55,7 +55,15 @@
* The width of the area in which a right edge swipe can open the hub, in pixels. Read from
* resources when [initView] is called.
*/
- private var edgeSwipeRegionWidth: Int = 0
+ // TODO(b/320786721): support RTL layouts
+ private var rightEdgeSwipeRegionWidth: Int = 0
+
+ /**
+ * The height of the area in which a top edge swipe while the hub is open will not intercept
+ * touches, in pixels. This allows the top edge swipe to instead open the notification shade.
+ * Read from resources when [initView] is called.
+ */
+ private var topEdgeSwipeRegionWidth: Int = 0
/**
* True if we are currently tracking a gesture for opening the hub that started in the edge
@@ -63,6 +71,9 @@
*/
private var isTrackingOpenGesture = false
+ /** True if we are currently tracking a touch on the hub while it's open. */
+ private var isTrackingHubTouch = false
+
/**
* True if the hub UI is fully open, meaning it should receive touch input.
*
@@ -113,8 +124,14 @@
communalContainerView = containerView
- edgeSwipeRegionWidth =
- communalContainerView.resources.getDimensionPixelSize(R.dimen.communal_grid_gutter_size)
+ rightEdgeSwipeRegionWidth =
+ communalContainerView.resources.getDimensionPixelSize(
+ R.dimen.communal_right_edge_swipe_region_width
+ )
+ topEdgeSwipeRegionWidth =
+ communalContainerView.resources.getDimensionPixelSize(
+ R.dimen.communal_top_edge_swipe_region_height
+ )
collectFlow(
communalContainerView,
@@ -157,17 +174,38 @@
// fully showing state
val hubOccluded = anyBouncerShowing || shadeShowing
- // If the hub is fully visible, send all touch events to it.
- val communalVisible = hubShowing && !hubOccluded
- if (communalVisible) {
+ // If the hub is fully visible, send all touch events to it, other than top and bottom edge
+ // swipes.
+ if (hubShowing && isDown) {
+ val y = ev.rawY
+ val topSwipe: Boolean = y <= topEdgeSwipeRegionWidth
+
+ // TODO(b/315207481): allow bottom edge swipes to open the bouncer
+ if (topSwipe) {
+ // Don't intercept touches at the top edge so that swipes can open the notification
+ // shade.
+ return false
+ }
+
+ if (!hubOccluded) {
+ isTrackingHubTouch = true
+ dispatchTouchEvent(ev)
+ // Return true regardless of dispatch result as some touches at the start of a
+ // gesture may return false from dispatchTouchEvent.
+ return true
+ }
+ } else if (isTrackingHubTouch) {
+ if (isUp || isCancel) {
+ isTrackingHubTouch = false
+ }
dispatchTouchEvent(ev)
// Return true regardless of dispatch result as some touches at the start of a gesture
// may return false from dispatchTouchEvent.
return true
}
- if (edgeSwipeRegionWidth == 0) {
- // If the edge region width has not been read yet or whatever reason, don't bother
+ if (rightEdgeSwipeRegionWidth == 0) {
+ // If the edge region width has not been read yet for whatever reason, don't bother
// intercepting touches to open the hub.
return false
}
@@ -175,7 +213,7 @@
if (!isTrackingOpenGesture && isDown) {
val x = ev.rawX
val inOpeningSwipeRegion: Boolean =
- x >= communalContainerView.width - edgeSwipeRegionWidth
+ x >= communalContainerView.width - rightEdgeSwipeRegionWidth
if (inOpeningSwipeRegion && !hubOccluded) {
isTrackingOpenGesture = true
dispatchTouchEvent(ev)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 60feb82..0053474 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -50,6 +50,7 @@
import com.android.systemui.Dumpable;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.communal.domain.interactor.CommunalInteractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -118,6 +119,7 @@
private final Lazy<SelectedUserInteractor> mUserInteractor;
private final Lazy<ShadeInteractor> mShadeInteractorLazy;
private final SceneContainerFlags mSceneContainerFlags;
+ private final Lazy<CommunalInteractor> mCommunalInteractor;
private ViewGroup mWindowRootView;
private LayoutParams mLp;
private boolean mHasTopUi;
@@ -165,7 +167,8 @@
ShadeWindowLogger logger,
Lazy<SelectedUserInteractor> userInteractor,
UserTracker userTracker,
- SceneContainerFlags sceneContainerFlags) {
+ SceneContainerFlags sceneContainerFlags,
+ Lazy<CommunalInteractor> communalInteractor) {
mContext = context;
mWindowRootViewComponentFactory = windowRootViewComponentFactory;
mWindowManager = windowManager;
@@ -184,6 +187,7 @@
mAuthController = authController;
mUserInteractor = userInteractor;
mSceneContainerFlags = sceneContainerFlags;
+ mCommunalInteractor = communalInteractor;
mLastKeyguardRotationAllowed = mKeyguardStateController.isKeyguardScreenRotationAllowed();
mLockScreenDisplayTimeout = context.getResources()
.getInteger(R.integer.config_lockScreenDisplayTimeout);
@@ -325,6 +329,11 @@
mShadeInteractorLazy.get().isQsExpanded(),
this::onQsExpansionChanged
);
+ collectFlow(
+ mWindowRootView,
+ mCommunalInteractor.get().isCommunalShowing(),
+ this::onCommunalShowingChanged
+ );
}
@Override
@@ -501,14 +510,21 @@
}
private void applyUserActivityTimeout(NotificationShadeWindowState state) {
- if (state.isKeyguardShowingAndNotOccluded()
+ final Boolean communalShowing = state.isCommunalShowingAndNotOccluded();
+ final Boolean keyguardShowing = state.isKeyguardShowingAndNotOccluded();
+ long timeout = -1;
+ if ((communalShowing || keyguardShowing)
&& state.statusBarState == StatusBarState.KEYGUARD
&& !state.qsExpanded) {
- mLpChanged.userActivityTimeout = state.bouncerShowing
- ? KeyguardViewMediator.AWAKE_INTERVAL_BOUNCER_MS : mLockScreenDisplayTimeout;
- } else {
- mLpChanged.userActivityTimeout = -1;
+ if (state.bouncerShowing) {
+ timeout = KeyguardViewMediator.AWAKE_INTERVAL_BOUNCER_MS;
+ } else if (communalShowing) {
+ timeout = CommunalInteractor.AWAKE_INTERVAL_MS;
+ } else if (keyguardShowing) {
+ timeout = mLockScreenDisplayTimeout;
+ }
}
+ mLpChanged.userActivityTimeout = timeout;
}
private void applyInputFeatures(NotificationShadeWindowState state) {
@@ -607,7 +623,8 @@
state.forcePluginOpen,
state.dozing,
state.scrimsVisibility,
- state.backgroundBlurRadius
+ state.backgroundBlurRadius,
+ state.communalShowing
);
}
@@ -731,6 +748,12 @@
apply(mCurrentState);
}
+ @VisibleForTesting
+ void onCommunalShowingChanged(Boolean showing) {
+ mCurrentState.communalShowing = showing;
+ apply(mCurrentState);
+ }
+
@Override
public void setForceUserActivity(boolean forceUserActivity) {
mCurrentState.forceUserActivity = forceUserActivity;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
index 0b20170..f9c9d83 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
@@ -58,12 +58,17 @@
@JvmField var dreaming: Boolean = false,
@JvmField var scrimsVisibility: Int = 0,
@JvmField var backgroundBlurRadius: Int = 0,
+ @JvmField var communalShowing: Boolean = false,
) {
fun isKeyguardShowingAndNotOccluded(): Boolean {
return keyguardShowing && !keyguardOccluded
}
+ fun isCommunalShowingAndNotOccluded(): Boolean {
+ return communalShowing && !keyguardOccluded
+ }
+
/** List of [String] to be used as a [Row] with [DumpsysTableLogger]. */
val asStringList: List<String> by lazy {
listOf(
@@ -93,7 +98,8 @@
forcePluginOpen.toString(),
dozing.toString(),
scrimsVisibility.toString(),
- backgroundBlurRadius.toString()
+ backgroundBlurRadius.toString(),
+ communalShowing.toString(),
)
}
@@ -134,6 +140,7 @@
dozing: Boolean,
scrimsVisibility: Int,
backgroundBlurRadius: Int,
+ communalShowing: Boolean,
) {
buffer.advance().apply {
this.keyguardShowing = keyguardShowing
@@ -165,6 +172,7 @@
this.dozing = dozing
this.scrimsVisibility = scrimsVisibility
this.backgroundBlurRadius = backgroundBlurRadius
+ this.communalShowing = communalShowing
}
}
@@ -209,7 +217,8 @@
"forcePluginOpen",
"dozing",
"scrimsVisibility",
- "backgroundBlurRadius"
+ "backgroundBlurRadius",
+ "communalShowing"
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 9a8cc0a..1143481 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -355,6 +355,12 @@
private float mMaxAlphaForExpansion = 1.0f;
private float mMaxAlphaForUnhide = 1.0f;
+ /**
+ * Maximum alpha when to and from or sitting idle on the glanceable hub. Will be 1.0f when the
+ * hub is not visible or transitioning.
+ */
+ private float mMaxAlphaForGlanceableHub = 1.0f;
+
private final NotificationListViewBinder mViewBinder;
private void updateResources() {
@@ -1285,9 +1291,19 @@
updateAlpha();
}
+ /**
+ * Sets the max alpha value for notifications when idle on the glanceable hub or when
+ * transitioning to/from the glanceable hub.
+ */
+ public void setMaxAlphaForGlanceableHub(float alpha) {
+ mMaxAlphaForGlanceableHub = alpha;
+ updateAlpha();
+ }
+
private void updateAlpha() {
if (mView != null) {
- mView.setAlpha(Math.min(mMaxAlphaForExpansion, mMaxAlphaForUnhide));
+ mView.setAlpha(Math.min(mMaxAlphaForExpansion,
+ Math.min(mMaxAlphaForUnhide, mMaxAlphaForGlanceableHub)));
}
}
@@ -1779,6 +1795,7 @@
public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("mMaxAlphaForExpansion=" + mMaxAlphaForExpansion);
pw.println("mMaxAlphaForUnhide=" + mMaxAlphaForUnhide);
+ pw.println("mMaxAlphaForGlanceableHub=" + mMaxAlphaForGlanceableHub);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index 12927b8..f842e30 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -18,7 +18,6 @@
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
-import android.animation.ValueAnimator
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.dagger.qualifiers.Main
@@ -125,7 +124,14 @@
launch { viewModel.translationY.collect { controller.setTranslationY(it) } }
- launch { viewModel.alpha.collect { controller.setMaxAlphaForExpansion(it) } }
+ launch {
+ viewModel.expansionAlpha.collect { controller.setMaxAlphaForExpansion(it) }
+ }
+ launch {
+ viewModel.glanceableHubAlpha.collect {
+ controller.setMaxAlphaForGlanceableHub(it)
+ }
+ }
}
}
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 a48fb45..99cd89b 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
@@ -20,6 +20,7 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
import com.android.systemui.common.shared.model.NotificationContainerBounds
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -27,6 +28,8 @@
import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
+import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel
import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -61,8 +64,11 @@
private val keyguardInteractor: KeyguardInteractor,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val shadeInteractor: ShadeInteractor,
+ communalInteractor: CommunalInteractor,
occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
lockscreenToOccludedTransitionViewModel: LockscreenToOccludedTransitionViewModel,
+ glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel,
+ lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel
) {
private val statesForConstrainedNotifications =
setOf(
@@ -87,6 +93,20 @@
.distinctUntilChanged()
.onStart { emit(false) }
+ private val lockscreenToGlanceableHubRunning =
+ keyguardTransitionInteractor
+ .transition(KeyguardState.LOCKSCREEN, KeyguardState.GLANCEABLE_HUB)
+ .map { it.transitionState == STARTED || it.transitionState == RUNNING }
+ .distinctUntilChanged()
+ .onStart { emit(false) }
+
+ private val glanceableHubToLockscreenRunning =
+ keyguardTransitionInteractor
+ .transition(KeyguardState.GLANCEABLE_HUB, KeyguardState.LOCKSCREEN)
+ .map { it.transitionState == STARTED || it.transitionState == RUNNING }
+ .distinctUntilChanged()
+ .onStart { emit(false) }
+
val shadeCollapseFadeInComplete = MutableStateFlow(false)
val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
@@ -144,6 +164,24 @@
initialValue = false,
)
+ /** Are we purely on the glanceable hub without the shade/qs? */
+ internal val isOnGlanceableHubWithoutShade: Flow<Boolean> =
+ combine(
+ communalInteractor.isIdleOnCommunal,
+ // Shade with notifications
+ shadeInteractor.shadeExpansion.map { it > 0f },
+ // Shade without notifications, quick settings only (pull down from very top on
+ // lockscreen)
+ shadeInteractor.qsExpansion.map { it > 0f },
+ ) { isIdleOnCommunal, isShadeVisible, qsExpansion ->
+ isIdleOnCommunal && !(isShadeVisible || qsExpansion)
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
+
/** Fade in only for use after the shade collapses */
val shadeCollpaseFadeIn: Flow<Boolean> =
flow {
@@ -201,7 +239,7 @@
initialValue = NotificationContainerBounds(),
)
- val alpha: Flow<Float> =
+ val expansionAlpha: Flow<Float> =
// Due to issues with the legacy shade, some shade expansion events are sent incorrectly,
// such as when the shade resets. This can happen while the LOCKSCREEN<->OCCLUDED transition
// is running. Therefore use a series of flatmaps to prevent unwanted interruptions while
@@ -235,6 +273,43 @@
}
/**
+ * Returns a flow of the expected alpha while running a LOCKSCREEN<->GLANCEABLE_HUB transition
+ * or idle on the glanceable hub.
+ *
+ * Must return 1.0f when not controlling the alpha since notifications does a min of all the
+ * alpha sources.
+ */
+ val glanceableHubAlpha: Flow<Float> =
+ isOnGlanceableHubWithoutShade.flatMapLatest { isOnGlanceableHubWithoutShade ->
+ combineTransform(
+ lockscreenToGlanceableHubRunning,
+ glanceableHubToLockscreenRunning,
+ merge(
+ lockscreenToGlanceableHubTransitionViewModel.notificationAlpha,
+ glanceableHubToLockscreenTransitionViewModel.notificationAlpha,
+ )
+ .onStart {
+ // Transition flows don't emit a value on start, kick things off so the
+ // combine starts.
+ emit(1f)
+ }
+ ) { 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)
+ }
+ }
+ }
+
+ /**
* Under certain scenarios, such as swiping up on the lockscreen, the container will need to be
* translated as the keyguard fades out.
*/
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 b57cf53..754a7fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -97,6 +97,7 @@
import com.android.systemui.flags.SystemPropertiesHelper;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.scene.FakeWindowRootViewComponent;
@@ -150,6 +151,7 @@
@TestableLooper.RunWithLooper
@SmallTest
public class KeyguardViewMediatorTest extends SysuiTestCase {
+ private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
private KeyguardViewMediator mViewMediator;
private final TestScope mTestScope = TestScopeProvider.getTestScope();
@@ -265,7 +267,8 @@
mShadeWindowLogger,
() -> mSelectedUserInteractor,
mUserTracker,
- mSceneContainerFlags);
+ mSceneContainerFlags,
+ mKosmos::getCommunalInteractor);
mFeatureFlags = new FakeFeatureFlags();
mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false);
mSetFlagsRule.enableFlags(FLAG_REFACTOR_GET_CURRENT_USER);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index 81ff817..c1f5d85 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -94,7 +94,8 @@
.thenReturn(bouncerShowingFlow)
whenever(shadeInteractor.isAnyFullyExpanded).thenReturn(shadeShowingFlow)
- overrideResource(R.dimen.communal_grid_gutter_size, SWIPE_REGION_WIDTH)
+ overrideResource(R.dimen.communal_right_edge_swipe_region_width, RIGHT_SWIPE_REGION_WIDTH)
+ overrideResource(R.dimen.communal_top_edge_swipe_region_height, TOP_SWIPE_REGION_WIDTH)
}
@Test
@@ -135,7 +136,7 @@
initAndAttachContainerView()
// Touch events are intercepted.
- assertThat(underTest.onTouchEvent(DOWN_IN_SWIPE_REGION_EVENT)).isTrue()
+ assertThat(underTest.onTouchEvent(DOWN_IN_RIGHT_SWIPE_REGION_EVENT)).isTrue()
}
@Test
@@ -147,7 +148,7 @@
// Initial touch down is intercepted, and so are touches outside of the region, until an up
// event is received.
- assertThat(underTest.onTouchEvent(DOWN_IN_SWIPE_REGION_EVENT)).isTrue()
+ assertThat(underTest.onTouchEvent(DOWN_IN_RIGHT_SWIPE_REGION_EVENT)).isTrue()
assertThat(underTest.onTouchEvent(MOVE_EVENT)).isTrue()
assertThat(underTest.onTouchEvent(UP_EVENT)).isTrue()
assertThat(underTest.onTouchEvent(MOVE_EVENT)).isFalse()
@@ -168,6 +169,17 @@
}
@Test
+ fun onTouchEvent_topSwipeWhenHubOpen_returnsFalse() {
+ // Communal is open.
+ communalRepository.setDesiredScene(CommunalSceneKey.Communal)
+
+ initAndAttachContainerView()
+
+ // Touch event in the top swipe reqgion is not intercepted.
+ assertThat(underTest.onTouchEvent(DOWN_IN_TOP_SWIPE_REGION_EVENT)).isFalse()
+ }
+
+ @Test
fun onTouchEvent_communalAndBouncerShowing_doesNotIntercept() {
// Communal is open.
communalRepository.setDesiredScene(CommunalSceneKey.Communal)
@@ -213,11 +225,29 @@
companion object {
private const val CONTAINER_WIDTH = 100
private const val CONTAINER_HEIGHT = 100
- private const val SWIPE_REGION_WIDTH = 20
+ private const val RIGHT_SWIPE_REGION_WIDTH = 20
+ private const val TOP_SWIPE_REGION_WIDTH = 20
- private val DOWN_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
- private val DOWN_IN_SWIPE_REGION_EVENT =
+ private val DOWN_EVENT =
+ MotionEvent.obtain(
+ 0L,
+ 0L,
+ MotionEvent.ACTION_DOWN,
+ CONTAINER_WIDTH.toFloat(),
+ CONTAINER_HEIGHT.toFloat(),
+ 0
+ )
+ private val DOWN_IN_RIGHT_SWIPE_REGION_EVENT =
MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, CONTAINER_WIDTH.toFloat(), 0f, 0)
+ private val DOWN_IN_TOP_SWIPE_REGION_EVENT =
+ MotionEvent.obtain(
+ 0L,
+ 0L,
+ MotionEvent.ACTION_DOWN,
+ 0f,
+ TOP_SWIPE_REGION_WIDTH.toFloat(),
+ 0
+ )
private val MOVE_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
private val UP_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 0bffa1c..200e758 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -301,7 +301,8 @@
mShadeWindowLogger,
() -> mSelectedUserInteractor,
mUserTracker,
- mSceneContainerFlags) {
+ mSceneContainerFlags,
+ () -> communalInteractor) {
@Override
protected boolean isDebuggable() {
return false;
@@ -449,6 +450,24 @@
}
@Test
+ public void setCommunalShowing_userTimeout() {
+ setKeyguardShowing();
+ clearInvocations(mWindowManager);
+
+ mNotificationShadeWindowController.onCommunalShowingChanged(true);
+ verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
+ assertThat(mLayoutParameters.getValue().userActivityTimeout)
+ .isEqualTo(CommunalInteractor.AWAKE_INTERVAL_MS);
+ clearInvocations(mWindowManager);
+
+ // Bouncer showing over communal overrides communal value
+ mNotificationShadeWindowController.setBouncerShowing(true);
+ verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
+ assertThat(mLayoutParameters.getValue().userActivityTimeout)
+ .isEqualTo(KeyguardViewMediator.AWAKE_INTERVAL_BOUNCER_MS);
+ }
+
+ @Test
public void setKeyguardShowing_notFocusable_byDefault() {
mNotificationShadeWindowController.setKeyguardShowing(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index a824bc4..06298b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -25,6 +25,9 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.shared.model.CommunalSceneKey
+import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
@@ -33,20 +36,19 @@
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState
-import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.ui.viewmodel.keyguardRootViewModel
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.shade.data.repository.shadeRepository
-import com.android.systemui.shade.largeScreenHeaderHelper
import com.android.systemui.shade.mockLargeScreenHeaderHelper
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -68,6 +70,7 @@
val keyguardInteractor = kosmos.keyguardInteractor
val keyguardRootViewModel = kosmos.keyguardRootViewModel
val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ val communalInteractor = kosmos.communalInteractor
val shadeRepository = kosmos.shadeRepository
val sharedNotificationContainerInteractor = kosmos.sharedNotificationContainerInteractor
val largeScreenHeaderHelper = kosmos.mockLargeScreenHeaderHelper
@@ -214,6 +217,61 @@
}
@Test
+ fun glanceableHubAlpha() =
+ testScope.runTest {
+ val alpha by collectLastValue(underTest.glanceableHubAlpha)
+
+ // Start on lockscreen
+ showLockscreen()
+ assertThat(alpha).isEqualTo(1f)
+
+ // Start transitioning to glanceable hub
+ val progress = 0.6f
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GLANCEABLE_HUB,
+ value = 0f,
+ )
+ )
+ runCurrent()
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.RUNNING,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GLANCEABLE_HUB,
+ value = progress,
+ )
+ )
+ runCurrent()
+ // Expected alpha is inverse of progress as notifications are fading away
+ assertThat(alpha).isEqualTo(1 - progress)
+
+ // Finish transition to glanceable hub
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.FINISHED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GLANCEABLE_HUB,
+ value = 1f,
+ )
+ )
+ val idleTransitionState =
+ MutableStateFlow<ObservableCommunalTransitionState>(
+ ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)
+ )
+ communalInteractor.setTransitionState(idleTransitionState)
+ runCurrent()
+ assertThat(alpha).isEqualTo(0f)
+
+ // While state is GLANCEABLE_HUB, verify alpha is restored to full if glanceable hub is
+ // not fully visible.
+ shadeRepository.setLockscreenShadeExpansion(0.1f)
+ assertThat(alpha).isEqualTo(1f)
+ }
+
+ @Test
fun validateMarginTop() =
testScope.runTest {
overrideResource(R.bool.config_use_large_screen_shade_header, false)
@@ -302,6 +360,43 @@
}
@Test
+ fun isOnGlanceableHubWithoutShade() =
+ testScope.runTest {
+ val isOnGlanceableHubWithoutShade by
+ collectLastValue(underTest.isOnGlanceableHubWithoutShade)
+
+ // Start on lockscreen
+ showLockscreen()
+ assertThat(isOnGlanceableHubWithoutShade).isFalse()
+
+ // Move to glanceable hub
+ val idleTransitionState =
+ MutableStateFlow<ObservableCommunalTransitionState>(
+ ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)
+ )
+ communalInteractor.setTransitionState(idleTransitionState)
+ runCurrent()
+ assertThat(isOnGlanceableHubWithoutShade).isTrue()
+
+ // While state is GLANCEABLE_HUB, validate variations of both shade and qs expansion
+ shadeRepository.setLockscreenShadeExpansion(0.1f)
+ shadeRepository.setQsExpansion(0f)
+ assertThat(isOnGlanceableHubWithoutShade).isFalse()
+
+ shadeRepository.setLockscreenShadeExpansion(0.1f)
+ shadeRepository.setQsExpansion(0.1f)
+ assertThat(isOnGlanceableHubWithoutShade).isFalse()
+
+ shadeRepository.setLockscreenShadeExpansion(0f)
+ shadeRepository.setQsExpansion(0.1f)
+ assertThat(isOnGlanceableHubWithoutShade).isFalse()
+
+ shadeRepository.setLockscreenShadeExpansion(0f)
+ shadeRepository.setQsExpansion(0f)
+ assertThat(isOnGlanceableHubWithoutShade).isTrue()
+ }
+
+ @Test
fun boundsOnLockscreenNotInSplitShade() =
testScope.runTest {
val bounds by collectLastValue(underTest.bounds)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 744f424..93d8dcc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -534,7 +534,8 @@
mShadeWindowLogger,
() -> mSelectedUserInteractor,
mUserTracker,
- mSceneContainerFlags
+ mSceneContainerFlags,
+ mKosmos::getCommunalInteractor
);
mNotificationShadeWindowController.fetchWindowRootView();
mNotificationShadeWindowController.attach();
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index 7e3c10b..1abf71f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -26,6 +26,7 @@
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.smartspace.data.repository.smartspaceRepository
+import com.android.systemui.user.data.repository.userRepository
import com.android.systemui.util.mockito.mock
val Kosmos.communalInteractor by Fixture {
@@ -36,6 +37,7 @@
mediaRepository = communalMediaRepository,
communalPrefsRepository = communalPrefsRepository,
smartspaceRepository = smartspaceRepository,
+ userRepository = userRepository,
appWidgetHost = mock(),
keyguardInteractor = keyguardInteractor,
editWidgetsActivityStarter = editWidgetsActivityStarter,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt
index baba88d..adaea7c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt
@@ -29,5 +29,6 @@
communalTutorialRepository = communalTutorialRepository,
keyguardInteractor = keyguardInteractor,
communalRepository = communalRepository,
+ communalInteractor = communalInteractor,
)
}
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 5564767..d376f12 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
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
@@ -33,6 +34,7 @@
deviceEntryInteractor = deviceEntryInteractor,
dozeParameters = dozeParameters,
keyguardInteractor = keyguardInteractor,
+ communalInteractor = communalInteractor,
keyguardTransitionInteractor = keyguardTransitionInteractor,
notificationsKeyguardInteractor = notificationsKeyguardInteractor,
aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
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 5ef9a8e..db40509 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
@@ -16,8 +16,11 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.lockscreenToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.lockscreenToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.occludedToLockscreenTransitionViewModel
import com.android.systemui.kosmos.Kosmos
@@ -33,7 +36,10 @@
keyguardInteractor = keyguardInteractor,
keyguardTransitionInteractor = keyguardTransitionInteractor,
shadeInteractor = shadeInteractor,
+ communalInteractor = communalInteractor,
occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel,
lockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel,
+ glanceableHubToLockscreenTransitionViewModel = glanceableHubToLockscreenTransitionViewModel,
+ lockscreenToGlanceableHubTransitionViewModel = lockscreenToGlanceableHubTransitionViewModel
)
}
diff --git a/packages/overlays/NavigationBarModeGesturalOverlay/res/values-sw600dp/config.xml b/packages/overlays/NavigationBarModeGesturalOverlay/res/values-sw600dp/config.xml
new file mode 100644
index 0000000..be1f081
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlay/res/values-sw600dp/config.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<resources>
+ <!-- If true, attach the navigation bar to the app during app transition -->
+ <bool name="config_attachNavBarToAppDuringTransition">false</bool>
+</resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values-sw600dp/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values-sw600dp/config.xml
new file mode 100644
index 0000000..be1f081
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values-sw600dp/config.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<resources>
+ <!-- If true, attach the navigation bar to the app during app transition -->
+ <bool name="config_attachNavBarToAppDuringTransition">false</bool>
+</resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values-sw600dp/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values-sw600dp/config.xml
new file mode 100644
index 0000000..be1f081
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values-sw600dp/config.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<resources>
+ <!-- If true, attach the navigation bar to the app during app transition -->
+ <bool name="config_attachNavBarToAppDuringTransition">false</bool>
+</resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values-sw600dp/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values-sw600dp/config.xml
new file mode 100644
index 0000000..be1f081
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values-sw600dp/config.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<resources>
+ <!-- If true, attach the navigation bar to the app during app transition -->
+ <bool name="config_attachNavBarToAppDuringTransition">false</bool>
+</resources>
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 3d8d7b7..d656892 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -4413,25 +4413,50 @@
@Override
public boolean isAccessibilityServiceWarningRequired(AccessibilityServiceInfo info) {
mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
+ final ComponentName componentName = info.getComponentName();
// Warning is not required if the service is already enabled.
synchronized (mLock) {
final AccessibilityUserState userState = getCurrentUserStateLocked();
- if (userState.getEnabledServicesLocked().contains(info.getComponentName())) {
+ if (userState.getEnabledServicesLocked().contains(componentName)) {
return false;
}
}
// Warning is not required if the service is already assigned to a shortcut.
for (int shortcutType : AccessibilityManager.SHORTCUT_TYPES) {
if (getAccessibilityShortcutTargets(shortcutType).contains(
- info.getComponentName().flattenToString())) {
+ componentName.flattenToString())) {
return false;
}
}
+ // Warning is not required if the service is preinstalled and in the
+ // trustedAccessibilityServices allowlist.
+ if (android.view.accessibility.Flags.skipAccessibilityWarningDialogForTrustedServices()
+ && isAccessibilityServicePreinstalledAndTrusted(info)) {
+ return false;
+ }
+
// Warning is required by default.
return true;
}
+ private boolean isAccessibilityServicePreinstalledAndTrusted(AccessibilityServiceInfo info) {
+ final ComponentName componentName = info.getComponentName();
+ final boolean isPreinstalled =
+ info.getResolveInfo().serviceInfo.applicationInfo.isSystemApp();
+ if (isPreinstalled) {
+ final String[] trustedAccessibilityServices =
+ mContext.getResources().getStringArray(
+ R.array.config_trustedAccessibilityServices);
+ if (Arrays.stream(trustedAccessibilityServices)
+ .map(ComponentName::unflattenFromString)
+ .anyMatch(componentName::equals)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
@Override
public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 50e1862..84e1d90 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -247,6 +247,11 @@
final Context context = getContext();
mPersistentStore = new PersistentDataStore();
+ mAssociationRequestsProcessor = new AssociationRequestsProcessor(
+ /* cdmService */ this, mAssociationStore);
+ mBackupRestoreProcessor = new BackupRestoreProcessor(
+ /* cdmService */ this, mAssociationStore, mPersistentStore,
+ mSystemDataTransferRequestStore, mAssociationRequestsProcessor);
loadAssociationsFromDisk();
mAssociationStore.registerListener(mAssociationStoreChangeListener);
@@ -254,17 +259,12 @@
mDevicePresenceMonitor = new CompanionDevicePresenceMonitor(mUserManager,
mAssociationStore, mDevicePresenceCallback);
- mAssociationRequestsProcessor = new AssociationRequestsProcessor(
- /* cdmService */this, mAssociationStore);
mCompanionAppController = new CompanionApplicationController(
context, mAssociationStore, mDevicePresenceMonitor);
mTransportManager = new CompanionTransportManager(context, mAssociationStore);
mSystemDataTransferProcessor = new SystemDataTransferProcessor(this,
mPackageManagerInternal, mAssociationStore,
mSystemDataTransferRequestStore, mTransportManager);
- mBackupRestoreProcessor = new BackupRestoreProcessor(
- /* cdmService */ this, mAssociationStore, mPersistentStore,
- mSystemDataTransferRequestStore, mAssociationRequestsProcessor);
// TODO(b/279663946): move context sync to a dedicated system service
mCrossDeviceSyncController = new CrossDeviceSyncController(getContext(), mTransportManager);
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 2cac7a0..db0f03f 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -1244,9 +1244,11 @@
}
private void deliveryTimeout(@NonNull BroadcastProcessQueue queue) {
+ final int cookie = traceBegin("deliveryTimeout");
synchronized (mService) {
deliveryTimeoutLocked(queue);
}
+ traceEnd(cookie);
}
private void deliveryTimeoutLocked(@NonNull BroadcastProcessQueue queue) {
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index c1d5ebf..67c23fc 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -3475,6 +3475,10 @@
private void applyAdditionalDisplayInputPropertiesLocked(
AdditionalDisplayInputProperties properties) {
// Handle changes to each of the individual properties.
+ // TODO(b/293587049): This approach for updating pointer display properties is only for when
+ // PointerChoreographer is disabled. Remove this logic when PointerChoreographer is
+ // permanently enabled.
+
if (properties.pointerIconVisible != mCurrentDisplayProperties.pointerIconVisible) {
mCurrentDisplayProperties.pointerIconVisible = properties.pointerIconVisible;
if (properties.pointerIconVisible) {
@@ -3493,7 +3497,6 @@
!= mCurrentDisplayProperties.mousePointerAccelerationEnabled) {
mCurrentDisplayProperties.mousePointerAccelerationEnabled =
properties.mousePointerAccelerationEnabled;
- mNative.setMousePointerAccelerationEnabled(properties.mousePointerAccelerationEnabled);
}
}
@@ -3507,10 +3510,15 @@
mAdditionalDisplayInputProperties.put(displayId, properties);
}
final boolean oldPointerIconVisible = properties.pointerIconVisible;
+ final boolean oldMouseAccelerationEnabled = properties.mousePointerAccelerationEnabled;
updater.accept(properties);
if (oldPointerIconVisible != properties.pointerIconVisible) {
mNative.setPointerIconVisibility(displayId, properties.pointerIconVisible);
}
+ if (oldMouseAccelerationEnabled != properties.mousePointerAccelerationEnabled) {
+ mNative.setMousePointerAccelerationEnabled(displayId,
+ properties.mousePointerAccelerationEnabled);
+ }
if (properties.allDefaults()) {
mAdditionalDisplayInputProperties.remove(displayId);
}
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 706db3d..6f52020 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -119,7 +119,7 @@
void setPointerSpeed(int speed);
- void setMousePointerAccelerationEnabled(boolean enabled);
+ void setMousePointerAccelerationEnabled(int displayId, boolean enabled);
void setTouchpadPointerSpeed(int speed);
@@ -354,7 +354,7 @@
public native void setPointerSpeed(int speed);
@Override
- public native void setMousePointerAccelerationEnabled(boolean enabled);
+ public native void setMousePointerAccelerationEnabled(int displayId, boolean enabled);
@Override
public native void setTouchpadPointerSpeed(int speed);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 308d441..425659e 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -282,6 +282,7 @@
import android.service.notification.StatusBarNotification;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenModeProto;
+import android.service.notification.ZenPolicy;
import android.telecom.TelecomManager;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
@@ -5968,6 +5969,20 @@
}
}
+ /**
+ * Gets the device-default zen policy as a ZenPolicy.
+ */
+ @Override
+ public ZenPolicy getDefaultZenPolicy() {
+ enforceSystemOrSystemUI("INotificationManager.getDefaultZenPolicy");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return mZenModeHelper.getDefaultZenPolicy();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
@Override
public List<String> getEnabledNotificationListenerPackages() {
checkCallerIsSystem();
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 153af13..c067fa0 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -604,6 +604,14 @@
ZenRule rule = newConfig.automaticRules.get(implicitRuleId(callingPkg));
if (rule == null) {
rule = newImplicitZenRule(callingPkg);
+
+ // For new implicit rules, create a policy matching the current global
+ // (manual rule) settings, for consistency with the policy that
+ // would apply if changing the global interruption filter. We only do this
+ // for newly created rules, as existing rules have a pre-existing policy
+ // (whether initialized here or set via app or user).
+ rule.zenPolicy = mConfig.toZenPolicy();
+
newConfig.automaticRules.put(rule.id, rule);
}
// If the user has changed the rule's *zenMode*, then don't let app overwrite it.
@@ -615,6 +623,7 @@
rule.condition = new Condition(rule.conditionId,
mContext.getString(R.string.zen_mode_implicit_activated),
Condition.STATE_TRUE);
+
setConfigLocked(newConfig, /* triggeringComponent= */ null, UPDATE_ORIGIN_APP,
"applyGlobalZenModeAsImplicitZenRule", callingUid);
}
@@ -643,8 +652,10 @@
return;
}
ZenModeConfig newConfig = mConfig.copy();
+ boolean isNew = false;
ZenRule rule = newConfig.automaticRules.get(implicitRuleId(callingPkg));
if (rule == null) {
+ isNew = true;
rule = newImplicitZenRule(callingPkg);
rule.zenMode = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
newConfig.automaticRules.put(rule.id, rule);
@@ -652,10 +663,20 @@
// If the user has changed the rule's *ZenPolicy*, then don't let app overwrite it.
// We allow the update if the user has only changed other aspects of the rule.
if (rule.zenPolicyUserModifiedFields == 0) {
+ ZenPolicy newZenPolicy = ZenAdapters.notificationPolicyToZenPolicy(policy);
+ if (isNew) {
+ // For new rules only, fill anything underspecified in the new policy with
+ // values from the global configuration, for consistency with the policy that
+ // would take effect if changing the global policy.
+ // Note that NotificationManager.Policy cannot have any unset priority
+ // categories, but *can* have unset visual effects, which is why we do this.
+ newZenPolicy = mConfig.toZenPolicy().overwrittenWith(newZenPolicy);
+ }
updatePolicy(
rule,
- ZenAdapters.notificationPolicyToZenPolicy(policy),
- /* updateBitmask= */ false);
+ newZenPolicy,
+ /* updateBitmask= */ false,
+ isNew);
setConfigLocked(newConfig, /* triggeringComponent= */ null, UPDATE_ORIGIN_APP,
"applyGlobalPolicyAsImplicitZenRule", callingUid);
@@ -685,6 +706,11 @@
}
ZenRule implicitRule = mConfig.automaticRules.get(implicitRuleId(callingPkg));
if (implicitRule != null && implicitRule.zenPolicy != null) {
+ // toNotificationPolicy takes defaults from mConfig, and technically, those are not
+ // the defaults that would apply if any fields were unset. However, all rules should
+ // have all fields set in their ZenPolicy objects upon rule creation, so in
+ // practice, this is only filling in the areChannelsBypassingDnd field, which is a
+ // state rather than a part of the policy.
return mConfig.toNotificationPolicy(implicitRule.zenPolicy);
} else {
return getNotificationPolicy();
@@ -1072,7 +1098,8 @@
rule.zenMode = newZenMode;
// Updates the bitmask and values for all policy fields, based on the origin.
- updatePolicy(rule, automaticZenRule.getZenPolicy(), updateBitmask);
+ updatePolicy(rule, automaticZenRule.getZenPolicy(), updateBitmask, isNew);
+
// Updates the bitmask and values for all device effect fields, based on the origin.
updateZenDeviceEffects(rule, automaticZenRule.getDeviceEffects(),
origin == UPDATE_ORIGIN_APP, updateBitmask);
@@ -1111,14 +1138,19 @@
/**
* Modifies the {@link ZenPolicy} associated to a new or updated ZenRule.
*
- * <p>The new policy is {@code newPolicy}, while the user-modified bitmask is updated to reflect
- * the changes being applied (if applicable, i.e. if the update is from the user).
+ * <p>The update takes any set fields in {@code newPolicy} as new policy settings for the
+ * provided {@code ZenRule}, keeping any pre-existing settings from {@code zenRule.zenPolicy}
+ * for any unset policy fields in {@code newPolicy}. The user-modified bitmask is updated to
+ * reflect the changes being applied (if applicable, i.e. if the update is from the user).
*/
private void updatePolicy(ZenRule zenRule, @Nullable ZenPolicy newPolicy,
- boolean updateBitmask) {
+ boolean updateBitmask, boolean isNew) {
if (newPolicy == null) {
- // TODO: b/319242206 - Treat as newPolicy == default policy and continue below.
- zenRule.zenPolicy = null;
+ if (isNew) {
+ // Newly created rule with no provided policy; fill in with the default.
+ zenRule.zenPolicy = mDefaultConfig.toZenPolicy();
+ }
+ // Otherwise, a null policy means no policy changes, so we can stop here.
return;
}
@@ -1127,6 +1159,16 @@
ZenPolicy oldPolicy =
zenRule.zenPolicy != null ? zenRule.zenPolicy : mDefaultConfig.toZenPolicy();
+ // If this is updating a rule rather than creating a new one, keep any fields from the
+ // old policy if they are unspecified in the new policy. For newly created rules, oldPolicy
+ // has been set to the default settings above, so any unspecified fields in a newly created
+ // policy are filled with default values. Then use the fully-specified version of the new
+ // policy for comparison below.
+ //
+ // Although we do not expect a policy update from the user to contain any unset fields,
+ // filling in fields here also guards against any unset fields counting as a "diff" when
+ // comparing fields for bitmask editing below.
+ newPolicy = oldPolicy.overwrittenWith(newPolicy);
zenRule.zenPolicy = newPolicy;
if (updateBitmask) {
@@ -1452,11 +1494,27 @@
}
allRulesDisabled &= !automaticRule.enabled;
+
+ // Upon upgrading to a version with modes_api enabled, keep all behaviors of
+ // rules with null ZenPolicies explicitly as a copy of the global policy.
+ if (Flags.modesApi() && config.version < ZenModeConfig.XML_VERSION_MODES_API) {
+ // Keep the manual ("global") policy that from config.
+ ZenPolicy manualRulePolicy = config.toZenPolicy();
+ if (automaticRule.zenPolicy == null) {
+ automaticRule.zenPolicy = manualRulePolicy;
+ } else {
+ // newPolicy is a policy with all unset fields in the rule's zenPolicy
+ // set to their values from the values in config. Then convert that back
+ // to ZenPolicy to store with the automatic zen rule.
+ automaticRule.zenPolicy =
+ manualRulePolicy.overwrittenWith(automaticRule.zenPolicy);
+ }
+ }
}
}
if (!hasDefaultRules && allRulesDisabled
- && (forRestore || config.version < ZenModeConfig.XML_VERSION)) {
+ && (forRestore || config.version < ZenModeConfig.XML_VERSION_ZEN_UPGRADE)) {
// reset zen automatic rules to default on restore or upgrade if:
// - doesn't already have default rules and
// - all previous automatic rules were disabled
@@ -1473,7 +1531,7 @@
// Resolve user id for settings.
userId = userId == UserHandle.USER_ALL ? UserHandle.USER_SYSTEM : userId;
- if (config.version < ZenModeConfig.XML_VERSION) {
+ if (config.version < ZenModeConfig.XML_VERSION_ZEN_UPGRADE) {
Settings.Secure.putIntForUser(mContext.getContentResolver(),
Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 1, userId);
} else {
@@ -1600,6 +1658,14 @@
return mConsolidatedPolicy.copy();
}
+ /**
+ * Returns a copy of the device default policy as a ZenPolicy object.
+ */
+ @VisibleForTesting
+ protected ZenPolicy getDefaultZenPolicy() {
+ return mDefaultConfig.toZenPolicy();
+ }
+
@GuardedBy("mConfigLock")
private boolean setConfigLocked(ZenModeConfig config, ComponentName triggeringComponent,
@ConfigChangeOrigin int origin, String reason, int callingUid) {
@@ -1783,7 +1849,7 @@
}
@GuardedBy("mConfigLock")
- private void applyCustomPolicy(ZenPolicy policy, ZenRule rule) {
+ private void applyCustomPolicy(ZenPolicy policy, ZenRule rule, boolean useManualConfig) {
if (rule.zenMode == Global.ZEN_MODE_NO_INTERRUPTIONS) {
policy.apply(new ZenPolicy.Builder()
.disallowAllSounds()
@@ -1797,8 +1863,22 @@
} else if (rule.zenPolicy != null) {
policy.apply(rule.zenPolicy);
} else {
- // active rule with no specified policy inherits the default settings
- policy.apply(mConfig.toZenPolicy());
+ if (Flags.modesApi()) {
+ if (useManualConfig) {
+ // manual rule is configured using the settings stored directly in mConfig
+ policy.apply(mConfig.toZenPolicy());
+ } else {
+ // under modes_api flag, an active automatic rule with no specified policy
+ // inherits the device default settings as stored in mDefaultConfig. While the
+ // rule's policy fields should be set upon creation, this is a fallback to
+ // catch any that may have fallen through the cracks.
+ Log.wtf(TAG, "active automatic rule found with no specified policy: " + rule);
+ policy.apply(mDefaultConfig.toZenPolicy());
+ }
+ } else {
+ // active rule with no specified policy inherits the global config settings
+ policy.apply(mConfig.toZenPolicy());
+ }
}
}
@@ -1810,7 +1890,7 @@
ZenPolicy policy = new ZenPolicy();
ZenDeviceEffects.Builder deviceEffectsBuilder = new ZenDeviceEffects.Builder();
if (mConfig.manualRule != null) {
- applyCustomPolicy(policy, mConfig.manualRule);
+ applyCustomPolicy(policy, mConfig.manualRule, true);
if (Flags.modesApi()) {
deviceEffectsBuilder.add(mConfig.manualRule.zenDeviceEffects);
}
@@ -1822,7 +1902,7 @@
// policy. This is relevant in case some other active rule has a more
// restrictive INTERRUPTION_FILTER but a more lenient ZenPolicy!
if (!Flags.modesApi() || automaticRule.zenMode != Global.ZEN_MODE_OFF) {
- applyCustomPolicy(policy, automaticRule);
+ applyCustomPolicy(policy, automaticRule, false);
}
if (Flags.modesApi()) {
deviceEffectsBuilder.add(automaticRule.zenDeviceEffects);
@@ -1830,6 +1910,14 @@
}
}
+ // While mConfig.toNotificationPolicy fills in any unset fields from the provided
+ // config (which, in this case is the manual "global" config), under modes API changes,
+ // we should have no remaining unset fields: the manual policy gets every field from
+ // the global policy, and each automatic rule has all policy fields filled in on
+ // creation or update.
+ // However, the piece of information that comes from mConfig that we must keep is the
+ // areChannelsBypassingDnd bit, which is a state, rather than a policy, and even when
+ // all policy fields are set, this state comes to the new policy from this call.
Policy newPolicy = mConfig.toNotificationPolicy(policy);
if (!Objects.equals(mConsolidatedPolicy, newPolicy)) {
mConsolidatedPolicy = newPolicy;
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 68c95b1..ac826af 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -18,10 +18,6 @@
import static android.Manifest.permission.READ_FRAME_BUFFER;
import static android.app.ActivityOptions.KEY_SPLASH_SCREEN_THEME;
-import static android.app.AppOpsManager.MODE_ALLOWED;
-import static android.app.AppOpsManager.MODE_IGNORED;
-import static android.app.AppOpsManager.OP_ARCHIVE_ICON_OVERLAY;
-import static android.app.AppOpsManager.OP_UNARCHIVAL_CONFIRMATION;
import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
import static android.app.PendingIntent.FLAG_IMMUTABLE;
@@ -47,7 +43,6 @@
import android.app.ActivityManagerInternal;
import android.app.ActivityOptions;
import android.app.AppGlobals;
-import android.app.AppOpsManager;
import android.app.IApplicationThread;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyCache;
@@ -218,7 +213,6 @@
private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
private final ShortcutServiceInternal mShortcutServiceInternal;
private final PackageManagerInternal mPackageManagerInternal;
- private final AppOpsManager mAppOpsManager;
private final PackageCallbackList<IOnAppsChangedListener> mListeners
= new PackageCallbackList<IOnAppsChangedListener>();
private final DevicePolicyManager mDpm;
@@ -259,7 +253,6 @@
LocalServices.getService(ShortcutServiceInternal.class));
mPackageManagerInternal = Objects.requireNonNull(
LocalServices.getService(PackageManagerInternal.class));
- mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
mShortcutServiceInternal.addListener(mPackageMonitor);
mShortcutChangeHandler = new ShortcutChangeHandler(mUserManagerInternal);
mShortcutServiceInternal.addShortcutChangeCallback(mShortcutChangeHandler);
@@ -2005,23 +1998,6 @@
}
}
- @Override
- public void setArchiveCompatibilityOptions(boolean enableIconOverlay,
- boolean enableUnarchivalConfirmation) {
- int callingUid = Binder.getCallingUid();
- Binder.withCleanCallingIdentity(
- () -> {
- mAppOpsManager.setUidMode(
- OP_ARCHIVE_ICON_OVERLAY,
- callingUid,
- enableIconOverlay ? MODE_ALLOWED : MODE_IGNORED);
- mAppOpsManager.setUidMode(
- OP_UNARCHIVAL_CONFIRMATION,
- callingUid,
- enableUnarchivalConfirmation ? MODE_ALLOWED : MODE_IGNORED);
- });
- }
-
/** Checks if user is a profile of or same as listeningUser.
* and the user is enabled. */
private boolean isEnabledProfileOf(UserHandle listeningUser, UserHandle user,
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index c1b3673..1a20c8d 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -20,7 +20,6 @@
import static android.app.ActivityManager.START_CLASS_NOT_FOUND;
import static android.app.ActivityManager.START_PERMISSION_DENIED;
import static android.app.ActivityManager.START_SUCCESS;
-import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_IGNORED;
import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
import static android.content.pm.ArchivedActivityInfo.bytesFromBitmap;
@@ -274,12 +273,11 @@
Slog.i(TAG, TextUtils.formatSimple("Unarchival is starting for: %s", packageName));
try {
+ // TODO(b/311709794) Make showUnarchivalConfirmation dependent on the compat options.
requestUnarchive(packageName, callerPackageName,
getOrCreateLauncherListener(userId, packageName),
UserHandle.of(userId),
- getAppOpsManager().checkOp(
- AppOpsManager.OP_UNARCHIVAL_CONFIRMATION, callingUid, callerPackageName)
- == MODE_ALLOWED);
+ false /* showUnarchivalConfirmation= */);
} catch (Throwable t) {
Slog.e(TAG, TextUtils.formatSimple(
"Unexpected error occurred while unarchiving package %s: %s.", packageName,
@@ -796,8 +794,7 @@
* <p> The icon is returned without any treatment/overlay. In the rare case the app had multiple
* launcher activities, only one of the icons is returned arbitrarily.
*/
- public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user,
- String callingPackageName) {
+ public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user) {
Objects.requireNonNull(packageName);
Objects.requireNonNull(user);
@@ -820,13 +817,7 @@
// TODO(b/298452477) Handle monochrome icons.
// In the rare case the archived app defined more than two launcher activities, we choose
// the first one arbitrarily.
- Bitmap icon = decodeIcon(archiveState.getActivityInfos().get(0));
- if (getAppOpsManager().checkOp(
- AppOpsManager.OP_ARCHIVE_ICON_OVERLAY, callingUid, callingPackageName)
- == MODE_ALLOWED) {
- icon = includeCloudOverlay(icon);
- }
- return icon;
+ return includeCloudOverlay(decodeIcon(archiveState.getActivityInfos().get(0)));
}
/**
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 7bf9fe7..cfafe7c 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -788,6 +788,24 @@
}
}
+ if (Flags.recoverabilityDetection()) {
+ if (params.rollbackImpactLevel == PackageManager.ROLLBACK_USER_IMPACT_HIGH
+ || params.rollbackImpactLevel
+ == PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL) {
+ if ((params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) == 0) {
+ throw new IllegalArgumentException(
+ "Can't set rollbackImpactLevel when rollback is not enabled");
+ }
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_ROLLBACKS)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException(
+ "Setting rollbackImpactLevel requires the MANAGE_ROLLBACKS permission");
+ }
+ } else if (params.rollbackImpactLevel < 0) {
+ throw new IllegalArgumentException("rollbackImpactLevel can't be negative.");
+ }
+ }
+
boolean isApex = (params.installFlags & PackageManager.INSTALL_APEX) != 0;
if (isApex) {
if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGE_UPDATES)
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 117d03f..1a0e807 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -1294,6 +1294,7 @@
info.autoRevokePermissionsMode = params.autoRevokePermissionsMode;
info.installFlags = params.installFlags;
info.rollbackLifetimeMillis = params.rollbackLifetimeMillis;
+ info.rollbackImpactLevel = params.rollbackImpactLevel;
info.isMultiPackage = params.isMultiPackage;
info.isStaged = params.isStaged;
info.rollbackDataPolicy = params.rollbackDataPolicy;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index f09fa21..609b3aa 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -6388,10 +6388,8 @@
}
@Override
- public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user,
- @NonNull String callingPackageName) {
- return mInstallerService.mPackageArchiver.getArchivedAppIcon(packageName, user,
- callingPackageName);
+ public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user) {
+ return mInstallerService.mPackageArchiver.getArchivedAppIcon(packageName, user);
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 88dc60c..dc1f1ac 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -2678,7 +2678,7 @@
}
final int translatedUserId =
translateUserId(userId, UserHandle.USER_NULL, "runSetStoppedState");
- mInterface.setPackageStoppedState(pkg, state, userId);
+ mInterface.setPackageStoppedState(pkg, state, translatedUserId);
getOutPrintWriter().println("Package " + pkg + " new stopped state: "
+ mInterface.isPackageStoppedForUser(pkg, translatedUserId));
return 0;
@@ -3716,7 +3716,19 @@
// remember to set it themselves.
params.installerPackageName = "com.android.shell";
}
- sessionParams.installFlags |= PackageManager.INSTALL_ENABLE_ROLLBACK;
+ int rollbackStrategy = PackageManager.ROLLBACK_DATA_POLICY_RESTORE;
+ try {
+ rollbackStrategy = Integer.parseInt(peekNextArg());
+ if (rollbackStrategy < PackageManager.ROLLBACK_DATA_POLICY_RESTORE
+ || rollbackStrategy > PackageManager.ROLLBACK_DATA_POLICY_RETAIN) {
+ throw new IllegalArgumentException(
+ rollbackStrategy + " is not a valid rollback data policy.");
+ }
+ getNextArg(); // pop the argument
+ } catch (NumberFormatException e) {
+ // not followed by a number assume ROLLBACK_DATA_POLICY_RESTORE.
+ }
+ sessionParams.setEnableRollback(true, rollbackStrategy);
break;
case "--staged-ready-timeout":
params.stagedReadyTimeoutMs = Long.parseLong(getNextArgRequired());
@@ -3751,6 +3763,11 @@
} else if (staged) {
sessionParams.setStaged();
}
+ if ((sessionParams.installFlags & PackageManager.INSTALL_APEX) != 0
+ && (sessionParams.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0
+ && sessionParams.rollbackDataPolicy == PackageManager.ROLLBACK_DATA_POLICY_WIPE) {
+ throw new IllegalArgumentException("Data policy 'wipe' is not supported for apex.");
+ }
return params;
}
@@ -4829,7 +4846,7 @@
pw.println(" [--install-reason 0/1/2/3/4] [--originating-uri URI]");
pw.println(" [--referrer URI] [--abi ABI_NAME] [--force-sdk]");
pw.println(" [--preload] [--instant] [--full] [--dont-kill]");
- pw.println(" [--enable-rollback]");
+ pw.println(" [--enable-rollback [0/1/2]]");
pw.println(" [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES]");
pw.println(" [--apex] [--non-staged] [--force-non-staged]");
pw.println(" [--staged-ready-timeout TIMEOUT] [--ignore-dexopt-profile]");
@@ -4853,6 +4870,8 @@
pw.println(" --abi: override the default ABI of the platform");
pw.println(" --instant: cause the app to be installed as an ephemeral install app");
pw.println(" --full: cause the app to be installed as a non-ephemeral full app");
+ pw.println(" --enable-rollback: enable rollbacks for the upgrade.");
+ pw.println(" 0=restore (default), 1=wipe, 2=retain");
pw.println(" --install-location: force the install location:");
pw.println(" 0=auto, 1=internal only, 2=prefer external");
pw.println(" --install-reason: indicates why the app is being installed:");
diff --git a/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java b/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java
index 5e8b4de..7808c4e 100644
--- a/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java
+++ b/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java
@@ -30,6 +30,7 @@
import android.content.pm.ResolveInfo;
import android.content.pm.Signature;
import android.os.Environment;
+import android.permission.flags.Flags;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -368,6 +369,7 @@
dataOutputStream.writeUTF(profileOwner);
dataOutputStream.writeInt(Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.DEVICE_DEMO_MODE, 0));
+ dataOutputStream.writeBoolean(Flags.walletRoleEnabled());
dataOutputStream.flush();
} catch (IOException e) {
// Never happens for MessageDigestOutputStream and DataOutputStream.
diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java
index 871e98b..4bf8a78 100644
--- a/services/core/java/com/android/server/power/ShutdownThread.java
+++ b/services/core/java/com/android/server/power/ShutdownThread.java
@@ -319,6 +319,11 @@
pd.setMax(100);
pd.setProgress(0);
pd.setIndeterminate(false);
+ boolean showPercent = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_showPercentageTextDuringRebootToUpdate);
+ if (!showPercent) {
+ pd.setProgressPercentFormat(null);
+ }
pd.setProgressNumberFormat(null);
pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
pd.setMessage(context.getText(
@@ -911,4 +916,4 @@
com.android.internal.R.string.config_defaultShutdownVibrationFile);
}
}
-}
\ No newline at end of file
+}
diff --git a/services/core/java/com/android/server/rollback/README.md b/services/core/java/com/android/server/rollback/README.md
index 08800da..f6bcbd0 100644
--- a/services/core/java/com/android/server/rollback/README.md
+++ b/services/core/java/com/android/server/rollback/README.md
@@ -187,13 +187,22 @@
### Installing an App with Rollback Enabled
-The `adb install` command accepts the `--enable-rollback` flag to install an app
+The `adb install` command accepts the `--enable-rollback [0/1/2]` flag to install an app
with rollback enabled. For example:
```
$ adb install --enable-rollback FooV2.apk
```
+The default rollback data policy is `ROLLBACK_DATA_POLICY_RESTORE` (0). To use
+a different `RollbackDataPolicy`, like `ROLLBACK_DATA_POLICY_RETAIN` (1) or
+`ROLLBACK_DATA_POLICY_WIPE` (2), provide the int value after
+`--enable-rollback`. For example:
+
+```
+$ adb install --enable-rollback 1 FooV2.apk
+```
+
### Triggering Rollback Manually
If rollback is available for an application, the pm command can be used to
@@ -217,7 +226,7 @@
-state: committed
-timestamp: 2019-04-23T14:57:35.944Z
-packages:
- com.android.tests.rollback.testapp.B 2 -> 1
+ com.android.tests.rollback.testapp.B 2 -> 1 [0]
-causePackages:
-committedSessionId: 1845805640
649899517:
@@ -225,7 +234,7 @@
-timestamp: 2019-04-23T12:55:21.342Z
-stagedSessionId: 343374391
-packages:
- com.android.tests.rollback.testapex 2 -> 1
+ com.android.tests.rollback.testapex 2 -> 1 [0]
-causePackages:
-committedSessionId: 2096717281
```
@@ -233,7 +242,8 @@
The example above shows two recently committed rollbacks. The update of
com.android.tests.rollback.testapp.B from version 1 to version 2 was rolled
back, and the update of com.android.tests.rollback.testapex from version 1 to
-version 2 was rolled back.
+version 2 was rolled back. For each package the value inside '[' and ']'
+indicates the `RollbackDataPolicy` for the rollback back.
The state is 'available' or 'committed'. The timestamp gives the time when the
rollback was first made available. If a stagedSessionId is present, then the
diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java
index a5b90f1..d1f91c8 100644
--- a/services/core/java/com/android/server/rollback/Rollback.java
+++ b/services/core/java/com/android/server/rollback/Rollback.java
@@ -215,7 +215,8 @@
/* packages */ new ArrayList<>(),
/* isStaged */ isStaged,
/* causePackages */ new ArrayList<>(),
- /* committedSessionId */ -1);
+ /* committedSessionId */ -1,
+ /* rollbackImpactLevel */ PackageManager.ROLLBACK_USER_IMPACT_LOW);
mUserId = userId;
mInstallerPackageName = installerPackageName;
mBackupDir = backupDir;
@@ -394,7 +395,8 @@
*/
@WorkerThread
boolean enableForPackage(String packageName, long newVersion, long installedVersion,
- boolean isApex, String sourceDir, String[] splitSourceDirs, int rollbackDataPolicy) {
+ boolean isApex, String sourceDir, String[] splitSourceDirs, int rollbackDataPolicy,
+ @PackageManager.RollbackImpactLevel int rollbackImpactLevel) {
assertInWorkerThread();
try {
RollbackStore.backupPackageCodePath(this, packageName, sourceDir);
@@ -415,6 +417,10 @@
isApex, false /* isApkInApex */, new ArrayList<>(), rollbackDataPolicy);
info.getPackages().add(packageRollbackInfo);
+
+ if (info.getRollbackImpactLevel() < rollbackImpactLevel) {
+ info.setRollbackImpactLevel(rollbackImpactLevel);
+ }
return true;
}
@@ -961,7 +967,8 @@
for (PackageRollbackInfo pkg : info.getPackages()) {
ipw.println(pkg.getPackageName()
+ " " + pkg.getVersionRolledBackFrom().getLongVersionCode()
- + " -> " + pkg.getVersionRolledBackTo().getLongVersionCode());
+ + " -> " + pkg.getVersionRolledBackTo().getLongVersionCode()
+ + " [" + pkg.getRollbackDataPolicy() + "]");
}
ipw.decreaseIndent();
if (isCommitted()) {
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 13f1141..359678b 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -954,7 +954,7 @@
ApplicationInfo appInfo = pkgInfo.applicationInfo;
return rollback.enableForPackage(packageName, newPackage.getVersionCode(),
pkgInfo.getLongVersionCode(), isApex, appInfo.sourceDir,
- appInfo.splitSourceDirs, rollbackDataPolicy);
+ appInfo.splitSourceDirs, rollbackDataPolicy, session.rollbackImpactLevel);
}
@ExtThread
diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java
index 0af137f..14539d5 100644
--- a/services/core/java/com/android/server/rollback/RollbackStore.java
+++ b/services/core/java/com/android/server/rollback/RollbackStore.java
@@ -193,16 +193,27 @@
json.put("isStaged", rollback.isStaged());
json.put("causePackages", versionedPackagesToJson(rollback.getCausePackages()));
json.put("committedSessionId", rollback.getCommittedSessionId());
+ if (Flags.recoverabilityDetection()) {
+ json.put("rollbackImpactLevel", rollback.getRollbackImpactLevel());
+ }
return json;
}
private static RollbackInfo rollbackInfoFromJson(JSONObject json) throws JSONException {
- return new RollbackInfo(
+ RollbackInfo rollbackInfo = new RollbackInfo(
json.getInt("rollbackId"),
packageRollbackInfosFromJson(json.getJSONArray("packages")),
json.getBoolean("isStaged"),
versionedPackagesFromJson(json.getJSONArray("causePackages")),
json.getInt("committedSessionId"));
+
+ if (Flags.recoverabilityDetection()) {
+ // to make it backward compatible.
+ rollbackInfo.setRollbackImpactLevel(json.optInt("rollbackImpactLevel",
+ PackageManager.ROLLBACK_USER_IMPACT_LOW));
+ }
+
+ return rollbackInfo;
}
/**
diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
index b384725..92b5764 100755
--- a/services/core/java/com/android/server/tv/TvInputHardwareManager.java
+++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
@@ -309,6 +309,24 @@
}
}
+ public SparseArray<String> getHardwareInputIdMap() {
+ synchronized (mLock) {
+ return mHardwareInputIdMap.clone();
+ }
+ }
+
+ public SparseArray<String> getHdmiInputIdMap() {
+ synchronized (mLock) {
+ return mHdmiInputIdMap.clone();
+ }
+ }
+
+ public Map<String, TvInputInfo> getInputMap() {
+ synchronized (mLock) {
+ return Collections.unmodifiableMap(mInputMap);
+ }
+ }
+
public Map<String, List<String>> getHdmiParentInputMap() {
synchronized (mLock) {
return Collections.unmodifiableMap(mHdmiParentInputMap);
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 73fc8e9..e434df7 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -135,6 +135,7 @@
private static final int APP_TAG_SELF = TunedInfo.APP_TAG_SELF;
private static final String PERMISSION_ACCESS_WATCHED_PROGRAMS =
"com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS";
+ private static final long UPDATE_HARDWARE_TIS_BINDING_DELAY_IN_MILLIS = 10 * 1000; // 10 seconds
// There are two different formats of DVB frontend devices. One is /dev/dvb%d.frontend%d,
// another one is /dev/dvb/adapter%d/frontend%d. Followings are the patterns for selecting the
@@ -174,7 +175,7 @@
@GuardedBy("mLock")
private final Map<String, SessionState> mSessionIdToSessionStateMap = new HashMap<>();
- private final WatchLogHandler mWatchLogHandler;
+ private final MessageHandler mMessageHandler;
private final ActivityManager mActivityManager;
@@ -187,8 +188,8 @@
super(context);
mContext = context;
- mWatchLogHandler = new WatchLogHandler(mContext.getContentResolver(),
- IoThread.get().getLooper());
+ mMessageHandler =
+ new MessageHandler(mContext.getContentResolver(), IoThread.get().getLooper());
mTvInputHardwareManager = new TvInputHardwareManager(context, new HardwareListener());
mActivityManager =
@@ -372,10 +373,10 @@
// service to populate the hardware list.
serviceState = new ServiceState(component, userId);
userState.serviceStateMap.put(component, serviceState);
+ updateServiceConnectionLocked(component, userId);
} else {
inputList.addAll(serviceState.hardwareInputMap.values());
}
- updateServiceConnectionLocked(component, userId);
} else {
try {
TvInputInfo info = new TvInputInfo.Builder(mContext, ri).build();
@@ -510,6 +511,7 @@
}
}
+ @GuardedBy("mLock")
private void startProfileLocked(int userId) {
mRunningProfiles.add(userId);
buildTvInputListLocked(userId, null);
@@ -538,8 +540,10 @@
mCurrentUserId = userId;
buildTvInputListLocked(userId, null);
buildTvContentRatingSystemListLocked(userId);
- mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_SWITCH_CONTENT_RESOLVER,
- getContentResolverForUser(userId)).sendToTarget();
+ mMessageHandler
+ .obtainMessage(MessageHandler.MSG_SWITCH_CONTENT_RESOLVER,
+ getContentResolverForUser(userId))
+ .sendToTarget();
}
}
@@ -593,7 +597,7 @@
Slog.e(TAG, "error in unregisterCallback", e);
}
}
- mContext.unbindService(serviceState.connection);
+ unbindService(serviceState);
it.remove();
}
}
@@ -661,7 +665,7 @@
Slog.e(TAG, "error in unregisterCallback", e);
}
}
- mContext.unbindService(serviceState.connection);
+ unbindService(serviceState);
}
}
userState.serviceStateMap.clear();
@@ -774,7 +778,8 @@
boolean shouldBind;
if (userId == mCurrentUserId || mRunningProfiles.contains(userId)) {
- shouldBind = !serviceState.sessionTokens.isEmpty() || serviceState.isHardware;
+ shouldBind = !serviceState.sessionTokens.isEmpty()
+ || (serviceState.isHardware && serviceState.neverConnected);
} else {
// For a non-current user,
// if sessionTokens is not empty, it contains recording sessions only
@@ -783,31 +788,14 @@
shouldBind = !serviceState.sessionTokens.isEmpty();
}
- if (serviceState.service == null && shouldBind) {
- // This means that the service is not yet connected but its state indicates that we
- // have pending requests. Then, connect the service.
- if (serviceState.bound) {
- // We have already bound to the service so we don't try to bind again until after we
- // unbind later on.
- return;
+ // only bind/unbind when necessary.
+ if (shouldBind && !serviceState.bound) {
+ bindService(serviceState, userId);
+ } else if (!shouldBind && serviceState.bound) {
+ unbindService(serviceState);
+ if (!serviceState.isHardware) {
+ userState.serviceStateMap.remove(component);
}
- if (DEBUG) {
- Slog.d(TAG, "bindServiceAsUser(service=" + component + ", userId=" + userId + ")");
- }
-
- Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(component);
- serviceState.bound = mContext.bindServiceAsUser(
- i, serviceState.connection,
- Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
- new UserHandle(userId));
- } else if (serviceState.service != null && !shouldBind) {
- // This means that the service is already connected but its state indicates that we have
- // nothing to do with it. Then, disconnect the service.
- if (DEBUG) {
- Slog.d(TAG, "unbindService(service=" + component + ")");
- }
- mContext.unbindService(serviceState.connection);
- userState.serviceStateMap.remove(component);
}
}
@@ -829,7 +817,11 @@
sendSessionTokenToClientLocked(sessionState.client,
sessionState.inputId, null, null, sessionState.seq);
}
- updateServiceConnectionLocked(serviceState.component, userId);
+ if (!serviceState.isHardware) {
+ updateServiceConnectionLocked(serviceState.component, userId);
+ } else {
+ updateHardwareServiceConnectionDelayed(userId);
+ }
}
@GuardedBy("mLock")
@@ -948,13 +940,17 @@
if (serviceState != null) {
serviceState.sessionTokens.remove(sessionToken);
}
- updateServiceConnectionLocked(sessionState.componentName, userId);
+ if (!serviceState.isHardware) {
+ updateServiceConnectionLocked(sessionState.componentName, userId);
+ } else {
+ updateHardwareServiceConnectionDelayed(userId);
+ }
// Log the end of watch.
SomeArgs args = SomeArgs.obtain();
args.arg1 = sessionToken;
args.arg2 = System.currentTimeMillis();
- mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_LOG_WATCH_END, args).sendToTarget();
+ mMessageHandler.obtainMessage(MessageHandler.MSG_LOG_WATCH_END, args).sendToTarget();
}
@GuardedBy("mLock")
@@ -1153,8 +1149,7 @@
ServiceState serviceState = userState.serviceStateMap.get(inputState.info.getComponent());
int oldState = inputState.state;
inputState.state = state;
- if (serviceState != null && serviceState.service == null
- && (!serviceState.sessionTokens.isEmpty() || serviceState.isHardware)) {
+ if (serviceState != null && serviceState.reconnecting) {
// We don't notify state change while reconnecting. It should remain disconnected.
return;
}
@@ -1881,7 +1876,7 @@
args.arg3 = ContentUris.parseId(channelUri);
args.arg4 = params;
args.arg5 = sessionToken;
- mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_LOG_WATCH_START, args)
+ mMessageHandler.obtainMessage(MessageHandler.MSG_LOG_WATCH_START, args)
.sendToTarget();
} catch (RemoteException | SessionNotFoundException e) {
Slog.e(TAG, "error in tune", e);
@@ -3327,16 +3322,21 @@
private final ComponentName component;
private final boolean isHardware;
private final Map<String, TvInputInfo> hardwareInputMap = new HashMap<>();
+ private final List<TvInputHardwareInfo> hardwareDeviceRemovedBuffer = new ArrayList<>();
+ private final List<HdmiDeviceInfo> hdmiDeviceRemovedBuffer = new ArrayList<>();
+ private final List<HdmiDeviceInfo> hdmiDeviceUpdatedBuffer = new ArrayList<>();
private ITvInputService service;
private ServiceCallback callback;
private boolean bound;
private boolean reconnecting;
+ private boolean neverConnected;
private ServiceState(ComponentName component, int userId) {
this.component = component;
this.connection = new InputServiceConnection(component, userId);
this.isHardware = hasHardwarePermission(mContext.getPackageManager(), component);
+ this.neverConnected = true;
}
}
@@ -3449,6 +3449,97 @@
}
}
+ @GuardedBy("mLock")
+ private void bindService(ServiceState serviceState, int userId) {
+ if (serviceState.bound) {
+ // We have already bound to the service so we don't try to bind again until after we
+ // unbind later on.
+ // For hardware services, call updateHardwareServiceConnectionDelayed() to delay the
+ // possible unbinding.
+ if (serviceState.isHardware) {
+ updateHardwareServiceConnectionDelayed(userId);
+ }
+ return;
+ }
+ if (DEBUG) {
+ Slog.d(TAG,
+ "bindServiceAsUser(service=" + serviceState.component + ", userId=" + userId
+ + ")");
+ }
+ Intent i =
+ new Intent(TvInputService.SERVICE_INTERFACE).setComponent(serviceState.component);
+ serviceState.bound = mContext.bindServiceAsUser(i, serviceState.connection,
+ Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
+ new UserHandle(userId));
+ if (!serviceState.bound) {
+ Slog.e(TAG, "failed to bind " + serviceState.component + " for userId " + userId);
+ mContext.unbindService(serviceState.connection);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void unbindService(ServiceState serviceState) {
+ if (!serviceState.bound) {
+ return;
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "unbindService(service=" + serviceState.component + ")");
+ }
+ mContext.unbindService(serviceState.connection);
+ serviceState.bound = false;
+ serviceState.service = null;
+ serviceState.callback = null;
+ }
+
+ @GuardedBy("mLock")
+ private void updateHardwareTvInputServiceBindingLocked(int userId) {
+ PackageManager pm = mContext.getPackageManager();
+ List<ResolveInfo> services =
+ pm.queryIntentServicesAsUser(new Intent(TvInputService.SERVICE_INTERFACE),
+ PackageManager.GET_SERVICES | PackageManager.GET_META_DATA, userId);
+ for (ResolveInfo ri : services) {
+ ServiceInfo si = ri.serviceInfo;
+ if (!android.Manifest.permission.BIND_TV_INPUT.equals(si.permission)) {
+ continue;
+ }
+ ComponentName component = new ComponentName(si.packageName, si.name);
+ if (hasHardwarePermission(pm, component)) {
+ updateServiceConnectionLocked(component, userId);
+ }
+ }
+ }
+
+ private void updateHardwareServiceConnectionDelayed(int userId) {
+ mMessageHandler.removeMessages(MessageHandler.MSG_UPDATE_HARDWARE_TIS_BINDING);
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = userId;
+ Message msg =
+ mMessageHandler.obtainMessage(MessageHandler.MSG_UPDATE_HARDWARE_TIS_BINDING, args);
+ mMessageHandler.sendMessageDelayed(msg, UPDATE_HARDWARE_TIS_BINDING_DELAY_IN_MILLIS);
+ }
+
+ @GuardedBy("mLock")
+ private void addHardwareInputLocked(
+ TvInputInfo inputInfo, ComponentName component, int userId) {
+ ServiceState serviceState = getServiceStateLocked(component, userId);
+ serviceState.hardwareInputMap.put(inputInfo.getId(), inputInfo);
+ buildTvInputListLocked(userId, null);
+ }
+
+ @GuardedBy("mLock")
+ private void removeHardwareInputLocked(String inputId, int userId) {
+ if (!mTvInputHardwareManager.getInputMap().containsKey(inputId)) {
+ return;
+ }
+ ComponentName component = mTvInputHardwareManager.getInputMap().get(inputId).getComponent();
+ ServiceState serviceState = getServiceStateLocked(component, userId);
+ boolean removed = serviceState.hardwareInputMap.remove(inputId) != null;
+ if (removed) {
+ buildTvInputListLocked(userId, null);
+ mTvInputHardwareManager.removeHardwareInput(inputId);
+ }
+ }
+
private final class InputServiceConnection implements ServiceConnection {
private final ComponentName mComponent;
private final int mUserId;
@@ -3472,6 +3563,7 @@
}
ServiceState serviceState = userState.serviceStateMap.get(mComponent);
serviceState.service = ITvInputService.Stub.asInterface(service);
+ serviceState.neverConnected = false;
// Register a callback, if we need to.
if (serviceState.isHardware && serviceState.callback == null) {
@@ -3483,6 +3575,58 @@
}
}
+ for (TvInputState inputState : userState.inputMap.values()) {
+ if (inputState.info.getComponent().equals(component)
+ && inputState.state != INPUT_STATE_CONNECTED) {
+ notifyInputStateChangedLocked(userState, inputState.info.getId(),
+ inputState.state, null);
+ }
+ }
+
+ if (serviceState.isHardware) {
+ for (TvInputHardwareInfo hardwareToBeRemoved :
+ serviceState.hardwareDeviceRemovedBuffer) {
+ try {
+ serviceState.service.notifyHardwareRemoved(hardwareToBeRemoved);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in hardwareDeviceRemovedBuffer", e);
+ }
+ }
+ serviceState.hardwareDeviceRemovedBuffer.clear();
+ for (HdmiDeviceInfo hdmiDeviceToBeRemoved :
+ serviceState.hdmiDeviceRemovedBuffer) {
+ try {
+ serviceState.service.notifyHdmiDeviceRemoved(hdmiDeviceToBeRemoved);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in hdmiDeviceRemovedBuffer", e);
+ }
+ }
+ serviceState.hdmiDeviceRemovedBuffer.clear();
+ for (TvInputHardwareInfo hardware : mTvInputHardwareManager.getHardwareList()) {
+ try {
+ serviceState.service.notifyHardwareAdded(hardware);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in notifyHardwareAdded", e);
+ }
+ }
+ for (HdmiDeviceInfo device : mTvInputHardwareManager.getHdmiDeviceList()) {
+ try {
+ serviceState.service.notifyHdmiDeviceAdded(device);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
+ }
+ }
+ for (HdmiDeviceInfo hdmiDeviceToBeUpdated :
+ serviceState.hdmiDeviceUpdatedBuffer) {
+ try {
+ serviceState.service.notifyHdmiDeviceUpdated(hdmiDeviceToBeUpdated);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in hdmiDeviceUpdatedBuffer", e);
+ }
+ }
+ serviceState.hdmiDeviceUpdatedBuffer.clear();
+ }
+
List<IBinder> tokensToBeRemoved = new ArrayList<>();
// And create sessions, if any.
@@ -3496,30 +3640,8 @@
removeSessionStateLocked(sessionToken, mUserId);
}
- for (TvInputState inputState : userState.inputMap.values()) {
- if (inputState.info.getComponent().equals(component)
- && inputState.state != INPUT_STATE_CONNECTED) {
- notifyInputStateChangedLocked(userState, inputState.info.getId(),
- inputState.state, null);
- }
- }
-
if (serviceState.isHardware) {
- serviceState.hardwareInputMap.clear();
- for (TvInputHardwareInfo hardware : mTvInputHardwareManager.getHardwareList()) {
- try {
- serviceState.service.notifyHardwareAdded(hardware);
- } catch (RemoteException e) {
- Slog.e(TAG, "error in notifyHardwareAdded", e);
- }
- }
- for (HdmiDeviceInfo device : mTvInputHardwareManager.getHdmiDeviceList()) {
- try {
- serviceState.service.notifyHdmiDeviceAdded(device);
- } catch (RemoteException e) {
- Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
- }
- }
+ updateHardwareServiceConnectionDelayed(mUserId);
}
}
}
@@ -3570,13 +3692,6 @@
}
}
- @GuardedBy("mLock")
- private void addHardwareInputLocked(TvInputInfo inputInfo) {
- ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
- serviceState.hardwareInputMap.put(inputInfo.getId(), inputInfo);
- buildTvInputListLocked(mUserId, null);
- }
-
public void addHardwareInput(int deviceId, TvInputInfo inputInfo) {
ensureHardwarePermission();
ensureValidInput(inputInfo);
@@ -3587,8 +3702,11 @@
if (serviceState.hardwareInputMap.containsKey(inputInfo.getId())) {
return;
}
+ Slog.d("ServiceCallback",
+ "addHardwareInput: device id " + deviceId + ", "
+ + inputInfo.toString());
mTvInputHardwareManager.addHardwareInput(deviceId, inputInfo);
- addHardwareInputLocked(inputInfo);
+ addHardwareInputLocked(inputInfo, mComponent, mUserId);
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -3606,7 +3724,7 @@
return;
}
mTvInputHardwareManager.addHdmiInput(id, inputInfo);
- addHardwareInputLocked(inputInfo);
+ addHardwareInputLocked(inputInfo, mComponent, mUserId);
if (mOnScreenInputId != null && mOnScreenSessionState != null) {
if (TextUtils.equals(mOnScreenInputId, inputInfo.getParentId())) {
// catch the use case when a CEC device is plugged in an HDMI port,
@@ -3635,14 +3753,9 @@
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
- boolean removed = serviceState.hardwareInputMap.remove(inputId) != null;
- if (removed) {
- buildTvInputListLocked(mUserId, null);
- mTvInputHardwareManager.removeHardwareInput(inputId);
- } else {
- Slog.e(TAG, "failed to remove input " + inputId);
- }
+ Slog.d("ServiceCallback",
+ "removeHardwareInput " + inputId + " by " + mComponent);
+ removeHardwareInputLocked(inputId, mUserId);
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -3860,6 +3973,23 @@
}
@Override
+ public void onVideoFreezeUpdated(boolean isFrozen) {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slog.d(TAG, "onVideoFreezeUpdated(" + isFrozen + ")");
+ }
+ if (mSessionState.session == null || mSessionState.client == null) {
+ return;
+ }
+ try {
+ mSessionState.client.onVideoFreezeUpdated(isFrozen, mSessionState.seq);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in onVideoFreezeUpdated", e);
+ }
+ }
+ }
+
+ @Override
public void onContentAllowed() {
synchronized (mLock) {
if (DEBUG) {
@@ -4209,11 +4339,12 @@
return loggedReason;
}
+ @GuardedBy("mLock")
private UserState getUserStateLocked(int userId) {
return mUserStates.get(userId);
}
- private static final class WatchLogHandler extends Handler {
+ private final class MessageHandler extends Handler {
// There are only two kinds of watch events that can happen on the system:
// 1. The current TV input session is tuned to a new channel.
// 2. The session is released for some reason.
@@ -4225,10 +4356,11 @@
static final int MSG_LOG_WATCH_START = 1;
static final int MSG_LOG_WATCH_END = 2;
static final int MSG_SWITCH_CONTENT_RESOLVER = 3;
+ static final int MSG_UPDATE_HARDWARE_TIS_BINDING = 4;
private ContentResolver mContentResolver;
- WatchLogHandler(ContentResolver contentResolver, Looper looper) {
+ MessageHandler(ContentResolver contentResolver, Looper looper) {
super(looper);
mContentResolver = contentResolver;
}
@@ -4287,6 +4419,14 @@
mContentResolver = (ContentResolver) msg.obj;
break;
}
+ case MSG_UPDATE_HARDWARE_TIS_BINDING:
+ SomeArgs args = (SomeArgs) msg.obj;
+ int userId = (int) args.arg1;
+ synchronized (mLock) {
+ updateHardwareTvInputServiceBindingLocked(userId);
+ }
+ args.recycle();
+ break;
default: {
Slog.w(TAG, "unhandled message code: " + msg.what);
break;
@@ -4342,29 +4482,46 @@
UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
// Broadcast the event to all hardware inputs.
for (ServiceState serviceState : userState.serviceStateMap.values()) {
- if (!serviceState.isHardware || serviceState.service == null) continue;
+ if (!serviceState.isHardware) {
+ continue;
+ }
try {
- serviceState.service.notifyHardwareAdded(info);
+ bindService(serviceState, mCurrentUserId);
+ if (serviceState.service != null) {
+ serviceState.service.notifyHardwareAdded(info);
+ }
} catch (RemoteException e) {
Slog.e(TAG, "error in notifyHardwareAdded", e);
}
}
+ updateHardwareServiceConnectionDelayed(mCurrentUserId);
}
}
@Override
public void onHardwareDeviceRemoved(TvInputHardwareInfo info) {
synchronized (mLock) {
+ String relatedInputId =
+ mTvInputHardwareManager.getHardwareInputIdMap().get(info.getDeviceId());
+ removeHardwareInputLocked(relatedInputId, mCurrentUserId);
UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
// Broadcast the event to all hardware inputs.
for (ServiceState serviceState : userState.serviceStateMap.values()) {
- if (!serviceState.isHardware || serviceState.service == null) continue;
+ if (!serviceState.isHardware) {
+ continue;
+ }
try {
- serviceState.service.notifyHardwareRemoved(info);
+ bindService(serviceState, mCurrentUserId);
+ if (serviceState.service != null) {
+ serviceState.service.notifyHardwareRemoved(info);
+ } else {
+ serviceState.hardwareDeviceRemovedBuffer.add(info);
+ }
} catch (RemoteException e) {
Slog.e(TAG, "error in notifyHardwareRemoved", e);
}
}
+ updateHardwareServiceConnectionDelayed(mCurrentUserId);
}
}
@@ -4374,29 +4531,46 @@
UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
// Broadcast the event to all hardware inputs.
for (ServiceState serviceState : userState.serviceStateMap.values()) {
- if (!serviceState.isHardware || serviceState.service == null) continue;
+ if (!serviceState.isHardware) {
+ continue;
+ }
try {
- serviceState.service.notifyHdmiDeviceAdded(deviceInfo);
+ bindService(serviceState, mCurrentUserId);
+ if (serviceState.service != null) {
+ serviceState.service.notifyHdmiDeviceAdded(deviceInfo);
+ }
} catch (RemoteException e) {
Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
}
}
+ updateHardwareServiceConnectionDelayed(mCurrentUserId);
}
}
@Override
public void onHdmiDeviceRemoved(HdmiDeviceInfo deviceInfo) {
synchronized (mLock) {
+ String relatedInputId =
+ mTvInputHardwareManager.getHdmiInputIdMap().get(deviceInfo.getId());
+ removeHardwareInputLocked(relatedInputId, mCurrentUserId);
UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
// Broadcast the event to all hardware inputs.
for (ServiceState serviceState : userState.serviceStateMap.values()) {
- if (!serviceState.isHardware || serviceState.service == null) continue;
+ if (!serviceState.isHardware) {
+ continue;
+ }
try {
- serviceState.service.notifyHdmiDeviceRemoved(deviceInfo);
+ bindService(serviceState, mCurrentUserId);
+ if (serviceState.service != null) {
+ serviceState.service.notifyHdmiDeviceRemoved(deviceInfo);
+ } else {
+ serviceState.hdmiDeviceRemovedBuffer.add(deviceInfo);
+ }
} catch (RemoteException e) {
Slog.e(TAG, "error in notifyHdmiDeviceRemoved", e);
}
}
+ updateHardwareServiceConnectionDelayed(mCurrentUserId);
}
}
@@ -4424,13 +4598,21 @@
UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
// Broadcast the event to all hardware inputs.
for (ServiceState serviceState : userState.serviceStateMap.values()) {
- if (!serviceState.isHardware || serviceState.service == null) continue;
+ if (!serviceState.isHardware) {
+ continue;
+ }
try {
- serviceState.service.notifyHdmiDeviceUpdated(deviceInfo);
+ bindService(serviceState, mCurrentUserId);
+ if (serviceState.service != null) {
+ serviceState.service.notifyHdmiDeviceUpdated(deviceInfo);
+ } else {
+ serviceState.hdmiDeviceUpdatedBuffer.add(deviceInfo);
+ }
} catch (RemoteException e) {
Slog.e(TAG, "error in notifyHdmiDeviceUpdated", e);
}
}
+ updateHardwareServiceConnectionDelayed(mCurrentUserId);
}
}
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 7ab075e..f0c8437 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -1038,7 +1038,7 @@
}
} finally {
if (surface != null) {
- // surface is not used in TvInteractiveAppManagerService.
+ // surface is not used in TvAdManagerService.
surface.release();
}
Binder.restoreCallingIdentity(identity);
@@ -1106,6 +1106,67 @@
}
}
+ @Override
+ public void createMediaView(IBinder sessionToken, IBinder windowToken, Rect frame,
+ int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "createMediaView");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getAdSessionLocked(sessionToken, callingUid, resolvedUserId)
+ .createMediaView(windowToken, frame);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slog.e(TAG, "error in createMediaView", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void relayoutMediaView(IBinder sessionToken, Rect frame, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "relayoutMediaView");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getAdSessionLocked(sessionToken, callingUid, resolvedUserId)
+ .relayoutMediaView(frame);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slog.e(TAG, "error in relayoutMediaView", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void removeMediaView(IBinder sessionToken, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "removeMediaView");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getAdSessionLocked(sessionToken, callingUid, resolvedUserId)
+ .removeMediaView();
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slog.e(TAG, "error in removeMediaView", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
}
private final class BinderService extends ITvInteractiveAppManager.Stub {
@@ -1470,6 +1531,28 @@
}
@Override
+ public void notifyVideoFreezeUpdated(IBinder sessionToken, boolean isFrozen, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId,
+ "notifyVideoFreezeUpdated");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ getSessionLocked(sessionState).notifyVideoFreezeUpdated(isFrozen);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in notifyVideoFreezeUpdated", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void notifyContentAllowed(IBinder sessionToken, int userId) {
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
@@ -1557,7 +1640,6 @@
}
}
-
@Override
public void notifyRecordingStarted(IBinder sessionToken, String recordingId,
String requestId, int userId) {
diff --git a/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java b/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java
index f1a2159..db27f60 100644
--- a/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java
+++ b/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java
@@ -43,7 +43,7 @@
static final String DOC_LINK = "go/android-asm";
/** Used to determine which version of the ASM logic was used in logs while we iterate */
- static final int ASM_VERSION = 8;
+ static final int ASM_VERSION = 9;
private static final String NAMESPACE = NAMESPACE_WINDOW_MANAGER;
private static final String KEY_ASM_PREFIX = "ActivitySecurity__";
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index f6d77ea..d6f52b8 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2053,8 +2053,8 @@
}
if (!mSupervisor.getBackgroundActivityLaunchController().checkActivityAllowedToStart(
- mSourceRecord, r, newTask, targetTask, mLaunchFlags, mBalCode, mCallingUid,
- mRealCallingUid)) {
+ mSourceRecord, r, newTask, avoidMoveToFront(), targetTask, mLaunchFlags, mBalCode,
+ mCallingUid, mRealCallingUid)) {
return START_ABORTED;
}
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 0f36d8e..39dd77e 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -57,6 +57,7 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Process;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.util.ArraySet;
@@ -1042,8 +1043,9 @@
* create a new task or bring an existing one into the foreground
*/
boolean checkActivityAllowedToStart(@Nullable ActivityRecord sourceRecord,
- @NonNull ActivityRecord targetRecord, boolean newTask, @NonNull Task targetTask,
- int launchFlags, int balCode, int callingUid, int realCallingUid) {
+ @NonNull ActivityRecord targetRecord, boolean newTask, boolean avoidMoveTaskToFront,
+ @Nullable Task targetTask, int launchFlags, int balCode, int callingUid,
+ int realCallingUid) {
// BAL Exception allowed in all cases
if (balCode == BAL_ALLOW_ALLOWLISTED_UID) {
return true;
@@ -1067,14 +1069,36 @@
}
if (balCode == BAL_ALLOW_GRACE_PERIOD) {
+ // Allow if launching into new task, and caller matches most recently finished activity
if (taskToFront && mTopFinishedActivity != null
&& mTopFinishedActivity.mUid == callingUid) {
return true;
- } else if (!taskToFront) {
- FinishedActivityEntry finishedEntry =
- mTaskIdToFinishedActivity.get(targetTask.mTaskId);
- if (finishedEntry != null && finishedEntry.mUid == callingUid) {
- return true;
+ }
+
+ // Launching into existing task - allow if matches most recently finished activity
+ // within the task.
+ // We can reach here multiple ways:
+ // 1. activity in fg fires intent (taskToFront = false, sourceRecord is available)
+ // 2. activity in bg fires intent (taskToFront = false, sourceRecord is available)
+ // 3. activity in bg fires intent with NEW_FLAG (taskToFront = true,
+ // avoidMoveTaskToFront = true, sourceRecord is available)
+ // 4. activity in bg fires PI (taskToFront = true, avoidMoveTaskToFront = true,
+ // sourceRecord is not available, targetTask may be available)
+ if (!taskToFront || avoidMoveTaskToFront) {
+ if (targetTask != null) {
+ FinishedActivityEntry finishedEntry =
+ mTaskIdToFinishedActivity.get(targetTask.mTaskId);
+ if (finishedEntry != null && finishedEntry.mUid == callingUid) {
+ return true;
+ }
+ }
+
+ if (sourceRecord != null) {
+ FinishedActivityEntry finishedEntry =
+ mTaskIdToFinishedActivity.get(sourceRecord.getTask().mTaskId);
+ if (finishedEntry != null && finishedEntry.mUid == callingUid) {
+ return true;
+ }
}
}
}
@@ -1098,7 +1122,7 @@
bas = isTopActivityMatchingUidAbsentForAsm(taskToCheck, sourceRecord.getUid(),
sourceRecord);
}
- } else if (!taskToFront) {
+ } else if (targetTask != null && (!taskToFront || avoidMoveTaskToFront)) {
// We don't have a sourceRecord, and we're launching into an existing task.
// Allow if callingUid is top of stack.
bas = isTopActivityMatchingUidAbsentForAsm(targetTask, callingUid,
@@ -1111,12 +1135,14 @@
// ASM rules have failed. Log why
return logAsmFailureAndCheckFeatureEnabled(sourceRecord, callingUid, realCallingUid,
- newTask, targetTask, targetRecord, balCode, launchFlags, bas, taskToFront);
+ newTask, avoidMoveTaskToFront, targetTask, targetRecord, balCode, launchFlags,
+ bas, taskToFront);
}
private boolean logAsmFailureAndCheckFeatureEnabled(ActivityRecord sourceRecord, int callingUid,
- int realCallingUid, boolean newTask, Task targetTask, ActivityRecord targetRecord,
- @BalCode int balCode, int launchFlags, BlockActivityStart bas, boolean taskToFront) {
+ int realCallingUid, boolean newTask, boolean avoidMoveTaskToFront, Task targetTask,
+ ActivityRecord targetRecord, @BalCode int balCode, int launchFlags,
+ BlockActivityStart bas, boolean taskToFront) {
ActivityRecord targetTopActivity = targetTask == null ? null
: targetTask.getActivity(ar -> !ar.finishing && !ar.isAlwaysOnTop());
@@ -1133,7 +1159,7 @@
String asmDebugInfo = getDebugInfoForActivitySecurity("Launch", sourceRecord,
targetRecord, targetTask, targetTopActivity, realCallingUid, balCode,
- blockActivityStartAndFeatureEnabled, taskToFront);
+ blockActivityStartAndFeatureEnabled, taskToFront, avoidMoveTaskToFront);
FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED,
/* caller_uid */
@@ -1265,7 +1291,7 @@
Slog.i(TAG, getDebugInfoForActivitySecurity("Clear Top", sourceRecord, targetRecord,
targetTask, targetTaskTop, realCallingUid, balCode, shouldBlockActivityStart,
- /* taskToFront */ true));
+ /* taskToFront */ true, /* avoidMoveTaskToFront */ false));
}
}
@@ -1379,7 +1405,7 @@
private BlockActivityStart isTopActivityMatchingUidAbsentForAsm(@NonNull Task task,
int uid, @Nullable ActivityRecord sourceRecord) {
// If the source is visible, consider it 'top'.
- if (sourceRecord != null && sourceRecord.isVisible()) {
+ if (sourceRecord != null && sourceRecord.isVisibleRequested()) {
return new BlockActivityStart(false, false);
}
@@ -1389,6 +1415,12 @@
return new BlockActivityStart(false, false);
}
+ // If UID is visible in target task, allow launch
+ if (task.forAllActivities((Predicate<ActivityRecord>)
+ ar -> ar.isUid(uid) && ar.isVisibleRequested())) {
+ return new BlockActivityStart(false, false);
+ }
+
// Consider the source activity, whether or not it is finishing. Do not consider any other
// finishing activity.
Predicate<ActivityRecord> topOfStackPredicate = (ar) -> ar.equals(sourceRecord)
@@ -1480,27 +1512,26 @@
@Nullable ActivityRecord sourceRecord, @NonNull ActivityRecord targetRecord,
@Nullable Task targetTask, @Nullable ActivityRecord targetTopActivity,
int realCallingUid, @BalCode int balCode,
- boolean blockActivityStartAndFeatureEnabled, boolean taskToFront) {
+ boolean blockActivityStartAndFeatureEnabled, boolean taskToFront,
+ boolean avoidMoveTaskToFront) {
final String prefix = "[ASM] ";
Function<ActivityRecord, String> recordToString = (ar) -> {
if (ar == null) {
return null;
}
- return (ar == sourceRecord ? " [source]=> "
+
+ return (ar == sourceRecord ? " [source]=> "
: ar == targetTopActivity ? " [ top ]=> "
- : ar == targetRecord ? " [target]=> "
- : " => ")
- + ar
- + " :: visible=" + ar.isVisible()
- + ", finishing=" + ar.isFinishing()
- + ", alwaysOnTop=" + ar.isAlwaysOnTop()
- + ", taskFragment=" + ar.getTaskFragment();
+ : ar == targetRecord ? " [target]=> "
+ : " => ")
+ + getDebugStringForActivityRecord(ar);
};
StringJoiner joiner = new StringJoiner("\n");
joiner.add(prefix + "------ Activity Security " + action + " Debug Logging Start ------");
joiner.add(prefix + "Block Enabled: " + blockActivityStartAndFeatureEnabled);
joiner.add(prefix + "ASM Version: " + ActivitySecurityModelFeatureFlags.ASM_VERSION);
+ joiner.add(prefix + "System Time: " + SystemClock.uptimeMillis());
boolean targetTaskMatchesSourceTask = targetTask != null
&& sourceRecord != null && sourceRecord.getTask() == targetTask;
@@ -1512,6 +1543,8 @@
joiner.add(prefix + "Real Calling Uid Package: " + realCallingPackage);
} else {
joiner.add(prefix + "Source Record: " + recordToString.apply(sourceRecord));
+ joiner.add(prefix + "Source Launch Package: " + sourceRecord.launchedFromPackage);
+ joiner.add(prefix + "Source Launch Intent: " + sourceRecord.intent);
if (targetTaskMatchesSourceTask) {
joiner.add(prefix + "Source/Target Task: " + sourceRecord.getTask());
joiner.add(prefix + "Source/Target Task Stack: ");
@@ -1536,7 +1569,30 @@
joiner.add(prefix + "Target Record: " + recordToString.apply(targetRecord));
joiner.add(prefix + "Intent: " + targetRecord.intent);
joiner.add(prefix + "TaskToFront: " + taskToFront);
+ joiner.add(prefix + "AvoidMoveToFront: " + avoidMoveTaskToFront);
joiner.add(prefix + "BalCode: " + balCodeToString(balCode));
+ joiner.add(prefix + "LastResumedActivity: "
+ + recordToString.apply(mService.mLastResumedActivity));
+
+ if (mTopFinishedActivity != null) {
+ joiner.add(prefix + "TopFinishedActivity: " + mTopFinishedActivity.mDebugInfo);
+ }
+
+ if (!mTaskIdToFinishedActivity.isEmpty()) {
+ joiner.add(prefix + "TaskIdToFinishedActivity: ");
+ mTaskIdToFinishedActivity.values().forEach(
+ (fae) -> joiner.add(prefix + " " + fae.mDebugInfo));
+ }
+
+ if (balCode == BAL_ALLOW_VISIBLE_WINDOW || balCode == BAL_ALLOW_NON_APP_VISIBLE_WINDOW
+ || balCode == BAL_ALLOW_FOREGROUND) {
+ Task task = sourceRecord != null ? sourceRecord.getTask() : targetTask;
+ if (task != null && task.getDisplayArea() != null) {
+ joiner.add(prefix + "Tasks: ");
+ task.getDisplayArea().forAllTasks((Consumer<Task>)
+ t -> joiner.add(prefix + " T: " + t.toFullString()));
+ }
+ }
joiner.add(prefix + "------ Activity Security " + action + " Debug Logging End ------");
return joiner.toString();
@@ -1620,7 +1676,7 @@
return;
}
- if (!finishActivity.mVisibleRequested
+ if (!finishActivity.isVisibleRequested()
&& finishActivity != finishActivity.getTask().getTopMostActivity()) {
return;
}
@@ -1666,10 +1722,22 @@
}
}
+ private static String getDebugStringForActivityRecord(ActivityRecord ar) {
+ return ar
+ + " :: visible=" + ar.isVisible()
+ + ", visibleRequested=" + ar.isVisibleRequested()
+ + ", finishing=" + ar.isFinishing()
+ + ", alwaysOnTop=" + ar.isAlwaysOnTop()
+ + ", lastLaunchTime=" + ar.lastLaunchTime
+ + ", lastVisibleTime=" + ar.lastVisibleTime
+ + ", taskFragment=" + ar.getTaskFragment();
+ }
+
private class FinishedActivityEntry {
int mUid;
int mTaskId;
int mLaunchCount;
+ String mDebugInfo;
FinishedActivityEntry(ActivityRecord ar) {
FinishedActivityEntry entry = mTaskIdToFinishedActivity.get(ar.getTask().mTaskId);
@@ -1677,6 +1745,7 @@
this.mUid = ar.getUid();
this.mTaskId = taskId;
this.mLaunchCount = entry == null || !ar.isUid(entry.mUid) ? 1 : entry.mLaunchCount + 1;
+ this.mDebugInfo = getDebugStringForActivityRecord(ar);
mService.mH.postDelayed(() -> {
synchronized (mService.mGlobalLock) {
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index ce0efe1..2049331 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -46,6 +46,7 @@
#include <com_android_input_flags.h>
#include <input/Input.h>
#include <input/PointerController.h>
+#include <input/PrintTools.h>
#include <input/SpriteController.h>
#include <inputflinger/InputManager.h>
#include <limits.h>
@@ -230,10 +231,6 @@
return a > b ? a : b;
}
-static inline const char* toString(bool value) {
- return value ? "true" : "false";
-}
-
static SpriteIcon toSpriteIcon(PointerIcon pointerIcon) {
// As a minor optimization, do not make a copy of the PointerIcon bitmap here. The loaded
// PointerIcons are only cached by InputManagerService in java, so we can safely assume they
@@ -288,7 +285,7 @@
void setSystemUiLightsOut(bool lightsOut);
void setPointerDisplayId(int32_t displayId);
void setPointerSpeed(int32_t speed);
- void setMousePointerAccelerationEnabled(bool enabled);
+ void setMousePointerAccelerationEnabled(int32_t displayId, bool enabled);
void setTouchpadPointerSpeed(int32_t speed);
void setTouchpadNaturalScrollingEnabled(bool enabled);
void setTouchpadTapToClickEnabled(bool enabled);
@@ -401,8 +398,8 @@
// Pointer speed.
int32_t pointerSpeed{0};
- // True if pointer acceleration is enabled for mice.
- bool mousePointerAccelerationEnabled{true};
+ // Displays on which its associated mice will have pointer acceleration disabled.
+ std::set<int32_t> displaysWithMousePointerAccelerationDisabled{};
// True if pointer gestures are enabled.
bool pointerGesturesEnabled{true};
@@ -493,8 +490,8 @@
dump += StringPrintf(INDENT "System UI Lights Out: %s\n",
toString(mLocked.systemUiLightsOut));
dump += StringPrintf(INDENT "Pointer Speed: %" PRId32 "\n", mLocked.pointerSpeed);
- dump += StringPrintf(INDENT "Mouse Pointer Acceleration: %s\n",
- mLocked.mousePointerAccelerationEnabled ? "Enabled" : "Disabled");
+ dump += StringPrintf(INDENT "Display with Mouse Pointer Acceleration Disabled: %s\n",
+ dumpSet(mLocked.displaysWithMousePointerAccelerationDisabled).c_str());
dump += StringPrintf(INDENT "Pointer Gestures Enabled: %s\n",
toString(mLocked.pointerGesturesEnabled));
dump += StringPrintf(INDENT "Show Touches: %s\n", toString(mLocked.showTouches));
@@ -677,11 +674,13 @@
std::scoped_lock _l(mLock);
outConfig->mousePointerSpeed = mLocked.pointerSpeed;
- outConfig->mousePointerAccelerationEnabled = mLocked.mousePointerAccelerationEnabled;
- outConfig->pointerVelocityControlParameters.scale = exp2f(mLocked.pointerSpeed
- * POINTER_SPEED_EXPONENT);
+ outConfig->displaysWithMousePointerAccelerationDisabled =
+ mLocked.displaysWithMousePointerAccelerationDisabled;
+ outConfig->pointerVelocityControlParameters.scale =
+ exp2f(mLocked.pointerSpeed * POINTER_SPEED_EXPONENT);
outConfig->pointerVelocityControlParameters.acceleration =
- mLocked.mousePointerAccelerationEnabled
+ mLocked.displaysWithMousePointerAccelerationDisabled.count(
+ mLocked.pointerDisplayId) == 0
? android::os::IInputConstants::DEFAULT_POINTER_ACCELERATION
: 1;
outConfig->pointerGesturesEnabled = mLocked.pointerGesturesEnabled;
@@ -1225,16 +1224,23 @@
InputReaderConfiguration::Change::POINTER_SPEED);
}
-void NativeInputManager::setMousePointerAccelerationEnabled(bool enabled) {
+void NativeInputManager::setMousePointerAccelerationEnabled(int32_t displayId, bool enabled) {
{ // acquire lock
std::scoped_lock _l(mLock);
- if (mLocked.mousePointerAccelerationEnabled == enabled) {
+ const bool oldEnabled =
+ mLocked.displaysWithMousePointerAccelerationDisabled.count(displayId) == 0;
+ if (oldEnabled == enabled) {
return;
}
- ALOGI("Setting mouse pointer acceleration to %s", toString(enabled));
- mLocked.mousePointerAccelerationEnabled = enabled;
+ ALOGI("Setting mouse pointer acceleration to %s on display %d", toString(enabled),
+ displayId);
+ if (enabled) {
+ mLocked.displaysWithMousePointerAccelerationDisabled.erase(displayId);
+ } else {
+ mLocked.displaysWithMousePointerAccelerationDisabled.emplace(displayId);
+ }
} // release lock
mInputManager->getReader().requestRefreshConfiguration(
@@ -2176,10 +2182,10 @@
}
static void nativeSetMousePointerAccelerationEnabled(JNIEnv* env, jobject nativeImplObj,
- jboolean enabled) {
+ jint displayId, jboolean enabled) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
- im->setMousePointerAccelerationEnabled(enabled);
+ im->setMousePointerAccelerationEnabled(displayId, enabled);
}
static void nativeSetTouchpadPointerSpeed(JNIEnv* env, jobject nativeImplObj, jint speed) {
@@ -2806,7 +2812,7 @@
(void*)nativeTransferTouchFocus},
{"transferTouch", "(Landroid/os/IBinder;I)Z", (void*)nativeTransferTouch},
{"setPointerSpeed", "(I)V", (void*)nativeSetPointerSpeed},
- {"setMousePointerAccelerationEnabled", "(Z)V",
+ {"setMousePointerAccelerationEnabled", "(IZ)V",
(void*)nativeSetMousePointerAccelerationEnabled},
{"setTouchpadPointerSpeed", "(I)V", (void*)nativeSetTouchpadPointerSpeed},
{"setTouchpadNaturalScrollingEnabled", "(Z)V",
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 11c40d7..9c033e2 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -675,7 +675,8 @@
options.enableCorrVecOutputs = enableCorrVecOutputs;
options.intervalMs = intervalMs;
- return gnssMeasurementIface->setCallback(std::make_unique<gnss::GnssMeasurementCallback>(),
+ return gnssMeasurementIface->setCallback(std::make_unique<gnss::GnssMeasurementCallback>(
+ gnssMeasurementIface->getInterfaceVersion()),
options);
}
diff --git a/services/core/jni/gnss/Gnss.cpp b/services/core/jni/gnss/Gnss.cpp
index 8934c3a..da8928b5 100644
--- a/services/core/jni/gnss/Gnss.cpp
+++ b/services/core/jni/gnss/Gnss.cpp
@@ -196,7 +196,8 @@
jboolean GnssHal::setCallback() {
if (gnssHalAidl != nullptr) {
- sp<IGnssCallbackAidl> gnssCbIfaceAidl = new GnssCallbackAidl();
+ sp<IGnssCallbackAidl> gnssCbIfaceAidl =
+ new GnssCallbackAidl(gnssHalAidl->getInterfaceVersion());
auto status = gnssHalAidl->setCallback(gnssCbIfaceAidl);
if (!checkAidlStatus(status, "IGnssAidl setCallback() failed.")) {
return JNI_FALSE;
diff --git a/services/core/jni/gnss/GnssCallback.cpp b/services/core/jni/gnss/GnssCallback.cpp
index 60eed8e6..3d598f7 100644
--- a/services/core/jni/gnss/GnssCallback.cpp
+++ b/services/core/jni/gnss/GnssCallback.cpp
@@ -120,7 +120,7 @@
Status GnssCallbackAidl::gnssSetCapabilitiesCb(const int capabilities) {
ALOGD("%s: %du\n", __func__, capabilities);
- bool isAdrCapabilityKnown = (getInterfaceVersion() >= 3) ? true : false;
+ bool isAdrCapabilityKnown = (interfaceVersion >= 3) ? true : false;
JNIEnv* env = getJniEnv();
env->CallVoidMethod(mCallbacksObj, method_setTopHalCapabilities, capabilities,
isAdrCapabilityKnown);
@@ -178,7 +178,7 @@
Status GnssCallbackAidl::gnssNmeaCb(const int64_t timestamp, const std::string& nmea) {
// In AIDL v1, if no listener is registered, do not report nmea to the framework.
- if (getInterfaceVersion() <= 1) {
+ if (interfaceVersion <= 1) {
if (!isNmeaRegistered) {
return Status::ok();
}
diff --git a/services/core/jni/gnss/GnssCallback.h b/services/core/jni/gnss/GnssCallback.h
index 33acec8..0622e53 100644
--- a/services/core/jni/gnss/GnssCallback.h
+++ b/services/core/jni/gnss/GnssCallback.h
@@ -60,6 +60,7 @@
*/
class GnssCallbackAidl : public hardware::gnss::BnGnssCallback {
public:
+ GnssCallbackAidl(int version) : interfaceVersion(version){};
binder::Status gnssSetCapabilitiesCb(const int capabilities) override;
binder::Status gnssSetSignalTypeCapabilitiesCb(
const std::vector<android::hardware::gnss::GnssSignalType>& signalTypes) override;
@@ -73,6 +74,9 @@
binder::Status gnssRequestTimeCb() override;
binder::Status gnssRequestLocationCb(const bool independentFromGnss,
const bool isUserEmergency) override;
+
+private:
+ const int interfaceVersion;
};
/*
diff --git a/services/core/jni/gnss/GnssMeasurement.h b/services/core/jni/gnss/GnssMeasurement.h
index 7a95db8..20400fd 100644
--- a/services/core/jni/gnss/GnssMeasurement.h
+++ b/services/core/jni/gnss/GnssMeasurement.h
@@ -41,6 +41,7 @@
const std::unique_ptr<GnssMeasurementCallback>& callback,
const android::hardware::gnss::IGnssMeasurementInterface::Options& options) = 0;
virtual jboolean close() = 0;
+ virtual int getInterfaceVersion() = 0;
};
class GnssMeasurement : public GnssMeasurementInterface {
@@ -50,6 +51,9 @@
const std::unique_ptr<GnssMeasurementCallback>& callback,
const android::hardware::gnss::IGnssMeasurementInterface::Options& options) override;
jboolean close() override;
+ int getInterfaceVersion() override {
+ return mIGnssMeasurement->getInterfaceVersion();
+ }
private:
const sp<android::hardware::gnss::IGnssMeasurementInterface> mIGnssMeasurement;
@@ -63,6 +67,9 @@
const std::unique_ptr<GnssMeasurementCallback>& callback,
const android::hardware::gnss::IGnssMeasurementInterface::Options& options) override;
jboolean close() override;
+ int getInterfaceVersion() override {
+ return 0;
+ }
private:
const sp<android::hardware::gnss::V1_0::IGnssMeasurement> mIGnssMeasurement_V1_0;
diff --git a/services/core/jni/gnss/GnssMeasurementCallback.cpp b/services/core/jni/gnss/GnssMeasurementCallback.cpp
index 2982546..ebab4c3 100644
--- a/services/core/jni/gnss/GnssMeasurementCallback.cpp
+++ b/services/core/jni/gnss/GnssMeasurementCallback.cpp
@@ -392,7 +392,7 @@
jobjectArray gnssAgcArray = nullptr;
gnssAgcArray = translateAllGnssAgcs(env, data.gnssAgcs);
- if (this->getInterfaceVersion() >= 3) {
+ if (interfaceVersion >= 3) {
setMeasurementData(env, mCallbacksObj, clock, measurementArray, gnssAgcArray,
/*hasIsFullTracking=*/true, data.isFullTracking);
} else {
@@ -467,7 +467,7 @@
satellitePvt.tropoDelayMeters);
}
- if (this->getInterfaceVersion() >= 2) {
+ if (interfaceVersion >= 2) {
callObjectMethodIgnoringResult(env, satellitePvtBuilderObject,
method_satellitePvtBuilderSetTimeOfClock,
satellitePvt.timeOfClockSeconds);
diff --git a/services/core/jni/gnss/GnssMeasurementCallback.h b/services/core/jni/gnss/GnssMeasurementCallback.h
index b3de486..3cb47ce 100644
--- a/services/core/jni/gnss/GnssMeasurementCallback.h
+++ b/services/core/jni/gnss/GnssMeasurementCallback.h
@@ -53,7 +53,8 @@
class GnssMeasurementCallbackAidl : public hardware::gnss::BnGnssMeasurementCallback {
public:
- GnssMeasurementCallbackAidl() : mCallbacksObj(getCallbacksObj()) {}
+ GnssMeasurementCallbackAidl(int version)
+ : mCallbacksObj(getCallbacksObj()), interfaceVersion(version) {}
android::binder::Status gnssMeasurementCb(const hardware::gnss::GnssData& data) override;
private:
@@ -71,6 +72,7 @@
void translateGnssClock(JNIEnv* env, const hardware::gnss::GnssData& data, JavaObject& object);
jobject& mCallbacksObj;
+ const int interfaceVersion;
};
/*
@@ -110,10 +112,10 @@
class GnssMeasurementCallback {
public:
- GnssMeasurementCallback() {}
+ GnssMeasurementCallback(int version) : interfaceVersion(version) {}
sp<GnssMeasurementCallbackAidl> getAidl() {
if (callbackAidl == nullptr) {
- callbackAidl = sp<GnssMeasurementCallbackAidl>::make();
+ callbackAidl = sp<GnssMeasurementCallbackAidl>::make(interfaceVersion);
}
return callbackAidl;
}
@@ -128,6 +130,7 @@
private:
sp<GnssMeasurementCallbackAidl> callbackAidl;
sp<GnssMeasurementCallbackHidl> callbackHidl;
+ const int interfaceVersion;
};
template <class T>
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 f469ab5..097d73a 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
@@ -960,8 +960,8 @@
if (permissionName !in androidPackage.requestedPermissions && oldFlags == 0) {
if (reportError) {
- throw SecurityException(
- "Permission $permissionName isn't requested by package $packageName"
+ Slog.e(
+ LOG_TAG, "Permission $permissionName isn't requested by package $packageName"
)
}
return
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
index e989d7b..ec7e359 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -552,22 +552,20 @@
when(mComputer.getPackageStateFiltered(eq(PACKAGE), anyInt(), anyInt())).thenReturn(
null);
- assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT,
- CALLER_PACKAGE)).isNull();
+ assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT)).isNull();
}
@Test
public void getArchivedAppIcon_notArchived() {
- assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT,
- CALLER_PACKAGE)).isNull();
+ assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT)).isNull();
}
@Test
public void getArchivedAppIcon_success() {
mUserState.setArchiveState(createArchiveState()).setInstalled(false);
- assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT,
- CALLER_PACKAGE)).isEqualTo(mIcon);
+ assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT)).isEqualTo(
+ mIcon);
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 57c3a1d..95cfc2a 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -22,6 +22,7 @@
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
import static android.view.accessibility.Flags.FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG;
+import static android.view.accessibility.Flags.FLAG_SKIP_ACCESSIBILITY_WARNING_DIALOG_FOR_TRUSTED_SERVICES;
import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
@@ -82,6 +83,7 @@
import androidx.test.filters.SmallTest;
import com.android.compatibility.common.util.TestUtils;
+import com.android.internal.R;
import com.android.internal.compat.IPlatformCompat;
import com.android.server.LocalServices;
import com.android.server.accessibility.AccessibilityManagerService.AccessibilityDisplayListener;
@@ -857,8 +859,7 @@
@RequiresFlagsEnabled(FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG)
public void testIsAccessibilityServiceWarningRequired_requiredByDefault() {
mockManageAccessibilityGranted(mTestableContext);
- final AccessibilityServiceInfo info = new AccessibilityServiceInfo();
- info.setComponentName(COMPONENT_NAME);
+ final AccessibilityServiceInfo info = mockAccessibilityServiceInfo(COMPONENT_NAME);
assertThat(mA11yms.isAccessibilityServiceWarningRequired(info)).isTrue();
}
@@ -867,10 +868,9 @@
@RequiresFlagsEnabled(FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG)
public void testIsAccessibilityServiceWarningRequired_notRequiredIfAlreadyEnabled() {
mockManageAccessibilityGranted(mTestableContext);
- final AccessibilityServiceInfo info_a = new AccessibilityServiceInfo();
- info_a.setComponentName(COMPONENT_NAME);
- final AccessibilityServiceInfo info_b = new AccessibilityServiceInfo();
- info_b.setComponentName(new ComponentName("package_b", "class_b"));
+ final AccessibilityServiceInfo info_a = mockAccessibilityServiceInfo(COMPONENT_NAME);
+ final AccessibilityServiceInfo info_b = mockAccessibilityServiceInfo(
+ new ComponentName("package_b", "class_b"));
final AccessibilityUserState userState = mA11yms.getCurrentUserState();
userState.mEnabledServices.clear();
userState.mEnabledServices.add(info_b.getComponentName());
@@ -883,12 +883,12 @@
@RequiresFlagsEnabled(FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG)
public void testIsAccessibilityServiceWarningRequired_notRequiredIfExistingShortcut() {
mockManageAccessibilityGranted(mTestableContext);
- final AccessibilityServiceInfo info_a = new AccessibilityServiceInfo();
- info_a.setComponentName(new ComponentName("package_a", "class_a"));
- final AccessibilityServiceInfo info_b = new AccessibilityServiceInfo();
- info_b.setComponentName(new ComponentName("package_b", "class_b"));
- final AccessibilityServiceInfo info_c = new AccessibilityServiceInfo();
- info_c.setComponentName(new ComponentName("package_c", "class_c"));
+ final AccessibilityServiceInfo info_a = mockAccessibilityServiceInfo(
+ new ComponentName("package_a", "class_a"));
+ final AccessibilityServiceInfo info_b = mockAccessibilityServiceInfo(
+ new ComponentName("package_b", "class_b"));
+ final AccessibilityServiceInfo info_c = mockAccessibilityServiceInfo(
+ new ComponentName("package_c", "class_c"));
final AccessibilityUserState userState = mA11yms.getCurrentUserState();
userState.mAccessibilityButtonTargets.clear();
userState.mAccessibilityButtonTargets.add(info_b.getComponentName().flattenToString());
@@ -900,6 +900,51 @@
assertThat(mA11yms.isAccessibilityServiceWarningRequired(info_c)).isFalse();
}
+ @Test
+ @RequiresFlagsEnabled({
+ FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG,
+ FLAG_SKIP_ACCESSIBILITY_WARNING_DIALOG_FOR_TRUSTED_SERVICES})
+ public void testIsAccessibilityServiceWarningRequired_notRequiredIfAllowlisted() {
+ mockManageAccessibilityGranted(mTestableContext);
+ final AccessibilityServiceInfo info_a = mockAccessibilityServiceInfo(
+ new ComponentName("package_a", "class_a"), true);
+ final AccessibilityServiceInfo info_b = mockAccessibilityServiceInfo(
+ new ComponentName("package_b", "class_b"), false);
+ final AccessibilityServiceInfo info_c = mockAccessibilityServiceInfo(
+ new ComponentName("package_c", "class_c"), true);
+ mTestableContext.getOrCreateTestableResources().addOverride(
+ R.array.config_trustedAccessibilityServices,
+ new String[]{
+ info_b.getComponentName().flattenToString(),
+ info_c.getComponentName().flattenToString()});
+
+ // info_a is not in the allowlist => require the warning
+ assertThat(mA11yms.isAccessibilityServiceWarningRequired(info_a)).isTrue();
+ // info_b is not preinstalled => require the warning
+ assertThat(mA11yms.isAccessibilityServiceWarningRequired(info_b)).isTrue();
+ // info_c is both in the allowlist and preinstalled => do not require the warning
+ assertThat(mA11yms.isAccessibilityServiceWarningRequired(info_c)).isFalse();
+ }
+
+ private static AccessibilityServiceInfo mockAccessibilityServiceInfo(
+ ComponentName componentName) {
+ return mockAccessibilityServiceInfo(componentName, false);
+ }
+
+ private static AccessibilityServiceInfo mockAccessibilityServiceInfo(
+ ComponentName componentName,
+ boolean isSystemApp) {
+ AccessibilityServiceInfo accessibilityServiceInfo =
+ Mockito.spy(new AccessibilityServiceInfo());
+ accessibilityServiceInfo.setComponentName(componentName);
+ ResolveInfo mockResolveInfo = Mockito.mock(ResolveInfo.class);
+ when(accessibilityServiceInfo.getResolveInfo()).thenReturn(mockResolveInfo);
+ mockResolveInfo.serviceInfo = Mockito.mock(ServiceInfo.class);
+ mockResolveInfo.serviceInfo.applicationInfo = Mockito.mock(ApplicationInfo.class);
+ when(mockResolveInfo.serviceInfo.applicationInfo.isSystemApp()).thenReturn(isSystemApp);
+ return accessibilityServiceInfo;
+ }
+
// Single package intents can trigger multiple PackageMonitor callbacks.
// Collect the state of the lock in a set, since tests only care if calls
// were all locked or all unlocked.
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index 81df597..0805485 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -157,7 +157,6 @@
return mMockDevicePolicyManager;
case Context.APP_SEARCH_SERVICE:
case Context.ROLE_SERVICE:
- case Context.APP_OPS_SERVICE:
// RoleManager is final and cannot be mocked, so we only override the inject
// accessor methods in ShortcutService.
return getTestContext().getSystemService(name);
diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java
index 9d56a36..5e11e17 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java
@@ -18,6 +18,7 @@
import static com.google.common.truth.Truth.assertThat;
+import android.content.pm.PackageManager;
import android.content.pm.VersionedPackage;
import android.content.rollback.PackageRollbackInfo;
import android.util.SparseIntArray;
@@ -81,7 +82,8 @@
+ "'installedUsers':[55,79],"
+ "'ceSnapshotInodes':[]}],'isStaged':false,'causePackages':[{'packageName':'hello',"
+ "'longVersionCode':23},{'packageName':'something','longVersionCode':999}],"
- + "'committedSessionId':45654465},'timestamp':'2019-10-01T12:29:08.855Z',"
+ + "'committedSessionId':45654465, 'rollbackImpactLevel':1},"
+ + "'timestamp':'2019-10-01T12:29:08.855Z',"
+ "'originalSessionId':567,'state':'enabling','apkSessionId':-1,"
+ "'restoreUserDataInProgress':true, 'userId':0,"
+ "'installerPackageName':'some.installer'}";
@@ -138,6 +140,8 @@
assertThat(rollback.getOriginalSessionId()).isEqualTo(567);
assertThat(rollback.info.getRollbackId()).isEqualTo(ID);
assertThat(rollback.info.getPackages()).isEmpty();
+ assertThat(rollback.info.getRollbackImpactLevel()).isEqualTo(
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
assertThat(rollback.isEnabling()).isTrue();
assertThat(rollback.getExtensionVersions().toString())
.isEqualTo(extensionVersions.toString());
@@ -158,6 +162,8 @@
assertThat(rollback.info.getRollbackId()).isEqualTo(ID);
assertThat(rollback.info.getPackages()).isEmpty();
+ assertThat(rollback.info.getRollbackImpactLevel()).isEqualTo(
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
assertThat(rollback.isEnabling()).isTrue();
assertThat(rollback.getExtensionVersions().toString())
.isEqualTo(extensionVersions.toString());
@@ -175,6 +181,7 @@
origRb.info.getCausePackages().add(new VersionedPackage("com.made.up", 2));
origRb.info.getCausePackages().add(new VersionedPackage("com.pack.age", 99));
origRb.info.setCommittedSessionId(123456);
+ origRb.info.setRollbackImpactLevel(PackageManager.ROLLBACK_USER_IMPACT_HIGH);
PackageRollbackInfo pkgInfo1 =
new PackageRollbackInfo(new VersionedPackage("com.made.up", 18),
@@ -226,6 +233,7 @@
expectedRb.info.getCausePackages().add(new VersionedPackage("hello", 23));
expectedRb.info.getCausePackages().add(new VersionedPackage("something", 999));
expectedRb.info.setCommittedSessionId(45654465);
+ expectedRb.info.setRollbackImpactLevel(PackageManager.ROLLBACK_USER_IMPACT_HIGH);
PackageRollbackInfo pkgInfo1 = new PackageRollbackInfo(new VersionedPackage("blah", 55),
new VersionedPackage("blah1", 50), new ArrayList<>(), new ArrayList<>(),
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 2486838..25ad7db 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -59,6 +59,7 @@
import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_UNKNOWN;
import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_CONTACTS;
+import static android.service.notification.ZenPolicy.PEOPLE_TYPE_NONE;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_STARRED;
import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.LOG_DND_STATE_EVENTS;
@@ -123,6 +124,7 @@
import android.os.Process;
import android.os.SimpleClock;
import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
@@ -238,15 +240,19 @@
public TestWithLooperRule mLooperRule = new TestWithLooperRule();
ConditionProviders mConditionProviders;
- @Mock NotificationManager mNotificationManager;
- @Mock PackageManager mPackageManager;
+ @Mock
+ NotificationManager mNotificationManager;
+ @Mock
+ PackageManager mPackageManager;
private Resources mResources;
private TestableLooper mTestableLooper;
private final TestClock mTestClock = new TestClock();
private ZenModeHelper mZenModeHelper;
private ContentResolver mContentResolver;
- @Mock DeviceEffectsApplier mDeviceEffectsApplier;
- @Mock AppOpsManager mAppOps;
+ @Mock
+ DeviceEffectsApplier mDeviceEffectsApplier;
+ @Mock
+ AppOpsManager mAppOps;
TestableFlagResolver mTestFlagResolver = new TestableFlagResolver();
ZenModeEventLoggerFake mZenModeEventLogger;
@@ -290,7 +296,7 @@
when(mPackageManager.getPackageUidAsUser(eq(CUSTOM_PKG_NAME), anyInt()))
.thenReturn(CUSTOM_PKG_UID);
when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(
- new String[] {pkg});
+ new String[]{pkg});
ApplicationInfo appInfoSpy = spy(new ApplicationInfo());
appInfoSpy.icon = ICON_RES_ID;
@@ -305,24 +311,26 @@
}
private XmlResourceParser getDefaultConfigParser() throws IOException, XmlPullParserException {
- String xml = "<zen version=\"8\" user=\"0\">\n"
- + "<allow calls=\"false\" repeatCallers=\"false\" messages=\"true\" "
- + "reminders=\"false\" events=\"false\" callsFrom=\"1\" messagesFrom=\"2\" "
- + "visualScreenOff=\"true\" alarms=\"true\" "
- + "media=\"true\" system=\"false\" conversations=\"true\""
- + " conversationsFrom=\"2\"/>\n"
- + "<automatic ruleId=\"" + EVENTS_DEFAULT_RULE_ID
- + "\" enabled=\"false\" snoozing=\"false\""
- + " name=\"Event\" zen=\"1\""
- + " component=\"android/com.android.server.notification.EventConditionProvider\""
- + " conditionId=\"condition://android/event?userId=-10000&calendar=&"
+ String xml = "<zen version=\"10\">\n"
+ + "<allow alarms=\"true\" media=\"true\" system=\"false\" calls=\"true\" "
+ + "callsFrom=\"2\" messages=\"true\"\n"
+ + "messagesFrom=\"2\" reminders=\"false\" events=\"false\" "
+ + "repeatCallers=\"true\" convos=\"true\"\n"
+ + "convosFrom=\"2\"/>\n"
+ + "<automatic ruleId=" + EVENTS_DEFAULT_RULE_ID
+ + " enabled=\"false\" snoozing=\"false\""
+ + " name=\"Event\" zen=\"1\"\n"
+ + " component=\"android/com.android.server.notification.EventConditionProvider\"\n"
+ + " conditionId=\"condition://android/event?userId=-10000&calendar=&"
+ "reply=1\"/>\n"
- + "<automatic ruleId=\"" + SCHEDULE_DEFAULT_RULE_ID + "\" enabled=\"false\""
- + " snoozing=\"false\" name=\"Sleeping\" zen=\"1\""
- + " component=\"android/com.android.server.notification.ScheduleConditionProvider\""
- + " conditionId=\"condition://android/schedule?days=1.2.3.4.5.6.7 &start=22.0"
- + "&end=7.0&exitAtAlarm=true\"/>"
- + "<disallow visualEffects=\"511\" />"
+ + "<automatic ruleId=" + SCHEDULE_DEFAULT_RULE_ID + " enabled=\"false\""
+ + " snoozing=\"false\" name=\"Sleeping\"\n zen=\"1\""
+ + " component=\"android/com.android.server.notification"
+ + ".ScheduleConditionProvider\"\n"
+ + " conditionId=\"condition://android/schedule?days=1.2.3.4.5.6.7&start=22.0"
+ + "&end=7.0&exitAtAlarm=true\"/>\n"
+ + "<disallow visualEffects=\"157\" />\n"
+ + "<state areChannelsBypassingDnd=\"false\" />\n"
+ "</zen>";
TypedXmlPullParser parser = Xml.newFastPullParser();
parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())), null);
@@ -408,7 +416,7 @@
@Test
public void testZenOff_NoMuteApplied() {
mZenModeHelper.mZenMode = ZEN_MODE_OFF;
- mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O});
+ mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O});
mZenModeHelper.mConsolidatedPolicy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS
| PRIORITY_CATEGORY_MEDIA, 0, 0, 0, 0, 0);
mZenModeHelper.applyRestrictions();
@@ -421,7 +429,7 @@
@Test
public void testZenOn_NotificationApplied() {
mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
- mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O});
+ mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O});
// The most permissive policy
mZenModeHelper.mConsolidatedPolicy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS
| PRIORITY_CATEGORY_MEDIA | PRIORITY_CATEGORY_MESSAGES
@@ -442,7 +450,7 @@
@Test
public void testZenOn_StarredCallers_CallTypesBlocked() {
mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
- mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O});
+ mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O});
// The most permissive policy
mZenModeHelper.mConsolidatedPolicy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS
| PRIORITY_CATEGORY_MEDIA | PRIORITY_CATEGORY_MESSAGES
@@ -462,7 +470,7 @@
@Test
public void testZenOn_AllCallers_CallTypesAllowed() {
mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
- mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O});
+ mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O});
// The most permissive policy
mZenModeHelper.mConsolidatedPolicy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS
| PRIORITY_CATEGORY_MEDIA | PRIORITY_CATEGORY_MESSAGES
@@ -481,7 +489,7 @@
@Test
public void testZenOn_AllowAlarmsMedia_NoAlarmMediaMuteApplied() {
mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
- mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O});
+ mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O});
mZenModeHelper.mConsolidatedPolicy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS
| PRIORITY_CATEGORY_MEDIA, 0, 0, 0, 0, 0);
@@ -493,7 +501,7 @@
@Test
public void testZenOn_DisallowAlarmsMedia_AlarmMediaMuteApplied() {
mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
- mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O});
+ mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O});
mZenModeHelper.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
mZenModeHelper.applyRestrictions();
verifyApplyRestrictions(true, true, AudioAttributes.USAGE_ALARM);
@@ -506,7 +514,7 @@
@Test
public void testTotalSilence() {
mZenModeHelper.mZenMode = Settings.Global.ZEN_MODE_NO_INTERRUPTIONS;
- mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O});
+ mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O});
mZenModeHelper.mConsolidatedPolicy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS
| PRIORITY_CATEGORY_MEDIA, 0, 0, 0, 0, 0);
mZenModeHelper.applyRestrictions();
@@ -525,7 +533,7 @@
@Test
public void testAlarmsOnly_alarmMediaMuteNotApplied() {
mZenModeHelper.mZenMode = Settings.Global.ZEN_MODE_ALARMS;
- mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O});
+ mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O});
mZenModeHelper.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
mZenModeHelper.applyRestrictions();
@@ -545,7 +553,7 @@
@Test
public void testAlarmsOnly_callsMuteApplied() {
mZenModeHelper.mZenMode = Settings.Global.ZEN_MODE_ALARMS;
- mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O});
+ mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O});
mZenModeHelper.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
mZenModeHelper.applyRestrictions();
@@ -559,7 +567,7 @@
public void testAlarmsOnly_allZenConfigToggledCannotBypass_alarmMuteNotApplied() {
// Only audio attributes with SUPPRESIBLE_NEVER can bypass
mZenModeHelper.mZenMode = Settings.Global.ZEN_MODE_ALARMS;
- mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O});
+ mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O});
mZenModeHelper.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
mZenModeHelper.applyRestrictions();
@@ -571,7 +579,7 @@
// Only audio attributes with SUPPRESIBLE_NEVER can bypass
// with special case USAGE_ASSISTANCE_SONIFICATION
mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
- mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O});
+ mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O});
mZenModeHelper.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
mZenModeHelper.applyRestrictions();
@@ -592,7 +600,7 @@
@Test
public void testApplyRestrictions_whitelist_priorityOnlyMode() {
- mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O});
+ mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O});
mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
mZenModeHelper.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
mZenModeHelper.applyRestrictions();
@@ -607,7 +615,7 @@
@Test
public void testApplyRestrictions_whitelist_alarmsOnlyMode() {
- mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O});
+ mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O});
mZenModeHelper.mZenMode = Global.ZEN_MODE_ALARMS;
mZenModeHelper.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
mZenModeHelper.applyRestrictions();
@@ -622,7 +630,7 @@
@Test
public void testApplyRestrictions_whitelist_totalSilenceMode() {
- mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O});
+ mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O});
mZenModeHelper.mZenMode = Global.ZEN_MODE_NO_INTERRUPTIONS;
mZenModeHelper.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
mZenModeHelper.applyRestrictions();
@@ -1007,7 +1015,7 @@
mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
assertEquals("Config mismatch: current vs expected: "
- + new ZenModeDiff.ConfigDiff(mZenModeHelper.mConfig, expected), expected,
+ + new ZenModeDiff.ConfigDiff(mZenModeHelper.mConfig, expected), expected,
mZenModeHelper.mConfig);
}
@@ -1336,7 +1344,7 @@
mZenModeHelper.readXml(parser, true, UserHandle.USER_SYSTEM);
assertEquals("Config mismatch: current vs original: "
- + new ZenModeDiff.ConfigDiff(mZenModeHelper.mConfig, original),
+ + new ZenModeDiff.ConfigDiff(mZenModeHelper.mConfig, original),
original, mZenModeHelper.mConfig);
assertEquals(original.hashCode(), mZenModeHelper.mConfig.hashCode());
}
@@ -1778,6 +1786,225 @@
}
@Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void testReadXml_onModesApi_noUpgrade() throws Exception {
+ // When reading XML for something that is already on the modes API system, make sure no
+ // rules' policies get changed.
+ setupZenConfig();
+
+ // Shared for rules
+ ArrayMap<String, ZenModeConfig.ZenRule> enabledAutoRules = new ArrayMap<>();
+ final ScheduleInfo weeknights = new ScheduleInfo();
+
+ // Custom rule with a custom policy
+ ZenModeConfig.ZenRule customRule = new ZenModeConfig.ZenRule();
+ customRule.enabled = true;
+ customRule.name = "Custom Rule";
+ customRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ customRule.conditionId = ZenModeConfig.toScheduleConditionId(weeknights);
+ customRule.component = new ComponentName("android", "ScheduleConditionProvider");
+ ZenPolicy policy = new ZenPolicy.Builder()
+ .allowCalls(PEOPLE_TYPE_CONTACTS)
+ .allowAlarms(true)
+ .allowRepeatCallers(false)
+ .build();
+ // Fill in policy fields, since on modes api we do not expect any rules to have unset fields
+ customRule.zenPolicy = mZenModeHelper.getDefaultZenPolicy().overwrittenWith(policy);
+ enabledAutoRules.put("customRule", customRule);
+ mZenModeHelper.mConfig.automaticRules = enabledAutoRules;
+
+ // set version to post-modes-API = 11
+ ByteArrayOutputStream baos = writeXmlAndPurge(11);
+ TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new BufferedInputStream(
+ new ByteArrayInputStream(baos.toByteArray())), null);
+ parser.nextTag();
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+
+ // basic check: global config maintained
+ setupZenConfigMaintained();
+
+ // Find our automatic rules.
+ ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
+ assertThat(rules).hasSize(1);
+ assertThat(rules).containsKey("customRule");
+ ZenRule rule = rules.get("customRule");
+ assertThat(rule.zenPolicy).isEqualTo(customRule.zenPolicy);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void testReadXml_upgradeToModesApi_makesCustomPolicies() throws Exception {
+ // When reading in an XML file written from a pre-modes-API version, confirm that we create
+ // a custom policy matching the global config for any automatic rule with no specified
+ // policy.
+ setupZenConfig();
+
+ ArrayMap<String, ZenModeConfig.ZenRule> enabledAutoRule = new ArrayMap<>();
+ ZenModeConfig.ZenRule customRule = new ZenModeConfig.ZenRule();
+ final ScheduleInfo weeknights = new ScheduleInfo();
+ customRule.enabled = true;
+ customRule.name = "Custom Rule";
+ customRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ customRule.conditionId = ZenModeConfig.toScheduleConditionId(weeknights);
+ customRule.component = new ComponentName("android", "ScheduleConditionProvider");
+ enabledAutoRule.put("customRule", customRule); // no custom policy set
+ mZenModeHelper.mConfig.automaticRules = enabledAutoRule;
+
+ // set version to pre-modes-API = 10
+ ByteArrayOutputStream baos = writeXmlAndPurge(10);
+ TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new BufferedInputStream(
+ new ByteArrayInputStream(baos.toByteArray())), null);
+ parser.nextTag();
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+
+ // basic check: global config maintained
+ setupZenConfigMaintained();
+
+ // Find our automatic rule and check that it has a policy set now
+ ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
+ assertThat(rules).hasSize(1);
+ assertThat(rules).containsKey("customRule");
+ ZenRule rule = rules.get("customRule");
+ assertThat(rule.zenPolicy).isNotNull();
+
+ // Check policy values as set up in setupZenConfig() to confirm they match
+ assertThat(rule.zenPolicy.getPriorityCategoryAlarms()).isEqualTo(STATE_DISALLOW);
+ assertThat(rule.zenPolicy.getPriorityCategoryMedia()).isEqualTo(STATE_DISALLOW);
+ assertThat(rule.zenPolicy.getPriorityCategorySystem()).isEqualTo(STATE_DISALLOW);
+ assertThat(rule.zenPolicy.getPriorityCategoryReminders()).isEqualTo(STATE_ALLOW);
+ assertThat(rule.zenPolicy.getPriorityCategoryCalls()).isEqualTo(STATE_ALLOW);
+ assertThat(rule.zenPolicy.getPriorityCallSenders()).isEqualTo(PEOPLE_TYPE_STARRED);
+ assertThat(rule.zenPolicy.getPriorityCategoryMessages()).isEqualTo(STATE_ALLOW);
+ assertThat(rule.zenPolicy.getPriorityCategoryConversations()).isEqualTo(STATE_ALLOW);
+ assertThat(rule.zenPolicy.getPriorityCategoryEvents()).isEqualTo(STATE_ALLOW);
+ assertThat(rule.zenPolicy.getPriorityCategoryRepeatCallers()).isEqualTo(STATE_ALLOW);
+ assertThat(rule.zenPolicy.getVisualEffectBadge()).isEqualTo(STATE_DISALLOW);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void testReadXml_upgradeToModesApi_fillsInCustomPolicies() throws Exception {
+ // When reading in an XML file written from a pre-modes-API version, confirm that for an
+ // underspecified ZenPolicy, we fill in all of the gaps with things from the global config
+ // in order to maintain consistency of behavior.
+ setupZenConfig();
+
+ ArrayMap<String, ZenModeConfig.ZenRule> enabledAutoRule = new ArrayMap<>();
+ ZenModeConfig.ZenRule customRule = new ZenModeConfig.ZenRule();
+ final ScheduleInfo weeknights = new ScheduleInfo();
+ customRule.enabled = true;
+ customRule.name = "Custom Rule";
+ customRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ customRule.conditionId = ZenModeConfig.toScheduleConditionId(weeknights);
+ customRule.component = new ComponentName("android", "ScheduleConditionProvider");
+ customRule.zenPolicy = new ZenPolicy.Builder()
+ .allowAlarms(true)
+ .allowMedia(true)
+ .allowRepeatCallers(false)
+ .build();
+ enabledAutoRule.put("customRule", customRule);
+ mZenModeHelper.mConfig.automaticRules = enabledAutoRule;
+
+ // set version to pre-modes-API = 10
+ ByteArrayOutputStream baos = writeXmlAndPurge(10);
+ TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new BufferedInputStream(
+ new ByteArrayInputStream(baos.toByteArray())), null);
+ parser.nextTag();
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+
+ // basic check: global config maintained
+ setupZenConfigMaintained();
+
+ // Find our automatic rule and check that it has a policy set now
+ ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
+ assertThat(rules).hasSize(1);
+ assertThat(rules).containsKey("customRule");
+ ZenRule rule = rules.get("customRule");
+ assertThat(rule.zenPolicy).isNotNull();
+
+ // Check unset policy values match values in setupZenConfig().
+ // Check that set policy values match the values set in the policy.
+ assertThat(rule.zenPolicy.getPriorityCategoryAlarms()).isEqualTo(STATE_ALLOW);
+ assertThat(rule.zenPolicy.getPriorityCategoryMedia()).isEqualTo(STATE_ALLOW);
+ assertThat(rule.zenPolicy.getPriorityCategoryRepeatCallers()).isEqualTo(STATE_DISALLOW);
+
+ // Check that the rest is filled in from the default
+ assertThat(rule.zenPolicy.getPriorityCategorySystem()).isEqualTo(STATE_DISALLOW);
+ assertThat(rule.zenPolicy.getPriorityCategoryReminders()).isEqualTo(STATE_ALLOW);
+ assertThat(rule.zenPolicy.getPriorityCategoryCalls()).isEqualTo(STATE_ALLOW);
+ assertThat(rule.zenPolicy.getPriorityCallSenders()).isEqualTo(PEOPLE_TYPE_STARRED);
+ assertThat(rule.zenPolicy.getPriorityCategoryMessages()).isEqualTo(STATE_ALLOW);
+ assertThat(rule.zenPolicy.getPriorityCategoryConversations()).isEqualTo(STATE_ALLOW);
+ assertThat(rule.zenPolicy.getPriorityCategoryEvents()).isEqualTo(STATE_ALLOW);
+ assertThat(rule.zenPolicy.getVisualEffectBadge()).isEqualTo(STATE_DISALLOW);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void testReadXml_upgradeToModesApi_existingDefaultRulesGetCustomPolicy()
+ throws Exception {
+ setupZenConfig();
+
+ // Default rules, if they exist and have no policies, should get a snapshot of the global
+ // policy, even if they are disabled upon upgrade.
+ ArrayMap<String, ZenModeConfig.ZenRule> automaticRules = new ArrayMap<>();
+ ZenModeConfig.ZenRule defaultScheduleRule = new ZenModeConfig.ZenRule();
+ final ScheduleInfo defaultScheduleRuleInfo = new ScheduleInfo();
+ defaultScheduleRule.enabled = false;
+ defaultScheduleRule.name = "Default Schedule Rule";
+ defaultScheduleRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ defaultScheduleRule.conditionId = ZenModeConfig.toScheduleConditionId(
+ defaultScheduleRuleInfo);
+ defaultScheduleRule.id = ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID;
+ automaticRules.put(ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID, defaultScheduleRule);
+
+ ZenModeConfig.ZenRule defaultEventRule = new ZenModeConfig.ZenRule();
+ final ScheduleInfo defaultEventRuleInfo = new ScheduleInfo();
+ defaultEventRule.enabled = false;
+ defaultEventRule.name = "Default Event Rule";
+ defaultEventRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ defaultEventRule.conditionId = ZenModeConfig.toScheduleConditionId(
+ defaultEventRuleInfo);
+ defaultEventRule.id = ZenModeConfig.EVENTS_DEFAULT_RULE_ID;
+ automaticRules.put(ZenModeConfig.EVENTS_DEFAULT_RULE_ID, defaultEventRule);
+
+ mZenModeHelper.mConfig.automaticRules = automaticRules;
+
+ // set previous version
+ ByteArrayOutputStream baos = writeXmlAndPurge(10);
+ TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new BufferedInputStream(
+ new ByteArrayInputStream(baos.toByteArray())), null);
+ parser.nextTag();
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+
+ // check default rules
+ ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
+ assertThat(rules.size()).isGreaterThan(0);
+ for (String defaultId : ZenModeConfig.DEFAULT_RULE_IDS) {
+ assertThat(rules).containsKey(defaultId);
+ ZenRule rule = rules.get(defaultId);
+ assertThat(rule.zenPolicy).isNotNull();
+
+ // Check policy values as set up in setupZenConfig() to confirm they match
+ assertThat(rule.zenPolicy.getPriorityCategoryAlarms()).isEqualTo(STATE_DISALLOW);
+ assertThat(rule.zenPolicy.getPriorityCategoryMedia()).isEqualTo(STATE_DISALLOW);
+ assertThat(rule.zenPolicy.getPriorityCategorySystem()).isEqualTo(STATE_DISALLOW);
+ assertThat(rule.zenPolicy.getPriorityCategoryReminders()).isEqualTo(STATE_ALLOW);
+ assertThat(rule.zenPolicy.getPriorityCategoryCalls()).isEqualTo(STATE_ALLOW);
+ assertThat(rule.zenPolicy.getPriorityCallSenders()).isEqualTo(PEOPLE_TYPE_STARRED);
+ assertThat(rule.zenPolicy.getPriorityCategoryMessages()).isEqualTo(STATE_ALLOW);
+ assertThat(rule.zenPolicy.getPriorityCategoryConversations()).isEqualTo(STATE_ALLOW);
+ assertThat(rule.zenPolicy.getPriorityCategoryEvents()).isEqualTo(STATE_ALLOW);
+ assertThat(rule.zenPolicy.getPriorityCategoryRepeatCallers()).isEqualTo(STATE_ALLOW);
+ assertThat(rule.zenPolicy.getVisualEffectBadge()).isEqualTo(STATE_DISALLOW);
+ }
+ }
+
+ @Test
public void testCountdownConditionSubscription() throws Exception {
ZenModeConfig config = new ZenModeConfig();
mZenModeHelper.mConfig = config;
@@ -2036,6 +2263,69 @@
}
@Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void testAddAutomaticZenRule_modesApi_fillsInDefaultValues() {
+ // When a new automatic zen rule is added with only some fields filled in, ensure that
+ // all unset fields are filled in with device defaults.
+
+ // Zen rule with null policy: should get entirely the default state
+ AutomaticZenRule zenRule1 = new AutomaticZenRule("name",
+ new ComponentName("android", "ScheduleConditionProvider"),
+ ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+ NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+ String id1 = mZenModeHelper.addAutomaticZenRule("android", zenRule1,
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+
+ // Zen rule with partially-filled policy: should get all of the filled fields set, and the
+ // rest filled with default state
+ AutomaticZenRule zenRule2 = new AutomaticZenRule("name",
+ null,
+ new ComponentName(mContext.getPackageName(), "ScheduleConditionProvider"),
+ ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+ new ZenPolicy.Builder()
+ .allowCalls(PEOPLE_TYPE_NONE)
+ .allowMessages(PEOPLE_TYPE_CONTACTS)
+ .showFullScreenIntent(true)
+ .build(),
+ NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+ String id2 = mZenModeHelper.addAutomaticZenRule("android", zenRule2,
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+
+ // rule 1 should exist
+ assertThat(id1).isNotNull();
+ ZenModeConfig.ZenRule rule1InConfig = mZenModeHelper.mConfig.automaticRules.get(id1);
+ assertThat(rule1InConfig).isNotNull();
+ assertThat(rule1InConfig.zenPolicy).isNotNull(); // we passed in null; it should now not be
+
+ // all of rule 1 should be the device default's policy
+ assertThat(rule1InConfig.zenPolicy).isEqualTo(mZenModeHelper.getDefaultZenPolicy());
+
+ // rule 2 should exist
+ assertThat(id2).isNotNull();
+ ZenModeConfig.ZenRule rule2InConfig = mZenModeHelper.mConfig.automaticRules.get(id2);
+ assertThat(rule2InConfig).isNotNull();
+
+ // rule 2: values set from the policy itself
+ assertThat(rule2InConfig.zenPolicy.getPriorityCallSenders()).isEqualTo(PEOPLE_TYPE_NONE);
+ assertThat(rule2InConfig.zenPolicy.getPriorityMessageSenders())
+ .isEqualTo(PEOPLE_TYPE_CONTACTS);
+ assertThat(rule2InConfig.zenPolicy.getVisualEffectFullScreenIntent())
+ .isEqualTo(ZenPolicy.STATE_ALLOW);
+
+ // the rest of rule 2's settings should be the device defaults
+ assertThat(rule2InConfig.zenPolicy.getPriorityConversationSenders())
+ .isEqualTo(CONVERSATION_SENDERS_IMPORTANT);
+ assertThat(rule2InConfig.zenPolicy.getPriorityCategorySystem())
+ .isEqualTo(ZenPolicy.STATE_DISALLOW);
+ assertThat(rule2InConfig.zenPolicy.getPriorityCategoryAlarms())
+ .isEqualTo(ZenPolicy.STATE_ALLOW);
+ assertThat(rule2InConfig.zenPolicy.getVisualEffectPeek())
+ .isEqualTo(ZenPolicy.STATE_DISALLOW);
+ assertThat(rule2InConfig.zenPolicy.getVisualEffectNotificationList())
+ .isEqualTo(ZenPolicy.STATE_ALLOW);
+ }
+
+ @Test
public void testSetAutomaticZenRuleState_nullPkg() {
AutomaticZenRule zenRule = new AutomaticZenRule("name",
null,
@@ -2357,6 +2647,68 @@
@Test
@EnableFlags(Flags.FLAG_MODES_API)
+ public void updateAutomaticZenRule_nullPolicy_doesNothing() {
+ // Test that when updateAutomaticZenRule is called with a null policy, nothing changes
+ // about the existing policy.
+ String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ new AutomaticZenRule.Builder("Rule", CONDITION_ID)
+ .setOwner(OWNER)
+ .setZenPolicy(new ZenPolicy.Builder()
+ .allowCalls(ZenPolicy.PEOPLE_TYPE_NONE) // default is stars
+ .build())
+ .build(),
+ UPDATE_ORIGIN_APP, "reasons", 0);
+
+ mZenModeHelper.updateAutomaticZenRule(ruleId,
+ new AutomaticZenRule.Builder("Rule", CONDITION_ID)
+ // no zen policy
+ .build(),
+ UPDATE_ORIGIN_APP, "reasons", 0);
+
+ AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ assertThat(savedRule.getZenPolicy().getPriorityCategoryCalls())
+ .isEqualTo(ZenPolicy.STATE_DISALLOW);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void updateAutomaticZenRule_overwritesExistingPolicy() {
+ // Test that when updating an automatic zen rule with an existing policy, the newly set
+ // fields overwrite those from the previous policy, but unset fields in the new policy
+ // keep values from the previous one.
+ String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ new AutomaticZenRule.Builder("Rule", CONDITION_ID)
+ .setOwner(OWNER)
+ .setZenPolicy(new ZenPolicy.Builder()
+ .allowCalls(ZenPolicy.PEOPLE_TYPE_NONE) // default is stars
+ .allowAlarms(false)
+ .allowReminders(true)
+ .build())
+ .build(),
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0);
+
+ mZenModeHelper.updateAutomaticZenRule(ruleId,
+ new AutomaticZenRule.Builder("Rule", CONDITION_ID)
+ .setZenPolicy(new ZenPolicy.Builder()
+ .allowCalls(ZenPolicy.PEOPLE_TYPE_CONTACTS)
+ .build())
+ .build(),
+ UPDATE_ORIGIN_APP, "reasons", 0);
+
+ AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ assertThat(savedRule.getZenPolicy().getPriorityCategoryCalls())
+ .isEqualTo(ZenPolicy.STATE_ALLOW); // from update
+ assertThat(savedRule.getZenPolicy().getPriorityCallSenders())
+ .isEqualTo(ZenPolicy.PEOPLE_TYPE_CONTACTS); // from update
+ assertThat(savedRule.getZenPolicy().getPriorityCategoryAlarms())
+ .isEqualTo(ZenPolicy.STATE_DISALLOW); // from original
+ assertThat(savedRule.getZenPolicy().getPriorityCategoryReminders())
+ .isEqualTo(ZenPolicy.STATE_ALLOW); // from original
+ }
+
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
public void addAutomaticZenRule_withTypeBedtime_replacesDisabledSleeping() {
ZenRule sleepingRule = createCustomAutomaticRule(ZEN_MODE_IMPORTANT_INTERRUPTIONS,
ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID);
@@ -2460,7 +2812,8 @@
DISABLED(false, /* originForUserActionInSystemUi= */ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI);
private final boolean mEnabled;
- @ConfigChangeOrigin private final int mOriginForUserActionInSystemUi;
+ @ConfigChangeOrigin
+ private final int mOriginForUserActionInSystemUi;
ModesApiFlag(boolean enabled, @ConfigChangeOrigin int originForUserActionInSystemUi) {
this.mEnabled = enabled;
@@ -2506,7 +2859,7 @@
// - rules active = 1
// - user action = true (system-based turning zen mode on)
// - package uid = system (as set above)
- // - resulting DNDPolicyProto the same as the values in setupZenConfig()
+ // - resulting DNDPolicyProto the same as the values in setupZenConfig() (global policy)
assertEquals(ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_ON.getId(),
mZenModeEventLogger.getEventId(0));
assertEquals(ZEN_MODE_OFF, mZenModeEventLogger.getPrevZenMode(0));
@@ -2600,7 +2953,7 @@
// - 1 rule (newly) active
// - automatic (is not a user action)
// - package UID is written to be the rule *owner* even though it "comes from system"
- // - zen policy is the same as the set-up zen config
+ // - zen policy is the default as it's unspecified
assertEquals(ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_ON.getId(),
mZenModeEventLogger.getEventId(0));
assertEquals(ZEN_MODE_OFF, mZenModeEventLogger.getPrevZenMode(0));
@@ -2609,10 +2962,10 @@
assertEquals(1, mZenModeEventLogger.getNumRulesActive(0));
assertFalse(mZenModeEventLogger.getIsUserAction(0));
assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(0));
- checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(0));
+ checkDndProtoMatchesDefaultZenConfig(mZenModeEventLogger.getPolicyProto(0));
// When the automatic rule is disabled, this should turn off zen mode and also count as a
- // user action.
+ // user action. We don't care what the consolidated policy is when DND turns off.
assertEquals(ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_OFF.getId(),
mZenModeEventLogger.getEventId(1));
assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeEventLogger.getPrevZenMode(1));
@@ -2835,28 +3188,28 @@
// First: turn on rule 1
mZenModeHelper.setAutomaticZenRuleState(id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
// Second: turn on rule 2
mZenModeHelper.setAutomaticZenRuleState(id2,
new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
// Third: turn on rule 3
mZenModeHelper.setAutomaticZenRuleState(id3,
new Condition(zenRule3.getConditionId(), "", STATE_TRUE),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
// Fourth: Turn *off* rule 2
mZenModeHelper.setAutomaticZenRuleState(id2,
new Condition(zenRule2.getConditionId(), "", STATE_FALSE),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
// This should result in a total of four events
assertEquals(4, mZenModeEventLogger.numLoggedChanges());
// Event 1: rule 1 turns on. We expect this to turn on DND (zen mode) overall, so that's
- // what the event should reflect. At this time, the policy is the same as initial setup.
+ // what the event should reflect. At this time, the policy is the default.
assertEquals(ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_ON.getId(),
mZenModeEventLogger.getEventId(0));
assertEquals(ZEN_MODE_OFF, mZenModeEventLogger.getPrevZenMode(0));
@@ -2864,7 +3217,7 @@
assertEquals(1, mZenModeEventLogger.getNumRulesActive(0));
assertFalse(mZenModeEventLogger.getIsUserAction(0));
assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(0));
- checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(0));
+ checkDndProtoMatchesDefaultZenConfig(mZenModeEventLogger.getPolicyProto(0));
// Event 2: rule 2 turns on. This should not change anything about the policy, so the only
// change is that there are more rules active now.
@@ -2873,7 +3226,7 @@
assertEquals(2, mZenModeEventLogger.getNumRulesActive(1));
assertFalse(mZenModeEventLogger.getIsUserAction(1));
assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(1));
- checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(1));
+ checkDndProtoMatchesDefaultZenConfig(mZenModeEventLogger.getPolicyProto(1));
// Event 3: rule 3 turns on. This should trigger a policy change, and be classified as such,
// but meanwhile also change the number of active rules.
@@ -2926,12 +3279,16 @@
mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
setupZenConfig();
+ // Explicitly set up all rules with the same policy as the manual rule so there will be
+ // no policy changes in this test case.
+ ZenPolicy manualRulePolicy = mZenModeHelper.mConfig.toZenPolicy();
+
// Rule 1, owned by a package
AutomaticZenRule zenRule = new AutomaticZenRule("name",
null,
new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
- null,
+ manualRulePolicy,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
UPDATE_ORIGIN_APP, "test", Process.SYSTEM_UID);
@@ -2941,7 +3298,7 @@
null,
new ComponentName("android", "ScheduleConditionProvider"),
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
- null,
+ manualRulePolicy,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
modesApiFlag.mOriginForUserActionInSystemUi, "test", Process.SYSTEM_UID);
@@ -3172,7 +3529,8 @@
}
@Test
- public void testUpdateConsolidatedPolicy_defaultRulesOnly() {
+ @DisableFlags(Flags.FLAG_MODES_API)
+ public void testUpdateConsolidatedPolicy_preModesApiDefaultRulesOnly_takesGlobalDefault() {
setupZenConfig();
// When there's one automatic rule active and it doesn't specify a policy, test that the
@@ -3205,12 +3563,39 @@
}
@Test
- public void testUpdateConsolidatedPolicy_customPolicyOnly() {
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void testUpdateConsolidatedPolicy_modesApiDefaultRulesOnly_takesDeviceDefault() {
+ setupZenConfig();
+
+ // When there's one automatic rule active and it doesn't specify a policy, test that the
+ // resulting consolidated policy is one that matches the default *device* settings.
+ AutomaticZenRule zenRule = new AutomaticZenRule("name",
+ null,
+ new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
+ ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+ null, // null policy
+ NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+ String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+
+ // enable the rule
+ mZenModeHelper.setAutomaticZenRuleState(id,
+ new Condition(zenRule.getConditionId(), "", STATE_TRUE),
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+
+ // inspect the consolidated policy, which should match the device default settings.
+ assertThat(ZenAdapters.notificationPolicyToZenPolicy(mZenModeHelper.mConsolidatedPolicy))
+ .isEqualTo(mZenModeHelper.getDefaultZenPolicy());
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MODES_API)
+ public void testUpdateConsolidatedPolicy_preModesApiCustomPolicyOnly_fillInWithGlobal() {
setupZenConfig();
// when there's only one automatic rule active and it has a custom policy, make sure that's
- // what the consolidated policy reflects whether or not it's stricter than what the default
- // would specify.
+ // what the consolidated policy reflects whether or not it's stricter than what the global
+ // config would specify.
ZenPolicy customPolicy = new ZenPolicy.Builder()
.allowAlarms(true) // more lenient than default
.allowMedia(true) // more lenient than default
@@ -3249,7 +3634,51 @@
}
@Test
- public void testUpdateConsolidatedPolicy_defaultAndCustomActive() {
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void testUpdateConsolidatedPolicy_modesApiCustomPolicyOnly_fillInWithDeviceDefault() {
+ setupZenConfig();
+
+ // when there's only one automatic rule active and it has a custom policy, make sure that's
+ // what the consolidated policy reflects whether or not it's stricter than what the default
+ // would specify.
+ ZenPolicy customPolicy = new ZenPolicy.Builder()
+ .allowSystem(true) // more lenient than default
+ .allowRepeatCallers(false) // more restrictive than default
+ .allowCalls(ZenPolicy.PEOPLE_TYPE_NONE) // more restrictive than default
+ .showFullScreenIntent(true) // more lenient
+ .showBadges(false) // more restrictive
+ .build();
+
+ AutomaticZenRule zenRule = new AutomaticZenRule("name",
+ null,
+ new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
+ ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+ customPolicy,
+ NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+ String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+
+ // enable the rule; this will update the consolidated policy
+ mZenModeHelper.setAutomaticZenRuleState(id,
+ new Condition(zenRule.getConditionId(), "", STATE_TRUE),
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+
+ // since this is the only active rule, the consolidated policy should match the custom
+ // policy for every field specified, and take default values for unspecified things
+ assertThat(mZenModeHelper.mConsolidatedPolicy.allowAlarms()).isTrue(); // default
+ assertThat(mZenModeHelper.mConsolidatedPolicy.allowMedia()).isTrue(); // default
+ assertThat(mZenModeHelper.mConsolidatedPolicy.allowSystem()).isTrue(); // custom
+ assertThat(mZenModeHelper.mConsolidatedPolicy.allowReminders()).isFalse(); // default
+ assertThat(mZenModeHelper.mConsolidatedPolicy.allowCalls()).isFalse(); // custom
+ assertThat(mZenModeHelper.mConsolidatedPolicy.allowMessages()).isTrue(); // default
+ assertThat(mZenModeHelper.mConsolidatedPolicy.allowRepeatCallers()).isFalse(); // custom
+ assertThat(mZenModeHelper.mConsolidatedPolicy.showBadges()).isFalse(); // custom
+ assertThat(mZenModeHelper.mConsolidatedPolicy.showFullScreenIntents()).isTrue(); // custom
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MODES_API)
+ public void testUpdateConsolidatedPolicy_preModesApiDefaultAndCustomActive_mergesWithGlobal() {
setupZenConfig();
// when there are two rules active, one inheriting the default policy and one setting its
@@ -3309,6 +3738,68 @@
}
@Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void testUpdateConsolidatedPolicy_modesApiDefaultAndCustomActive_mergesWithDefault() {
+ setupZenConfig();
+
+ // when there are two rules active, one inheriting the default policy and one setting its
+ // own custom policy, they should be merged to form the most restrictive combination.
+
+ // rule 1: no custom policy
+ AutomaticZenRule zenRule = new AutomaticZenRule("name",
+ null,
+ new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
+ ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+ null,
+ NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+ String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+
+ // enable rule 1
+ mZenModeHelper.setAutomaticZenRuleState(id,
+ new Condition(zenRule.getConditionId(), "", STATE_TRUE),
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+
+ // custom policy for rule 2
+ ZenPolicy customPolicy = new ZenPolicy.Builder()
+ .allowAlarms(false) // more restrictive than default
+ .allowSystem(true) // more lenient than default
+ .allowRepeatCallers(false) // more restrictive than default
+ .allowCalls(ZenPolicy.PEOPLE_TYPE_NONE) // more restrictive than default
+ .showBadges(false) // more restrictive
+ .showPeeking(true) // more lenient
+ .build();
+
+ AutomaticZenRule zenRule2 = new AutomaticZenRule("name2",
+ null,
+ new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
+ ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+ customPolicy,
+ NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+ String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+
+ // enable rule 2; this will update the consolidated policy
+ mZenModeHelper.setAutomaticZenRuleState(id2,
+ new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
+ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+
+ // now both rules should be on, and the consolidated policy should reflect the most
+ // restrictive option of each of the two
+ assertThat(mZenModeHelper.mConsolidatedPolicy.allowAlarms()).isFalse(); // custom stricter
+ assertThat(mZenModeHelper.mConsolidatedPolicy.allowMedia()).isTrue(); // default
+ assertThat(mZenModeHelper.mConsolidatedPolicy.allowSystem()).isFalse(); // default stricter
+ assertThat(mZenModeHelper.mConsolidatedPolicy.allowReminders()).isFalse(); // default
+ assertThat(mZenModeHelper.mConsolidatedPolicy.allowCalls()).isFalse(); // custom stricter
+ assertThat(mZenModeHelper.mConsolidatedPolicy.allowMessages()).isTrue(); // default
+ assertThat(mZenModeHelper.mConsolidatedPolicy.allowConversations()).isTrue(); // default
+ assertThat(mZenModeHelper.mConsolidatedPolicy.allowRepeatCallers())
+ .isFalse(); // custom stricter
+ assertThat(mZenModeHelper.mConsolidatedPolicy.showBadges()).isFalse(); // custom stricter
+ assertThat(mZenModeHelper.mConsolidatedPolicy.showPeeking()).isFalse(); // default stricter
+ }
+
+ @Test
public void testUpdateConsolidatedPolicy_allowChannels() {
mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
setupZenConfig();
@@ -3372,7 +3863,10 @@
null,
new ComponentName(CUSTOM_PKG_NAME, "cls"),
Uri.parse("priority"),
- new ZenPolicy.Builder().allowMedia(true).build(),
+ new ZenPolicy.Builder()
+ .allowMedia(true)
+ .allowSystem(true)
+ .build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String rule1Id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
zenRuleWithPriority, UPDATE_ORIGIN_APP, "test", CUSTOM_PKG_UID);
@@ -3394,10 +3888,10 @@
UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
// Consolidated Policy should be default + rule1.
- assertThat(mZenModeHelper.mConsolidatedPolicy.allowAlarms()).isFalse(); // default
+ assertThat(mZenModeHelper.mConsolidatedPolicy.allowAlarms()).isTrue(); // default
assertThat(mZenModeHelper.mConsolidatedPolicy.allowMedia()).isTrue(); // priority rule
- assertThat(mZenModeHelper.mConsolidatedPolicy.allowSystem()).isFalse(); // default
- assertThat(mZenModeHelper.mConsolidatedPolicy.allowReminders()).isTrue(); // default
+ assertThat(mZenModeHelper.mConsolidatedPolicy.allowSystem()).isTrue(); // priority rule
+ assertThat(mZenModeHelper.mConsolidatedPolicy.allowReminders()).isFalse(); // default
assertThat(mZenModeHelper.mConsolidatedPolicy.allowCalls()).isTrue(); // default
assertThat(mZenModeHelper.mConsolidatedPolicy.allowMessages()).isTrue(); // default
assertThat(mZenModeHelper.mConsolidatedPolicy.allowConversations()).isTrue(); // default
@@ -3408,7 +3902,7 @@
public void zenRuleToAutomaticZenRule_allFields() {
mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(
- new String[] {OWNER.getPackageName()});
+ new String[]{OWNER.getPackageName()});
ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
rule.configurationActivity = CONFIG_ACTIVITY;
@@ -3452,7 +3946,7 @@
public void automaticZenRuleToZenRule_allFields() {
mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(
- new String[] {OWNER.getPackageName()});
+ new String[]{OWNER.getPackageName()});
AutomaticZenRule azr = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
.setEnabled(true)
@@ -3478,7 +3972,8 @@
assertEquals(CONDITION_ID, storedRule.conditionId);
assertEquals(INTERRUPTION_FILTER_ZR, storedRule.zenMode);
assertEquals(ENABLED, storedRule.enabled);
- assertEquals(POLICY, storedRule.zenPolicy);
+ assertEquals(mZenModeHelper.getDefaultZenPolicy().overwrittenWith(POLICY),
+ storedRule.zenPolicy);
assertEquals(CONFIG_ACTIVITY, storedRule.configurationActivity);
assertEquals(TYPE, storedRule.type);
assertEquals(ALLOW_MANUAL, storedRule.allowManualInvocation);
@@ -3561,12 +4056,12 @@
// Modifies the zen policy and device effects
ZenPolicy policy = new ZenPolicy.Builder(rule.getZenPolicy())
- .allowPriorityChannels(true)
+ .allowPriorityChannels(false)
.build();
ZenDeviceEffects deviceEffects =
new ZenDeviceEffects.Builder(rule.getDeviceEffects())
- .setShouldDisplayGrayscale(true)
- .build();
+ .setShouldDisplayGrayscale(true)
+ .build();
AutomaticZenRule azrUpdate = new AutomaticZenRule.Builder(rule)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(policy)
@@ -3580,7 +4075,8 @@
// UPDATE_ORIGIN_USER should change the bitmask and change the values.
assertThat(rule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_PRIORITY);
- assertThat(rule.getZenPolicy().getPriorityChannels()).isEqualTo(ZenPolicy.STATE_ALLOW);
+ assertThat(rule.getZenPolicy().getPriorityChannels()).isEqualTo(ZenPolicy.STATE_DISALLOW);
+
assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue();
ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
@@ -3770,9 +4266,9 @@
@Test
@EnableFlags(Flags.FLAG_MODES_API)
public void updateAutomaticZenRule_nullPolicyUpdate() {
- // Adds a starting rule with empty zen policies and device effects
+ // Adds a starting rule with set zen policy and empty device effects
AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
- .setZenPolicy(new ZenPolicy.Builder().build())
+ .setZenPolicy(POLICY)
.build();
// Adds the rule using the app, to avoid having any user modified bits set.
String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
@@ -3790,8 +4286,10 @@
Process.SYSTEM_UID);
rule = mZenModeHelper.getAutomaticZenRule(ruleId);
- // When AZR's ZenPolicy is null, we expect the updated rule's policy to be null.
- assertThat(rule.getZenPolicy()).isNull();
+ // When AZR's ZenPolicy is null, we expect the updated rule's policy to be unchanged
+ // (equivalent to the provided policy, with additional fields filled in with defaults).
+ assertThat(rule.getZenPolicy()).isEqualTo(
+ mZenModeHelper.getDefaultZenPolicy().overwrittenWith(POLICY));
}
@Test
@@ -3847,13 +4345,13 @@
assertThat(storedRule.canBeUpdatedByApp()).isFalse();
assertThat(storedRule.zenPolicyUserModifiedFields).isEqualTo(
ZenPolicy.FIELD_ALLOW_CHANNELS
- | ZenPolicy.FIELD_PRIORITY_CATEGORY_REMINDERS
- | ZenPolicy.FIELD_PRIORITY_CATEGORY_EVENTS
- | ZenPolicy.FIELD_PRIORITY_CATEGORY_SYSTEM
- | ZenPolicy.FIELD_VISUAL_EFFECT_FULL_SCREEN_INTENT
- | ZenPolicy.FIELD_VISUAL_EFFECT_LIGHTS
- | ZenPolicy.FIELD_VISUAL_EFFECT_PEEK
- | ZenPolicy.FIELD_VISUAL_EFFECT_AMBIENT
+ | ZenPolicy.FIELD_PRIORITY_CATEGORY_REMINDERS
+ | ZenPolicy.FIELD_PRIORITY_CATEGORY_EVENTS
+ | ZenPolicy.FIELD_PRIORITY_CATEGORY_SYSTEM
+ | ZenPolicy.FIELD_VISUAL_EFFECT_FULL_SCREEN_INTENT
+ | ZenPolicy.FIELD_VISUAL_EFFECT_LIGHTS
+ | ZenPolicy.FIELD_VISUAL_EFFECT_PEEK
+ | ZenPolicy.FIELD_VISUAL_EFFECT_AMBIENT
);
}
@@ -4016,6 +4514,7 @@
final int[] actualStatus = new int[2];
ZenModeHelper.Callback callback = new ZenModeHelper.Callback() {
int i = 0;
+
@Override
void onAutomaticRuleStatusChanged(int userId, String pkg, String id, int status) {
if (Objects.equals(createdId, id)) {
@@ -4056,6 +4555,7 @@
final int[] actualStatus = new int[2];
ZenModeHelper.Callback callback = new ZenModeHelper.Callback() {
int i = 0;
+
@Override
void onAutomaticRuleStatusChanged(int userId, String pkg, String id, int status) {
if (Objects.equals(createdId, id)) {
@@ -4202,6 +4702,7 @@
.build()),
eq(UPDATE_ORIGIN_APP));
}
+
@Test
public void testDeviceEffects_noChangeToConsolidatedEffects_notApplied() {
mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
@@ -4607,7 +5108,8 @@
.comparingElementsUsing(IGNORE_METADATA)
.containsExactly(
expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
- null, true));
+ mZenModeHelper.mConfig.toZenPolicy(), // copy of global config
+ true));
}
@Test
@@ -4626,7 +5128,9 @@
assertThat(mZenModeHelper.mConfig.automaticRules.values())
.comparingElementsUsing(IGNORE_METADATA)
.containsExactly(
- expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_ALARMS, null, true));
+ expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_ALARMS,
+ mZenModeHelper.mConfig.toZenPolicy(), // copy of global config
+ true));
}
@Test
@@ -4820,6 +5324,10 @@
mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, CUSTOM_PKG_UID, originalPolicy);
String ruleId = getOnlyElement(mZenModeHelper.mConfig.automaticRules.keySet());
+ // Store this for checking later.
+ ZenPolicy originalEffectiveZenPolicy = new ZenPolicy.Builder(
+ mZenModeHelper.mConfig.toZenPolicy()).allowMedia(true).build();
+
// From user, update that rule's policy.
AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
ZenPolicy userUpdateZenPolicy = new ZenPolicy.Builder().disallowAllSounds()
@@ -4839,7 +5347,9 @@
.comparingElementsUsing(IGNORE_METADATA)
.containsExactly(
expectedImplicitRule(pkg, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
- userUpdateZenPolicy,
+ // the final policy for the rule should contain the user's update
+ // overlaid on top of the original existing policy.
+ originalEffectiveZenPolicy.overwrittenWith(userUpdateZenPolicy),
/* conditionActive= */ null));
}
@@ -4854,6 +5364,10 @@
mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, CUSTOM_PKG_UID, originalPolicy);
String ruleId = getOnlyElement(mZenModeHelper.mConfig.automaticRules.keySet());
+ // Store this for checking later.
+ ZenPolicy originalEffectiveZenPolicy = new ZenPolicy.Builder(
+ mZenModeHelper.mConfig.toZenPolicy()).allowMedia(true).build();
+
// From user, update something in that rule, but not the ZenPolicy.
AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
@@ -4873,7 +5387,7 @@
.allowPriorityChannels(true)
.build();
assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenPolicy)
- .isEqualTo(appsSecondZenPolicy);
+ .isEqualTo(originalEffectiveZenPolicy.overwrittenWith(appsSecondZenPolicy));
}
@Test
@@ -4905,14 +5419,18 @@
}
@Test
- public void getNotificationPolicyFromImplicitZenRule_ruleWithoutPolicy_returnsGlobalPolicy() {
+ public void getNotificationPolicyFromImplicitZenRule_ruleWithoutPolicy_copiesGlobalPolicy() {
mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
-
- mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
- ZEN_MODE_ALARMS);
mZenModeHelper.mConfig.allowCalls = true;
mZenModeHelper.mConfig.allowConversations = false;
+ // Implicit rule will get the global policy at the time of rule creation.
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
+ ZEN_MODE_ALARMS);
+
+ // If the policy then changes afterwards, we should keep the snapshotted version.
+ mZenModeHelper.mConfig.allowCalls = false;
+
Policy readPolicy = mZenModeHelper.getNotificationPolicyFromImplicitZenRule(
CUSTOM_PKG_NAME);
@@ -4952,7 +5470,7 @@
p.recycle();
}
},
- "Ignoring timestamp and userModifiedFields");
+ "Ignoring timestamp and userModifiedFields");
private ZenRule expectedImplicitRule(String ownerPkg, int zenMode, ZenPolicy policy,
@Nullable Boolean conditionActive) {
@@ -4962,7 +5480,7 @@
if (conditionActive != null) {
rule.condition = conditionActive
? new Condition(rule.conditionId,
- mContext.getString(R.string.zen_mode_implicit_activated), STATE_TRUE)
+ mContext.getString(R.string.zen_mode_implicit_activated), STATE_TRUE)
: new Condition(rule.conditionId,
mContext.getString(R.string.zen_mode_implicit_deactivated),
STATE_FALSE);
@@ -5030,8 +5548,35 @@
assertEquals(STATE_ALLOW, dndProto.getNotificationList().getNumber());
}
+ private void checkDndProtoMatchesDefaultZenConfig(DNDPolicyProto dndProto) {
+ if (!Flags.modesApi()) {
+ checkDndProtoMatchesSetupZenConfig(dndProto);
+ return;
+ }
+
+ // When modes_api flag is on, the default zen config is the device defaults.
+ assertThat(dndProto.getAlarms().getNumber()).isEqualTo(STATE_ALLOW);
+ assertThat(dndProto.getMedia().getNumber()).isEqualTo(STATE_ALLOW);
+ assertThat(dndProto.getSystem().getNumber()).isEqualTo(STATE_DISALLOW);
+ assertThat(dndProto.getReminders().getNumber()).isEqualTo(STATE_DISALLOW);
+ assertThat(dndProto.getCalls().getNumber()).isEqualTo(STATE_ALLOW);
+ assertThat(dndProto.getAllowCallsFrom().getNumber()).isEqualTo(PEOPLE_STARRED);
+ assertThat(dndProto.getMessages().getNumber()).isEqualTo(STATE_ALLOW);
+ assertThat(dndProto.getAllowMessagesFrom().getNumber()).isEqualTo(PEOPLE_STARRED);
+ assertThat(dndProto.getEvents().getNumber()).isEqualTo(STATE_DISALLOW);
+ assertThat(dndProto.getRepeatCallers().getNumber()).isEqualTo(STATE_ALLOW);
+ assertThat(dndProto.getFullscreen().getNumber()).isEqualTo(STATE_DISALLOW);
+ assertThat(dndProto.getLights().getNumber()).isEqualTo(STATE_DISALLOW);
+ assertThat(dndProto.getPeek().getNumber()).isEqualTo(STATE_DISALLOW);
+ assertThat(dndProto.getStatusBar().getNumber()).isEqualTo(STATE_ALLOW);
+ assertThat(dndProto.getBadge().getNumber()).isEqualTo(STATE_ALLOW);
+ assertThat(dndProto.getAmbient().getNumber()).isEqualTo(STATE_DISALLOW);
+ assertThat(dndProto.getNotificationList().getNumber()).isEqualTo(STATE_ALLOW);
+ }
+
private static void withoutWtfCrash(Runnable test) {
- Log.TerribleFailureHandler oldHandler = Log.setWtfHandler((tag, what, system) -> {});
+ Log.TerribleFailureHandler oldHandler = Log.setWtfHandler((tag, what, system) -> {
+ });
try {
test.run();
} finally {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
index 4ed55df..57e1132 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
@@ -269,6 +269,78 @@
}
@Test
+ public void testZenPolicyOverwrite_allUnsetPolicies() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+
+ ZenPolicy.Builder builder = new ZenPolicy.Builder();
+ ZenPolicy unset = builder.build();
+
+ builder.allowCalls(ZenPolicy.PEOPLE_TYPE_CONTACTS);
+ builder.allowMedia(false);
+ builder.allowEvents(true);
+ builder.showFullScreenIntent(false);
+ builder.showInNotificationList(false);
+ ZenPolicy set = builder.build();
+
+ ZenPolicy overwritten = set.overwrittenWith(unset);
+ assertThat(overwritten).isEqualTo(set);
+
+ // should actually work the other way too.
+ ZenPolicy overwrittenWithSet = unset.overwrittenWith(set);
+ assertThat(overwrittenWithSet).isEqualTo(set);
+ }
+
+ @Test
+ public void testZenPolicyOverwrite_someOverlappingFields_takeNewPolicy() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+
+ ZenPolicy p1 = new ZenPolicy.Builder()
+ .allowCalls(ZenPolicy.PEOPLE_TYPE_CONTACTS)
+ .allowMessages(ZenPolicy.PEOPLE_TYPE_STARRED)
+ .allowMedia(false)
+ .showBadges(true)
+ .build();
+
+ ZenPolicy p2 = new ZenPolicy.Builder()
+ .allowRepeatCallers(false)
+ .allowConversations(ZenPolicy.CONVERSATION_SENDERS_IMPORTANT)
+ .allowMessages(ZenPolicy.PEOPLE_TYPE_NONE)
+ .showBadges(false)
+ .showPeeking(true)
+ .build();
+
+ // when p1 is overwritten with p2, all values from p2 win regardless of strictness, and
+ // remaining fields take values from p1.
+ ZenPolicy p1OverwrittenWithP2 = p1.overwrittenWith(p2);
+ assertThat(p1OverwrittenWithP2.getPriorityCallSenders())
+ .isEqualTo(ZenPolicy.PEOPLE_TYPE_CONTACTS); // from p1
+ assertThat(p1OverwrittenWithP2.getPriorityMessageSenders())
+ .isEqualTo(ZenPolicy.PEOPLE_TYPE_NONE); // from p2
+ assertThat(p1OverwrittenWithP2.getPriorityCategoryRepeatCallers())
+ .isEqualTo(ZenPolicy.STATE_DISALLOW); // from p2
+ assertThat(p1OverwrittenWithP2.getPriorityCategoryMedia())
+ .isEqualTo(ZenPolicy.STATE_DISALLOW); // from p1
+ assertThat(p1OverwrittenWithP2.getVisualEffectBadge())
+ .isEqualTo(ZenPolicy.STATE_DISALLOW); // from p2
+ assertThat(p1OverwrittenWithP2.getVisualEffectPeek())
+ .isEqualTo(ZenPolicy.STATE_ALLOW); // from p2
+
+ ZenPolicy p2OverwrittenWithP1 = p2.overwrittenWith(p1);
+ assertThat(p2OverwrittenWithP1.getPriorityCallSenders())
+ .isEqualTo(ZenPolicy.PEOPLE_TYPE_CONTACTS); // from p1
+ assertThat(p2OverwrittenWithP1.getPriorityMessageSenders())
+ .isEqualTo(ZenPolicy.PEOPLE_TYPE_STARRED); // from p1
+ assertThat(p2OverwrittenWithP1.getPriorityCategoryRepeatCallers())
+ .isEqualTo(ZenPolicy.STATE_DISALLOW); // from p2
+ assertThat(p2OverwrittenWithP1.getPriorityCategoryMedia())
+ .isEqualTo(ZenPolicy.STATE_DISALLOW); // from p1
+ assertThat(p2OverwrittenWithP1.getVisualEffectBadge())
+ .isEqualTo(ZenPolicy.STATE_ALLOW); // from p1
+ assertThat(p2OverwrittenWithP1.getVisualEffectPeek())
+ .isEqualTo(ZenPolicy.STATE_ALLOW); // from p2
+ }
+
+ @Test
public void testZenPolicyMessagesInvalid() {
ZenPolicy.Builder builder = new ZenPolicy.Builder();
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index a5c6d57..1bf11df 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -1618,14 +1618,15 @@
}
/**
- * Register for changes to the list of active {@link SubscriptionInfo} records or to the
- * individual records themselves. When a change occurs the onSubscriptionsChanged method of
- * the listener will be invoked immediately if there has been a notification. The
- * onSubscriptionChanged method will also be triggered once initially when calling this
- * function.
+ * Register for changes to the list of {@link SubscriptionInfo} records or to the
+ * individual records (active or inactive) themselves. When a change occurs, the
+ * {@link OnSubscriptionsChangedListener#onSubscriptionsChanged()} method of
+ * the listener will be invoked immediately. The
+ * {@link OnSubscriptionsChangedListener#onSubscriptionsChanged()} method will also be invoked
+ * once initially when calling this method.
*
* @param listener an instance of {@link OnSubscriptionsChangedListener} with
- * onSubscriptionsChanged overridden.
+ * {@link OnSubscriptionsChangedListener#onSubscriptionsChanged()} overridden.
* @param executor the executor that will execute callbacks.
*/
public void addOnSubscriptionsChangedListener(
@@ -1953,7 +1954,6 @@
* {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
*/
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
- // @RequiresPermission(TODO(b/308809058))
public List<SubscriptionInfo> getActiveSubscriptionInfoList() {
List<SubscriptionInfo> activeList = null;
@@ -2010,6 +2010,9 @@
* Create a new subscription manager instance that can see all subscriptions across
* user profiles.
*
+ * The permission check for accessing all subscriptions will be enforced upon calling the
+ * individual APIs linked below.
+ *
* @return a SubscriptionManager that can see all subscriptions regardless its user profile
* association.
*
@@ -2018,9 +2021,7 @@
* @see UserHandle
*/
@FlaggedApi(Flags.FLAG_ENFORCE_SUBSCRIPTION_USER_FILTER)
- // @RequiresPermission(TODO(b/308809058))
- // The permission check for accessing all subscriptions will be enforced upon calling the
- // individual APIs linked above.
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_PROFILES)
@NonNull public SubscriptionManager createForAllUserProfiles() {
return new SubscriptionManager(mContext, true/*isForAllUserProfiles*/);
}
@@ -2215,7 +2216,6 @@
* {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
*/
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
- // @RequiresPermission(TODO(b/308809058))
public int getActiveSubscriptionInfoCount() {
int result = 0;
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 67bb1f0..cbd5524 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -13148,7 +13148,7 @@
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public ServiceState getServiceStateForSubscriber(int subId) {
- return getServiceStateForSubscriber(getSubId(), false, false);
+ return getServiceStateForSubscriber(subId, false, false);
}
/**
@@ -18492,7 +18492,6 @@
/**
* Get last known cell identity.
- * Require appropriate permissions, otherwise throws SecurityException.
*
* If there is current registered network this value will be same as the registered cell
* identity. If the device goes out of service the previous cell identity is cached and
@@ -18504,7 +18503,7 @@
@SystemApi
@FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
@RequiresPermission(allOf = {Manifest.permission.ACCESS_FINE_LOCATION,
- "com.android.phone.permission.ACCESS_LAST_KNOWN_CELL_ID"})
+ Manifest.permission.ACCESS_LAST_KNOWN_CELL_ID})
public @Nullable CellIdentity getLastKnownCellIdentity() {
try {
ITelephony telephony = getITelephony();
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index 60a9f0b..256a469 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -295,13 +295,13 @@
verify(native).setPointerIconVisibility(10, false)
verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL))
localService.setMousePointerAccelerationEnabled(false, 10)
- verify(native).setMousePointerAccelerationEnabled(eq(false))
+ verify(native).setMousePointerAccelerationEnabled(10, false)
service.onDisplayRemoved(10)
verify(native).setPointerIconVisibility(10, true)
verify(native).displayRemoved(eq(10))
verify(native).setPointerIconType(eq(PointerIcon.TYPE_NOT_SPECIFIED))
- verify(native).setMousePointerAccelerationEnabled(true)
+ verify(native).setMousePointerAccelerationEnabled(10, true)
verifyNoMoreInteractions(native)
// This call should not block because the virtual mouse pointer override was never removed.
@@ -319,26 +319,24 @@
verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL))
verify(native).setPointerIconVisibility(10, false)
localService.setMousePointerAccelerationEnabled(false, 10)
- verify(native).setMousePointerAccelerationEnabled(eq(false))
+ verify(native).setMousePointerAccelerationEnabled(10, false)
localService.setPointerIconVisible(true, 10)
verify(native).setPointerIconType(eq(PointerIcon.TYPE_NOT_SPECIFIED))
verify(native).setPointerIconVisibility(10, true)
localService.setMousePointerAccelerationEnabled(true, 10)
- verify(native).setMousePointerAccelerationEnabled(eq(true))
+ verify(native).setMousePointerAccelerationEnabled(10, true)
- // Verify that setting properties on a different display is not propagated until the
- // pointer is moved to that display.
localService.setPointerIconVisible(false, 20)
verify(native).setPointerIconVisibility(20, false)
localService.setMousePointerAccelerationEnabled(false, 20)
+ verify(native).setMousePointerAccelerationEnabled(20, false)
verifyNoMoreInteractions(native)
clearInvocations(native)
setVirtualMousePointerDisplayIdAndVerify(20)
verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL))
- verify(native).setMousePointerAccelerationEnabled(eq(false))
}
@Test
@@ -347,12 +345,12 @@
localService.setMousePointerAccelerationEnabled(false, 10)
verify(native).setPointerIconVisibility(10, false)
+ verify(native).setMousePointerAccelerationEnabled(10, false)
verifyNoMoreInteractions(native)
setVirtualMousePointerDisplayIdAndVerify(10)
verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL))
- verify(native).setMousePointerAccelerationEnabled(eq(false))
}
@Test
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
index cbdcb88..518183f 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
@@ -30,6 +30,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.content.rollback.RollbackInfo;
import android.content.rollback.RollbackManager;
import android.os.UserManager;
@@ -146,7 +147,8 @@
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
// Upgrade from v1 to v2, with rollbacks enabled.
- Install.single(TestApp.A2).setEnableRollback().commit();
+ Install.single(TestApp.A2).setEnableRollback().setRollbackImpactLevel(
+ PackageManager.ROLLBACK_USER_IMPACT_HIGH).commit();
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
// The app should now be available for rollback.
@@ -154,6 +156,8 @@
assertThat(available).isNotStaged();
assertThat(available).packagesContainsExactly(
Rollback.from(TestApp.A2).to(TestApp.A1));
+ assertThat(available.getRollbackImpactLevel()).isEqualTo(
+ PackageManager.ROLLBACK_USER_IMPACT_HIGH);
// We should not have received any rollback requests yet.
// TODO: Possibly flaky if, by chance, some other app on device
@@ -264,6 +268,8 @@
RollbackInfo rollbackB = waitForAvailableRollback(TestApp.B);
assertThat(rollbackB).packagesContainsExactly(
Rollback.from(TestApp.B2).to(TestApp.B1));
+ assertThat(rollbackB.getRollbackImpactLevel()).isEqualTo(
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
// Register rollback committed receiver
RollbackBroadcastReceiver rollbackReceiver = new RollbackBroadcastReceiver();