diff options
179 files changed, 2805 insertions, 1189 deletions
diff --git a/Android.bp b/Android.bp index 986a07108b91..b5f7e99c8823 100644 --- a/Android.bp +++ b/Android.bp @@ -695,12 +695,10 @@ stubs_defaults { "--hide CallbackInterface", "--hide DeprecationMismatch", "--hide HiddenSuperclass", - "--hide HiddenTypeParameter", "--hide MissingPermission", "--hide RequiresPermission", "--hide SdkConstant", "--hide Todo", - "--hide UnavailableSymbol", "--hide-package android.audio.policy.configuration.V7_0", "--hide-package com.android.server", "--manifest $(location :frameworks-base-core-AndroidManifest.xml)", diff --git a/core/api/current.txt b/core/api/current.txt index cce83292b2c9..18001e832afd 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -33455,7 +33455,9 @@ package android.os { method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.os.Bundle getUserRestrictions(android.os.UserHandle); method public boolean hasUserRestriction(String); method public boolean isAdminUser(); + method @FlaggedApi("android.multiuser.support_communal_profile") public boolean isCommunalProfile(); method public boolean isDemoUser(); + method @FlaggedApi("android.multiuser.support_communal_profile_nextgen") public boolean isForegroundUserAdmin(); method public static boolean isHeadlessSystemUserMode(); method public boolean isManagedProfile(); method public boolean isProfile(); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 119e0ad4e3da..6062f7919644 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3875,6 +3875,7 @@ package android.content.pm { method public void setInstallAsInstantApp(boolean); method public void setInstallAsVirtualPreload(); method public void setRequestDowngrade(boolean); + 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(); } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index e130206d49fa..83b68808b62c 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -2148,7 +2148,9 @@ package android.os { } public final class BugreportParams { + field @FlaggedApi("android.app.admin.flags.onboarding_bugreport_v2_enabled") public static final int BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL = 4; // 0x4 field @FlaggedApi("android.os.bugreport_mode_max_value") public static final int BUGREPORT_MODE_MAX_VALUE = 7; // 0x7 + field @FlaggedApi("android.app.admin.flags.onboarding_bugreport_v2_enabled") public static final int BUGREPORT_MODE_ONBOARDING = 7; // 0x7 } public class Build { @@ -2355,6 +2357,7 @@ package android.os { method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createUser(@Nullable String, @NonNull String, int); method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getAliveUsers(); method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.os.UserHandle getBootUser(); + method @FlaggedApi("android.multiuser.support_communal_profile") @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public android.os.UserHandle getCommunalProfile(); method public int getMainDisplayIdAssignedToUser(); method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.Set<java.lang.String> getPreInstallableSystemPackages(@NonNull String); method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public String getUserType(); diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index ecbc9b1d52c2..9a19d8edf8a8 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -8638,8 +8638,8 @@ public class AppOpsManager { } } - SyncNotedAppOp syncOp = mService.noteProxyOperation(op, attributionSource.asState(), - collectionMode == COLLECT_ASYNC, message, + SyncNotedAppOp syncOp = mService.noteProxyOperationWithState(op, + attributionSource.asState(), collectionMode == COLLECT_ASYNC, message, shouldCollectMessage, skipProxyOperation); if (syncOp.getOpMode() == MODE_ALLOWED) { @@ -9110,7 +9110,7 @@ public class AppOpsManager { } } - SyncNotedAppOp syncOp = mService.startProxyOperation(clientId, op, + SyncNotedAppOp syncOp = mService.startProxyOperationWithState(clientId, op, attributionSource.asState(), false, collectionMode == COLLECT_ASYNC, message, shouldCollectMessage, skipProxyOperation, proxyAttributionFlags, proxiedAttributionFlags, attributionChainId); @@ -9229,8 +9229,8 @@ public class AppOpsManager { public void finishProxyOp(@NonNull IBinder clientId, @NonNull String op, @NonNull AttributionSource attributionSource, boolean skipProxyOperation) { try { - mService.finishProxyOperation(clientId, strOpToOp(op), attributionSource.asState(), - skipProxyOperation); + mService.finishProxyOperationWithState( + clientId, strOpToOp(op), attributionSource.asState(), skipProxyOperation); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java index 2d554031ab48..545ba8e81f62 100644 --- a/core/java/android/app/KeyguardManager.java +++ b/core/java/android/app/KeyguardManager.java @@ -26,6 +26,7 @@ import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; +import android.annotation.UserIdInt; import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager.PasswordComplexity; import android.app.admin.PasswordMetrics; @@ -736,7 +737,7 @@ public class KeyguardManager { * @see #isKeyguardLocked() */ public boolean isDeviceLocked() { - return isDeviceLocked(mContext.getUserId()); + return isDeviceLocked(mContext.getUserId(), mContext.getDeviceId()); } /** @@ -746,8 +747,17 @@ public class KeyguardManager { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public boolean isDeviceLocked(int userId) { + return isDeviceLocked(userId, mContext.getDeviceId()); + } + + /** + * Per-user per-device version of {@link #isDeviceLocked()}. + * + * @hide + */ + public boolean isDeviceLocked(@UserIdInt int userId, int deviceId) { try { - return mTrustManager.isDeviceLocked(userId, mContext.getAssociatedDisplayId()); + return mTrustManager.isDeviceLocked(userId, deviceId); } catch (RemoteException e) { return false; } @@ -769,7 +779,7 @@ public class KeyguardManager { * @see #isKeyguardSecure() */ public boolean isDeviceSecure() { - return isDeviceSecure(mContext.getUserId()); + return isDeviceSecure(mContext.getUserId(), mContext.getDeviceId()); } /** @@ -779,8 +789,17 @@ public class KeyguardManager { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public boolean isDeviceSecure(int userId) { + return isDeviceSecure(userId, mContext.getDeviceId()); + } + + /** + * Per-user per-device version of {@link #isDeviceSecure()}. + * + * @hide + */ + public boolean isDeviceSecure(@UserIdInt int userId, int deviceId) { try { - return mTrustManager.isDeviceSecure(userId, mContext.getAssociatedDisplayId()); + return mTrustManager.isDeviceSecure(userId, deviceId); } catch (RemoteException e) { return false; } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 3bde39c03f25..bbc843b0acc5 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -374,7 +374,7 @@ public class Notification implements Parcelable * that you take care of task management as described in the * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back * Stack</a> document. In particular, make sure to read the - * <a href="{@docRoot}/training/notify-user/navigation">Start + * <a href="{@docRoot}training/notify-user/navigation">Start * an Activity from a Notification</a> page for the correct ways to launch an application from a * notification. */ @@ -5606,7 +5606,8 @@ public class Notification implements Parcelable contentView.setInt(R.id.expand_button, "setDefaultPillColor", pillColor); // Use different highlighted colors for conversations' unread count if (p.mHighlightExpander) { - pillColor = Colors.flattenAlpha(getColors(p).getTertiaryAccentColor(), bgColor); + pillColor = Colors.flattenAlpha( + getColors(p).getTertiaryFixedDimAccentColor(), bgColor); textColor = Colors.flattenAlpha( getColors(p).getOnTertiaryAccentTextColor(), pillColor); } @@ -12835,6 +12836,9 @@ public class Notification implements Parcelable private int mSecondaryAccentColor = COLOR_INVALID; private int mTertiaryAccentColor = COLOR_INVALID; private int mOnTertiaryAccentTextColor = COLOR_INVALID; + private int mTertiaryFixedDimAccentColor = COLOR_INVALID; + private int mOnTertiaryFixedAccentTextColor = COLOR_INVALID; + private int mErrorColor = COLOR_INVALID; private int mContrastColor = COLOR_INVALID; private int mRippleAlpha = 0x33; @@ -12892,7 +12896,7 @@ public class Notification implements Parcelable if (isColorized) { if (rawColor == COLOR_DEFAULT) { - int[] attrs = {R.attr.colorAccentSecondary}; + int[] attrs = {R.attr.materialColorSecondary}; try (TypedArray ta = obtainDayNightAttributes(ctx, attrs)) { mBackgroundColor = getColor(ta, 0, Color.WHITE); } @@ -12910,17 +12914,21 @@ public class Notification implements Parcelable mSecondaryAccentColor = mSecondaryTextColor; mTertiaryAccentColor = flattenAlpha(mPrimaryTextColor, mBackgroundColor); mOnTertiaryAccentTextColor = mBackgroundColor; + mTertiaryFixedDimAccentColor = mTertiaryAccentColor; + mOnTertiaryFixedAccentTextColor = mOnTertiaryAccentTextColor; mErrorColor = mPrimaryTextColor; mRippleAlpha = 0x33; } else { int[] attrs = { - R.attr.colorSurface, - R.attr.textColorPrimary, - R.attr.textColorSecondary, - R.attr.colorAccent, - R.attr.colorAccentSecondary, - R.attr.colorAccentTertiary, - R.attr.textColorOnAccent, + R.attr.materialColorSurfaceContainerHigh, + R.attr.materialColorOnSurface, + R.attr.materialColorOnSurfaceVariant, + R.attr.materialColorPrimary, + R.attr.materialColorSecondary, + R.attr.materialColorTertiary, + R.attr.materialColorOnTertiary, + R.attr.materialColorTertiaryFixedDim, + R.attr.materialColorOnTertiaryFixed, R.attr.colorError, R.attr.colorControlHighlight }; @@ -12932,8 +12940,10 @@ public class Notification implements Parcelable mSecondaryAccentColor = getColor(ta, 4, COLOR_INVALID); mTertiaryAccentColor = getColor(ta, 5, COLOR_INVALID); mOnTertiaryAccentTextColor = getColor(ta, 6, COLOR_INVALID); - mErrorColor = getColor(ta, 7, COLOR_INVALID); - mRippleAlpha = Color.alpha(getColor(ta, 8, 0x33ffffff)); + mTertiaryFixedDimAccentColor = getColor(ta, 7, COLOR_INVALID); + mOnTertiaryFixedAccentTextColor = getColor(ta, 8, COLOR_INVALID); + mErrorColor = getColor(ta, 9, COLOR_INVALID); + mRippleAlpha = Color.alpha(getColor(ta, 10, 0x33ffffff)); } mContrastColor = calculateContrastColor(ctx, rawColor, mPrimaryAccentColor, mBackgroundColor, nightMode); @@ -12961,6 +12971,14 @@ public class Notification implements Parcelable ContrastColorUtil.resolvePrimaryColor( ctx, mTertiaryAccentColor, nightMode), 0xFF); } + if (mTertiaryFixedDimAccentColor == COLOR_INVALID) { + mTertiaryFixedDimAccentColor = mContrastColor; + } + if (mOnTertiaryFixedAccentTextColor == COLOR_INVALID) { + mOnTertiaryFixedAccentTextColor = ColorUtils.setAlphaComponent( + ContrastColorUtil.resolvePrimaryColor( + ctx, mTertiaryFixedDimAccentColor, nightMode), 0xFF); + } if (mErrorColor == COLOR_INVALID) { mErrorColor = mPrimaryTextColor; } @@ -13034,6 +13052,16 @@ public class Notification implements Parcelable return mOnTertiaryAccentTextColor; } + /** @return the theme's tertiary fixed dim accent color for colored UI elements. */ + public @ColorInt int getTertiaryFixedDimAccentColor() { + return mTertiaryFixedDimAccentColor; + } + + /** @return the theme's text color to be used on the tertiary fixed accent color. */ + public @ColorInt int getOnTertiaryFixedAccentTextColor() { + return mOnTertiaryFixedAccentTextColor; + } + /** * @return the contrast-adjusted version of the color provided by the app, or the * primary text color when colorized. diff --git a/core/java/android/app/trust/ITrustManager.aidl b/core/java/android/app/trust/ITrustManager.aidl index a5e5135f2cb0..70c25ea3ab1c 100644 --- a/core/java/android/app/trust/ITrustManager.aidl +++ b/core/java/android/app/trust/ITrustManager.aidl @@ -34,8 +34,8 @@ interface ITrustManager { void unregisterTrustListener(in ITrustListener trustListener); void reportKeyguardShowingChanged(); void setDeviceLockedForUser(int userId, boolean locked); - boolean isDeviceLocked(int userId, int displayId); - boolean isDeviceSecure(int userId, int displayId); + boolean isDeviceLocked(int userId, int deviceId); + boolean isDeviceSecure(int userId, int deviceId); @EnforcePermission("TRUST_LISTENER") boolean isTrustUsuallyManaged(int userId); void unlockedByBiometricForUser(int userId, in BiometricSourceType source); diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index aefa55f30826..323592c43760 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -1204,12 +1204,15 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { /** * This change id is the gatekeeper for all treatments that force a given min aspect ratio. * Enabling this change will allow the following min aspect ratio treatments to be applied: - * OVERRIDE_MIN_ASPECT_RATIO_MEDIUM - * OVERRIDE_MIN_ASPECT_RATIO_LARGE + * <ul> + * <li>OVERRIDE_MIN_ASPECT_RATIO_MEDIUM + * <li>OVERRIDE_MIN_ASPECT_RATIO_LARGE + * </ul> * * If OVERRIDE_MIN_ASPECT_RATIO is applied, the min aspect ratio given in the app's manifest * will be overridden to the largest enabled aspect ratio treatment unless the app's manifest - * value is higher. + * value is higher. By default, this will only apply to activities with fixed portrait + * orientation if OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY is not explicitly disabled. * @hide */ @ChangeId diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index cd8938d1dd77..cbb20e08f368 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -2535,6 +2535,8 @@ public class PackageInstaller { public DataLoaderParams dataLoaderParams; /** {@hide} */ public int rollbackDataPolicy = PackageManager.ROLLBACK_DATA_POLICY_RESTORE; + /** @hide */ + public long rollbackLifetimeMillis = 0; /** {@hide} */ public boolean forceQueryableOverride; /** {@hide} */ @@ -2589,6 +2591,7 @@ public class PackageInstaller { dataLoaderParams = new DataLoaderParams(dataLoaderParamsParcel); } rollbackDataPolicy = source.readInt(); + rollbackLifetimeMillis = source.readLong(); requireUserAction = source.readInt(); packageSource = source.readInt(); applicationEnabledSettingPersistent = source.readBoolean(); @@ -2621,6 +2624,7 @@ public class PackageInstaller { ret.requiredInstalledVersionCode = requiredInstalledVersionCode; ret.dataLoaderParams = dataLoaderParams; ret.rollbackDataPolicy = rollbackDataPolicy; + ret.rollbackLifetimeMillis = rollbackLifetimeMillis; ret.requireUserAction = requireUserAction; ret.packageSource = packageSource; ret.applicationEnabledSettingPersistent = applicationEnabledSettingPersistent; @@ -2902,12 +2906,7 @@ public class PackageInstaller { */ @SystemApi public void setEnableRollback(boolean enable) { - if (enable) { - installFlags |= PackageManager.INSTALL_ENABLE_ROLLBACK; - } else { - installFlags &= ~PackageManager.INSTALL_ENABLE_ROLLBACK; - } - rollbackDataPolicy = PackageManager.ROLLBACK_DATA_POLICY_RESTORE; + setEnableRollback(enable, PackageManager.ROLLBACK_DATA_POLICY_RESTORE); } /** @@ -2931,10 +2930,36 @@ public class PackageInstaller { installFlags |= PackageManager.INSTALL_ENABLE_ROLLBACK; } else { installFlags &= ~PackageManager.INSTALL_ENABLE_ROLLBACK; + rollbackLifetimeMillis = 0; } rollbackDataPolicy = dataPolicy; } + /** + * If rollback enabled for this session (via {@link #setEnableRollback}, set time + * after which rollback will no longer be possible + * + * <p>For multi-package installs, this value must be set on the parent session. + * Child session rollback lifetime will be ignored. + * + * @param lifetimeMillis time after which rollback expires + * @throws IllegalArgumentException if lifetimeMillis is negative or rollback is not + * enabled via setEnableRollback. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) + @FlaggedApi(Flags.FLAG_ROLLBACK_LIFETIME) + public void setRollbackLifetimeMillis(@DurationMillisLong long lifetimeMillis) { + if (lifetimeMillis < 0) { + throw new IllegalArgumentException("rollbackLifetimeMillis can't be negative."); + } + if ((installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) == 0) { + throw new IllegalArgumentException( + "Can't set rollbackLifetimeMillis when rollback is not enabled"); + } + rollbackLifetimeMillis = lifetimeMillis; + } /** * @deprecated use {@link #setRequestDowngrade(boolean)}. @@ -3295,6 +3320,7 @@ public class PackageInstaller { pw.printPair("requiredInstalledVersionCode", requiredInstalledVersionCode); pw.printPair("dataLoaderParams", dataLoaderParams); pw.printPair("rollbackDataPolicy", rollbackDataPolicy); + pw.printPair("rollbackLifetimeMillis", rollbackLifetimeMillis); pw.printPair("applicationEnabledSettingPersistent", applicationEnabledSettingPersistent); pw.printHexPair("developmentInstallFlags", developmentInstallFlags); @@ -3336,6 +3362,7 @@ public class PackageInstaller { dest.writeParcelable(null, flags); } dest.writeInt(rollbackDataPolicy); + dest.writeLong(rollbackLifetimeMillis); dest.writeInt(requireUserAction); dest.writeInt(packageSource); dest.writeBoolean(applicationEnabledSettingPersistent); @@ -3529,6 +3556,9 @@ public class PackageInstaller { /** {@hide} */ public int rollbackDataPolicy; + /** @hide */ + public long rollbackLifetimeMillis; + /** {@hide} */ public int requireUserAction; @@ -3596,6 +3626,7 @@ public class PackageInstaller { isCommitted = source.readBoolean(); isPreapprovalRequested = source.readBoolean(); rollbackDataPolicy = source.readInt(); + rollbackLifetimeMillis = source.readLong(); createdMillis = source.readLong(); requireUserAction = source.readInt(); installerUid = source.readInt(); @@ -4220,6 +4251,7 @@ public class PackageInstaller { dest.writeBoolean(isCommitted); dest.writeBoolean(isPreapprovalRequested); dest.writeInt(rollbackDataPolicy); + dest.writeLong(rollbackLifetimeMillis); dest.writeLong(createdMillis); dest.writeInt(requireUserAction); dest.writeInt(installerUid); diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig index 96609ad241bc..9ad66ce1155a 100644 --- a/core/java/android/content/pm/flags.aconfig +++ b/core/java/android/content/pm/flags.aconfig @@ -58,3 +58,11 @@ flag { bug: "295827951" is_fixed_read_only: true } + +flag { + name: "rollback_lifetime" + namespace: "package_manager_service" + description: "Feature flag to enable custom rollback lifetime during install." + bug: "299670324" + is_fixed_read_only: true +}
\ No newline at end of file diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig index 3ec239c7125e..43cf97fd2090 100644 --- a/core/java/android/content/pm/multiuser.aconfig +++ b/core/java/android/content/pm/multiuser.aconfig @@ -28,3 +28,10 @@ flag { description: "Framework support for communal profile." bug: "285426179" } + +flag { + name: "support_communal_profile_nextgen" + namespace: "multiuser" + description: "Further framework support for communal profile, beyond the basics, for later releases." + bug: "285426179" +} diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java index a40fb154c256..66e3c28c1951 100644 --- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java +++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java @@ -16,10 +16,12 @@ package android.net.vcn; import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_MOBIKE; +import static android.net.vcn.Flags.FLAG_SAFE_MODE_CONFIG; import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED; import static com.android.internal.annotations.VisibleForTesting.Visibility; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; @@ -235,6 +237,9 @@ public final class VcnGatewayConnectionConfig { "mMinUdpPort4500NatTimeoutSeconds"; private final int mMinUdpPort4500NatTimeoutSeconds; + private static final String IS_SAFE_MODE_DISABLED_KEY = "mIsSafeModeDisabled"; + private final boolean mIsSafeModeDisabled; + private static final String GATEWAY_OPTIONS_KEY = "mGatewayOptions"; @NonNull private final Set<Integer> mGatewayOptions; @@ -247,6 +252,7 @@ public final class VcnGatewayConnectionConfig { @NonNull long[] retryIntervalsMs, @IntRange(from = MIN_MTU_V6) int maxMtu, @NonNull int minUdpPort4500NatTimeoutSeconds, + boolean isSafeModeDisabled, @NonNull Set<Integer> gatewayOptions) { mGatewayConnectionName = gatewayConnectionName; mTunnelConnectionParams = tunnelConnectionParams; @@ -255,6 +261,7 @@ public final class VcnGatewayConnectionConfig { mMaxMtu = maxMtu; mMinUdpPort4500NatTimeoutSeconds = minUdpPort4500NatTimeoutSeconds; mGatewayOptions = Collections.unmodifiableSet(new ArraySet(gatewayOptions)); + mIsSafeModeDisabled = isSafeModeDisabled; mUnderlyingNetworkTemplates = new ArrayList<>(underlyingNetworkTemplates); if (mUnderlyingNetworkTemplates.isEmpty()) { @@ -317,6 +324,7 @@ public final class VcnGatewayConnectionConfig { in.getInt( MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS_KEY, MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET); + mIsSafeModeDisabled = in.getBoolean(IS_SAFE_MODE_DISABLED_KEY); validate(); } @@ -483,6 +491,17 @@ public final class VcnGatewayConnectionConfig { } /** + * Check whether safe mode is enabled + * + * @see Builder#enableSafeMode(boolean) + * @hide + */ + @FlaggedApi(FLAG_SAFE_MODE_CONFIG) + public boolean isSafeModeEnabled() { + return !mIsSafeModeDisabled; + } + + /** * Checks if the given VCN gateway option is enabled. * * @param option the option to check. @@ -528,6 +547,7 @@ public final class VcnGatewayConnectionConfig { result.putLongArray(RETRY_INTERVAL_MS_KEY, mRetryIntervalsMs); result.putInt(MAX_MTU_KEY, mMaxMtu); result.putInt(MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS_KEY, mMinUdpPort4500NatTimeoutSeconds); + result.putBoolean(IS_SAFE_MODE_DISABLED_KEY, mIsSafeModeDisabled); return result; } @@ -542,6 +562,7 @@ public final class VcnGatewayConnectionConfig { Arrays.hashCode(mRetryIntervalsMs), mMaxMtu, mMinUdpPort4500NatTimeoutSeconds, + mIsSafeModeDisabled, mGatewayOptions); } @@ -559,6 +580,7 @@ public final class VcnGatewayConnectionConfig { && Arrays.equals(mRetryIntervalsMs, rhs.mRetryIntervalsMs) && mMaxMtu == rhs.mMaxMtu && mMinUdpPort4500NatTimeoutSeconds == rhs.mMinUdpPort4500NatTimeoutSeconds + && mIsSafeModeDisabled == rhs.mIsSafeModeDisabled && mGatewayOptions.equals(rhs.mGatewayOptions); } @@ -577,6 +599,7 @@ public final class VcnGatewayConnectionConfig { @NonNull private long[] mRetryIntervalsMs = DEFAULT_RETRY_INTERVALS_MS; private int mMaxMtu = DEFAULT_MAX_MTU; private int mMinUdpPort4500NatTimeoutSeconds = MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET; + private boolean mIsSafeModeDisabled = false; @NonNull private final Set<Integer> mGatewayOptions = new ArraySet<>(); @@ -789,6 +812,28 @@ public final class VcnGatewayConnectionConfig { } /** + * Enable/disable safe mode + * + * <p>If a VCN fails to provide connectivity within a system-provided timeout, it will enter + * safe mode. In safe mode, the VCN Network will be torn down and the system will restore + * connectivity by allowing underlying cellular networks to be used as default. At the same + * time, VCN will continue to retry until it succeeds. + * + * <p>When safe mode is disabled and VCN connection fails to provide connectivity, end users + * might not have connectivity, and may not have access to carrier-owned underlying + * networks. + * + * @param enabled whether safe mode should be enabled. Defaults to {@code true} + * @hide + */ + @FlaggedApi(FLAG_SAFE_MODE_CONFIG) + @NonNull + public Builder enableSafeMode(boolean enabled) { + mIsSafeModeDisabled = !enabled; + return this; + } + + /** * Builds and validates the VcnGatewayConnectionConfig. * * @return an immutable VcnGatewayConnectionConfig instance @@ -803,6 +848,7 @@ public final class VcnGatewayConnectionConfig { mRetryIntervalsMs, mMaxMtu, mMinUdpPort4500NatTimeoutSeconds, + mIsSafeModeDisabled, mGatewayOptions); } } diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java index 58def6ef7961..960e84d671e7 100644 --- a/core/java/android/os/BugreportManager.java +++ b/core/java/android/os/BugreportManager.java @@ -26,6 +26,7 @@ import android.annotation.RequiresPermission; import android.annotation.SuppressAutoDoc; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.annotation.UserHandleAware; import android.annotation.WorkerThread; import android.app.ActivityManager; import android.content.Context; @@ -280,8 +281,8 @@ public final class BugreportManager { * * <p>{@link BugreportManager} takes ownership of {@code bugreportFd}. * - * <p>The caller may only request to retrieve a given bugreport once. Subsequent calls will fail - * with error code {@link BugreportCallback#BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE}. + * <p>The caller can reattempt to retrieve the bugreport multiple times if the user has not + * consented on previous attempts. * * @param bugreportFile the identifier for a bugreport that was previously generated for this * caller using {@code startBugreport}. @@ -294,6 +295,7 @@ public final class BugreportManager { @SystemApi @RequiresPermission(Manifest.permission.DUMP) @WorkerThread + @UserHandleAware public void retrieveBugreport( @NonNull String bugreportFile, @NonNull ParcelFileDescriptor bugreportFd, @@ -307,8 +309,10 @@ public final class BugreportManager { Preconditions.checkNotNull(callback); DumpstateListener dsListener = new DumpstateListener(executor, callback, false, false); mBinder.retrieveBugreport(Binder.getCallingUid(), mContext.getOpPackageName(), + mContext.getUserId(), bugreportFd.getFileDescriptor(), bugreportFile, + /* keepBugreportOnRetrieval = */ false, dsListener); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); diff --git a/core/java/android/os/BugreportParams.java b/core/java/android/os/BugreportParams.java index e8ad303c4208..8510084c309d 100644 --- a/core/java/android/os/BugreportParams.java +++ b/core/java/android/os/BugreportParams.java @@ -20,6 +20,7 @@ import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.SystemApi; import android.annotation.TestApi; +import android.app.admin.flags.Flags; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -128,6 +129,8 @@ public final class BugreportParams { * * @hide */ + @TestApi + @FlaggedApi(Flags.FLAG_ONBOARDING_BUGREPORT_V2_ENABLED) public static final int BUGREPORT_MODE_ONBOARDING = IDumpstate.BUGREPORT_MODE_ONBOARDING; /** @@ -145,7 +148,8 @@ public final class BugreportParams { @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, prefix = { "BUGREPORT_FLAG_" }, value = { BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA, - BUGREPORT_FLAG_DEFER_CONSENT + BUGREPORT_FLAG_DEFER_CONSENT, + BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL }) public @interface BugreportFlag {} @@ -165,4 +169,20 @@ public final class BugreportParams { * String, ParcelFileDescriptor, Executor, BugreportManager.BugreportCallback)}. */ public static final int BUGREPORT_FLAG_DEFER_CONSENT = IDumpstate.BUGREPORT_FLAG_DEFER_CONSENT; + + /** + * Flag for keeping a bugreport stored even after it has been retrieved via + * {@link BugreportManager#retrieveBugreport}. + * + * <p>This flag can only be used when {@link #BUGREPORT_FLAG_DEFER_CONSENT} is set. + * The bugreport may be retrieved multiple times using + * {@link BugreportManager#retrieveBugreport( + * String, ParcelFileDescriptor, Executor, BugreportManager.BugreportCallback)}. + * + * @hide + */ + @TestApi + @FlaggedApi(Flags.FLAG_ONBOARDING_BUGREPORT_V2_ENABLED) + public static final int BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL = + IDumpstate.BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL; } diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index 839c56f85b7e..330b992ef308 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -137,6 +137,7 @@ interface IUserManager { boolean isUserVisible(int userId); int[] getVisibleUsers(); int getMainDisplayIdAssignedToUser(); + boolean isForegroundUserAdmin(); boolean isUserNameSet(int userId); boolean hasRestrictedProfiles(int userId); boolean requestQuietModeEnabled(String callingPackage, boolean enableQuietMode, int userId, in IntentSender target, int flags); diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 72bc2113f93f..cc79ae307f60 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -2774,6 +2774,8 @@ public class UserManager { * Returns the designated "communal profile" of the device, or {@code null} if there is none. * @hide */ + @FlaggedApi(android.multiuser.Flags.FLAG_SUPPORT_COMMUNAL_PROFILE) + @TestApi @RequiresPermission(anyOf = { Manifest.permission.MANAGE_USERS, Manifest.permission.CREATE_USERS, @@ -2791,16 +2793,33 @@ public class UserManager { } /** + * Checks if the context user is running in a communal profile. + * + * A communal profile is a {@link #isProfile() profile}, but instead of being associated with a + * particular parent user, it is communal to the device. + * + * @return whether the context user is a communal profile. + */ + @FlaggedApi(android.multiuser.Flags.FLAG_SUPPORT_COMMUNAL_PROFILE) + @UserHandleAware( + requiresAnyOfPermissionsIfNotCallerProfileGroup = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.QUERY_USERS, + android.Manifest.permission.INTERACT_ACROSS_USERS}) + public boolean isCommunalProfile() { + return isCommunalProfile(mUserId); + } + + /** * Returns {@code true} if the given user is the designated "communal profile" of the device. * @hide */ @RequiresPermission(anyOf = { - Manifest.permission.MANAGE_USERS, - Manifest.permission.CREATE_USERS, - Manifest.permission.QUERY_USERS}) - public boolean isCommunalProfile(@UserIdInt int userId) { - final UserInfo user = getUserInfo(userId); - return user != null && user.isCommunalProfile(); + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.QUERY_USERS, + android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true) + private boolean isCommunalProfile(@UserIdInt int userId) { + return isUserTypeCommunalProfile(getProfileType(userId)); } /** @@ -2840,6 +2859,23 @@ public class UserManager { } /** + * Used to check if the user currently running in the <b>foreground</b> is an + * {@link #isAdminUser() admin} user. + * + * @return whether the foreground user is an admin user. + * @see #isAdminUser() + * @see #isUserForeground() + */ + @FlaggedApi(android.multiuser.Flags.FLAG_SUPPORT_COMMUNAL_PROFILE_NEXTGEN) + public boolean isForegroundUserAdmin() { + try { + return mService.isForegroundUserAdmin(); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** * Returns whether the context user is of the given user type. * * @param userType the name of the user's user type, e.g. @@ -2997,7 +3033,7 @@ public class UserManager { } /** - * Checks if the calling context user can have a restricted profile. + * Checks if the context user can have a restricted profile. * @return whether the context user can have a restricted profile. * @hide */ @@ -3101,11 +3137,11 @@ public class UserManager { } /** - * Checks if the calling context user is running in a profile. A profile is a user that + * Checks if the context user is running in a profile. A profile is a user that * typically has its own separate data but shares its UI with some parent user. For example, a * {@link #isManagedProfile() managed profile} is a type of profile. * - * @return whether the caller is in a profile. + * @return whether the context user is in a profile. */ @UserHandleAware( requiresAnyOfPermissionsIfNotCallerProfileGroup = { @@ -5247,7 +5283,7 @@ public class UserManager { /** * Returns the parent of the profile which this method is called from - * or null if called from a user that is not a profile. + * or null if it has no parent (e.g. if it is not a profile). * * @hide */ @@ -5269,7 +5305,7 @@ public class UserManager { * * @param user the handle of the user profile * - * @return the parent of the user or {@code null} if the user is not profile + * @return the parent of the user or {@code null} if the user has no parent * * @hide */ diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index be4693bc0377..4da02f902e60 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -11612,7 +11612,14 @@ public final class ViewRootImpl implements ViewParent, Log.d(mTag, "registerCallbacksForSync syncBuffer=" + syncBuffer); } - surfaceSyncGroup.addTransaction(mPendingTransaction); + final Transaction t; + if (mHasPendingTransactions) { + t = new Transaction(); + t.merge(mPendingTransaction); + } else { + t = null; + } + mAttachInfo.mThreadedRenderer.registerRtFrameCallback(new FrameDrawingCallback() { @Override public void onFrameDraw(long frame) { @@ -11625,6 +11632,9 @@ public final class ViewRootImpl implements ViewParent, "Received frameDrawingCallback syncResult=" + syncResult + " frameNum=" + frame + "."); } + if (t != null) { + mergeWithNextTransaction(t, frame); + } // If the syncResults are SYNC_LOST_SURFACE_REWARD_IF_FOUND or // SYNC_CONTEXT_IS_STOPPED it means nothing will draw. There's no need to set up diff --git a/core/java/android/window/TaskFragmentParentInfo.java b/core/java/android/window/TaskFragmentParentInfo.java index 841354a92192..e6eeca4b4801 100644 --- a/core/java/android/window/TaskFragmentParentInfo.java +++ b/core/java/android/window/TaskFragmentParentInfo.java @@ -35,17 +35,21 @@ public class TaskFragmentParentInfo implements Parcelable { private final boolean mVisible; + private final boolean mHasDirectActivity; + public TaskFragmentParentInfo(@NonNull Configuration configuration, int displayId, - boolean visible) { + boolean visible, boolean hasDirectActivity) { mConfiguration.setTo(configuration); mDisplayId = displayId; mVisible = visible; + mHasDirectActivity = hasDirectActivity; } public TaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) { mConfiguration.setTo(info.getConfiguration()); mDisplayId = info.mDisplayId; mVisible = info.mVisible; + mHasDirectActivity = info.mHasDirectActivity; } /** The {@link Configuration} of the parent Task */ @@ -68,6 +72,14 @@ public class TaskFragmentParentInfo implements Parcelable { } /** + * Whether the parent Task has any direct child activity, which is not embedded in any + * TaskFragment, or not + */ + public boolean hasDirectActivity() { + return mHasDirectActivity; + } + + /** * Returns {@code true} if the parameters which are important for task fragment * organizers are equal between this {@link TaskFragmentParentInfo} and {@code that}. * Note that this method is usually called with @@ -80,7 +92,7 @@ public class TaskFragmentParentInfo implements Parcelable { return false; } return getWindowingMode() == that.getWindowingMode() && mDisplayId == that.mDisplayId - && mVisible == that.mVisible; + && mVisible == that.mVisible && mHasDirectActivity == that.mHasDirectActivity; } @WindowConfiguration.WindowingMode @@ -94,6 +106,7 @@ public class TaskFragmentParentInfo implements Parcelable { + "config=" + mConfiguration + ", displayId=" + mDisplayId + ", visible=" + mVisible + + ", hasDirectActivity=" + mHasDirectActivity + "}"; } @@ -114,7 +127,8 @@ public class TaskFragmentParentInfo implements Parcelable { final TaskFragmentParentInfo that = (TaskFragmentParentInfo) obj; return mConfiguration.equals(that.mConfiguration) && mDisplayId == that.mDisplayId - && mVisible == that.mVisible; + && mVisible == that.mVisible + && mHasDirectActivity == that.mHasDirectActivity; } @Override @@ -122,6 +136,7 @@ public class TaskFragmentParentInfo implements Parcelable { int result = mConfiguration.hashCode(); result = 31 * result + mDisplayId; result = 31 * result + (mVisible ? 1 : 0); + result = 31 * result + (mHasDirectActivity ? 1 : 0); return result; } @@ -130,12 +145,14 @@ public class TaskFragmentParentInfo implements Parcelable { mConfiguration.writeToParcel(dest, flags); dest.writeInt(mDisplayId); dest.writeBoolean(mVisible); + dest.writeBoolean(mHasDirectActivity); } private TaskFragmentParentInfo(Parcel in) { mConfiguration.readFromParcel(in); mDisplayId = in.readInt(); mVisible = in.readBoolean(); + mHasDirectActivity = in.readBoolean(); } public static final Creator<TaskFragmentParentInfo> CREATOR = diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl index 584dd806361c..492e2ac7cc28 100644 --- a/core/java/com/android/internal/app/IAppOpsService.aidl +++ b/core/java/com/android/internal/app/IAppOpsService.aidl @@ -20,6 +20,7 @@ import android.app.AppOpsManager; import android.app.AsyncNotedAppOp; import android.app.SyncNotedAppOp; import android.app.RuntimeAppOpAccessMessage; +import android.content.AttributionSource; import android.content.AttributionSourceState; import android.content.pm.ParceledListSlice; import android.os.Bundle; @@ -32,10 +33,17 @@ import com.android.internal.app.IAppOpsNotedCallback; import com.android.internal.app.IAppOpsStartedCallback; import com.android.internal.app.MessageSamplingConfig; +// AppOpsService AIDL interface. +// PLEASE READ BEFORE MODIFYING THIS FILE. +// Some methods in this interface or their transaction codes are mentioned in +// frameworks/base/boot/hiddenapi/hiddenapi-unsupported.txt, meaning that we cannot change their +// signature or ordering as they may be used by 3p apps. +// Also, some methods are mentioned in native code, meaning that the numbering in +// frameworks/native/libs/permission/include/binder/IAppOpsService.h must match the order here. +// Please be careful to respect both these issues when modifying this file. interface IAppOpsService { - // These methods are also called by native code, so must - // be kept in sync with frameworks/native/libs/permission/include/binder/IAppOpsService.h - // and not be reordered + // These methods are also called by native code, so please be careful that the number in + // frameworks/native/libs/permission/include/binder/IAppOpsService.h matches the ordering here. int checkOperation(int code, int uid, String packageName); SyncNotedAppOp noteOperation(int code, int uid, String packageName, @nullable String attributionTag, boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage); @@ -54,21 +62,21 @@ interface IAppOpsService { void setCameraAudioRestriction(int mode); void startWatchingModeWithFlags(int op, String packageName, int flags, IAppOpsCallback callback); - // End of methods also called by native code. - // Any new method exposed to native must be added after the last one, do not reorder - - SyncNotedAppOp noteProxyOperation(int code, in AttributionSourceState attributionSourceState, + // End of methods also called by native code (there may be more blocks like this of native + // methods later in this file). + // Deprecated, use noteProxyOperationWithState instead. + SyncNotedAppOp noteProxyOperation(int code, in AttributionSource attributionSource, boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage, boolean skipProxyOperation); + // Deprecated, use startProxyOperationWithState instead. SyncNotedAppOp startProxyOperation(IBinder clientId, int code, - in AttributionSourceState attributionSourceState, boolean startIfModeDefault, + in AttributionSource attributionSource, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage, boolean skipProxyOperation, int proxyAttributionFlags, int proxiedAttributionFlags, int attributionChainId); - void finishProxyOperation(IBinder clientId, int code, - in AttributionSourceState attributionSourceState, boolean skipProxyOperation); - - // Remaining methods are only used in Java. + // Deprecated, use finishProxyOperationWithState instead. + void finishProxyOperation(IBinder clientId, int code, in AttributionSource attributionSource, + boolean skipProxyOperation); int checkPackage(int uid, String packageName); RuntimeAppOpAccessMessage collectRuntimeAppOpAccessMessage(); MessageSamplingConfig reportRuntimeAppOpAccessMessageAndGetConfig(String packageName, @@ -127,8 +135,19 @@ interface IAppOpsService { List<AsyncNotedAppOp> extractAsyncOps(String packageName); int checkOperationRaw(int code, int uid, String packageName, @nullable String attributionTag); - void reloadNonHistoricalState(); void collectNoteOpCallsForValidation(String stackTrace, int op, String packageName, long version); + + SyncNotedAppOp noteProxyOperationWithState(int code, + in AttributionSourceState attributionSourceStateState, + boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage, + boolean skipProxyOperation); + SyncNotedAppOp startProxyOperationWithState(IBinder clientId, int code, + in AttributionSourceState attributionSourceStateState, boolean startIfModeDefault, + boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage, + boolean skipProxyOperation, int proxyAttributionFlags, int proxiedAttributionFlags, + int attributionChainId); + void finishProxyOperationWithState(IBinder clientId, int code, + in AttributionSourceState attributionSourceStateState, boolean skipProxyOperation); } diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index 965277c4635e..1c5f4f0f1369 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -868,6 +868,11 @@ public final class Zygote { args.mPkgDataInfoList, args.mAllowlistedDataInfoList, args.mBindMountAppDataDirs, args.mBindMountAppStorageDirs); + // While `specializeAppProcess` sets the thread name on the process's main thread, this + // is distinct from the app process name which appears in stack traces, as the latter is + // sourced from the argument buffer of the Process class. Set the app process name here. + Zygote.setAppProcessName(args, TAG); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); return ZygoteInit.zygoteInit(args.mTargetSdkVersion, diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java index 993e4e7b4b3d..5fe086da8c6a 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -296,7 +296,6 @@ class ZygoteConnection { } else { // child; result is a Runnable. zygoteServer.setForkChild(); - Zygote.setAppProcessName(parsedArgs, TAG); // ??? Necessary? return result; } } diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 8d11672144b2..a3e0016f9174 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -1105,10 +1105,9 @@ public class LockPatternUtils { @UnsupportedAppUsage public long setLockoutAttemptDeadline(int userId, int timeoutMs) { final long deadline = SystemClock.elapsedRealtime() + timeoutMs; - if (isSpecialUserId(userId)) { - // For secure password storage (that is required for special users such as FRP), the - // underlying storage also enforces the deadline. Since we cannot store settings - // for special users, don't. + if (userId == USER_FRP) { + // For secure password storage (that is required for FRP), the underlying storage also + // enforces the deadline. Since we cannot store settings for the FRP user, don't. return deadline; } mLockoutDeadlines.put(userId, deadline); diff --git a/core/jni/android_view_VelocityTracker.cpp b/core/jni/android_view_VelocityTracker.cpp index 03e9a6a9d139..1b24efa163db 100644 --- a/core/jni/android_view_VelocityTracker.cpp +++ b/core/jni/android_view_VelocityTracker.cpp @@ -39,7 +39,7 @@ public: explicit VelocityTrackerState(const VelocityTracker::Strategy strategy); void clear(); - void addMovement(const MotionEvent* event); + void addMovement(const MotionEvent& event); // TODO(b/32830165): consider supporting an overload that supports computing velocity only for // a subset of the supported axes. void computeCurrentVelocity(int32_t units, float maxVelocity); @@ -57,7 +57,7 @@ void VelocityTrackerState::clear() { mVelocityTracker.clear(); } -void VelocityTrackerState::addMovement(const MotionEvent* event) { +void VelocityTrackerState::addMovement(const MotionEvent& event) { mVelocityTracker.addMovement(event); } @@ -102,13 +102,13 @@ static void android_view_VelocityTracker_nativeClear(JNIEnv* env, jclass clazz, static void android_view_VelocityTracker_nativeAddMovement(JNIEnv* env, jclass clazz, jlong ptr, jobject eventObj) { const MotionEvent* event = android_view_MotionEvent_getNativePtr(env, eventObj); - if (!event) { + if (event == nullptr) { LOG(WARNING) << "nativeAddMovement failed because MotionEvent was finalized."; return; } VelocityTrackerState* state = reinterpret_cast<VelocityTrackerState*>(ptr); - state->addMovement(event); + state->addMovement(*event); } static void android_view_VelocityTracker_nativeComputeCurrentVelocity(JNIEnv* env, jclass clazz, diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index a6830a6e3793..c0c6e05efe87 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -135,11 +135,12 @@ <drawable name="notification_template_icon_low_bg">#0cffffff</drawable> <drawable name="notification_template_divider">#29000000</drawable> <drawable name="notification_template_divider_media">#29ffffff</drawable> - <color name="notification_primary_text_color_light">@color/primary_text_default_material_light</color> - <color name="notification_primary_text_color_dark">@color/primary_text_default_material_dark</color> - <color name="notification_secondary_text_color_light">@color/primary_text_default_material_light</color> + <color name="notification_primary_text_color_light">@color/system_on_surface_light</color> + <color name="notification_primary_text_color_dark">@color/system_on_surface_dark</color> + <!-- Note that the primary and secondary notification text colors are, in fact, the same. --> + <color name="notification_secondary_text_color_light">@color/system_on_surface_light</color> + <color name="notification_secondary_text_color_dark">@color/system_on_surface_dark</color> <item name="notification_secondary_text_disabled_alpha" format="float" type="dimen">0.38</item> - <color name="notification_secondary_text_color_dark">@color/primary_text_default_material_dark</color> <color name="notification_default_color_dark">#ddffffff</color> <color name="notification_default_color_light">#a3202124</color> diff --git a/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java index 72969f7d8b96..37a499adf682 100644 --- a/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java +++ b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java @@ -104,19 +104,15 @@ public class BugreportManagerTest { private static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT"; private static final Path[] UI_TRACES_PREDUMPED = { + Paths.get("/data/misc/perfetto-traces/bugreport/systrace.pftrace"), Paths.get("/data/misc/wmtrace/ime_trace_clients.winscope"), Paths.get("/data/misc/wmtrace/ime_trace_managerservice.winscope"), Paths.get("/data/misc/wmtrace/ime_trace_service.winscope"), Paths.get("/data/misc/wmtrace/wm_trace.winscope"), Paths.get("/data/misc/wmtrace/wm_log.winscope"), - Paths.get("/data/misc/wmtrace/layers_trace.winscope"), - Paths.get("/data/misc/wmtrace/transactions_trace.winscope"), - Paths.get("/data/misc/wmtrace/transition_trace.winscope"), + Paths.get("/data/misc/wmtrace/wm_transition_trace.winscope"), Paths.get("/data/misc/wmtrace/shell_transition_trace.winscope"), }; - private static final Path[] UI_TRACES_GENERATED_DURING_BUGREPORT = { - Paths.get("/data/misc/wmtrace/layers_trace_from_transactions.winscope"), - }; private Handler mHandler; private Executor mExecutor; @@ -210,7 +206,7 @@ public class BugreportManagerTest { mBrm.preDumpUiData(); waitTillDumpstateExitedOrTimeout(); - List<File> expectedPreDumpedTraceFiles = copyFilesAsRoot(UI_TRACES_PREDUMPED); + List<File> expectedPreDumpedTraceFiles = copyFiles(UI_TRACES_PREDUMPED); BugreportCallbackImpl callback = new BugreportCallbackImpl(); mBrm.startBugreport(mBugreportFd, null, fullWithUsePreDumpFlag(), mExecutor, @@ -225,7 +221,6 @@ public class BugreportManagerTest { assertFdsAreClosed(mBugreportFd); assertThatBugreportContainsFiles(UI_TRACES_PREDUMPED); - assertThatBugreportContainsFiles(UI_TRACES_GENERATED_DURING_BUGREPORT); List<File> actualPreDumpedTraceFiles = extractFilesFromBugreport(UI_TRACES_PREDUMPED); assertThatAllFileContentsAreEqual(actualPreDumpedTraceFiles, expectedPreDumpedTraceFiles); @@ -240,9 +235,9 @@ public class BugreportManagerTest { // In some corner cases, data dumped as part of the full bugreport could be the same as the // pre-dumped data and this test would fail. Hence, here we create fake/artificial // pre-dumped data that we know it won't match with the full bugreport data. - createFilesWithFakeDataAsRoot(UI_TRACES_PREDUMPED, "system"); + createFakeTraceFiles(UI_TRACES_PREDUMPED); - List<File> preDumpedTraceFiles = copyFilesAsRoot(UI_TRACES_PREDUMPED); + List<File> preDumpedTraceFiles = copyFiles(UI_TRACES_PREDUMPED); BugreportCallbackImpl callback = new BugreportCallbackImpl(); mBrm.startBugreport(mBugreportFd, null, full(), mExecutor, @@ -257,7 +252,6 @@ public class BugreportManagerTest { assertFdsAreClosed(mBugreportFd); assertThatBugreportContainsFiles(UI_TRACES_PREDUMPED); - assertThatBugreportContainsFiles(UI_TRACES_GENERATED_DURING_BUGREPORT); List<File> actualTraceFiles = extractFilesFromBugreport(UI_TRACES_PREDUMPED); assertThatAllFileContentsAreDifferent(preDumpedTraceFiles, actualTraceFiles); @@ -480,7 +474,32 @@ public class BugreportManagerTest { return f; } - private static void startPreDumpedUiTraces() { + private static void startPreDumpedUiTraces() throws Exception { + // Perfetto traces + String perfettoConfig = + "buffers: {\n" + + " size_kb: 2048\n" + + " fill_policy: RING_BUFFER\n" + + "}\n" + + "data_sources: {\n" + + " config {\n" + + " name: \"android.surfaceflinger.transactions\"\n" + + " }\n" + + "}\n" + + "bugreport_score: 10\n"; + File tmp = createTempFile("tmp", ".cfg"); + Files.write(perfettoConfig.getBytes(StandardCharsets.UTF_8), tmp); + InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand( + "install -m 644 -o root -g root " + + tmp.getAbsolutePath() + " /data/misc/perfetto-configs/bugreport-manager-test.cfg" + ); + InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand( + "perfetto --background-wait" + + " --config /data/misc/perfetto-configs/bugreport-manager-test.cfg --txt" + + " --out /data/misc/perfetto-traces/not-used.perfetto-trace" + ); + + // Legacy traces InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand( "cmd input_method tracing start" ); @@ -563,19 +582,24 @@ public class BugreportManagerTest { return extractedFile; } - private static void createFilesWithFakeDataAsRoot(Path[] paths, String owner) throws Exception { + private static void createFakeTraceFiles(Path[] paths) throws Exception { File src = createTempFile("fake", ".data"); Files.write("fake data".getBytes(StandardCharsets.UTF_8), src); for (Path path : paths) { InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand( - "install -m 611 -o " + owner + " -g " + owner - + " " + src.getAbsolutePath() + " " + path.toString() + "install -m 644 -o system -g system " + + src.getAbsolutePath() + " " + path.toString() ); } + + // Dumpstate executes "perfetto --save-for-bugreport" as shell + InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand( + "chown shell:shell /data/misc/perfetto-traces/bugreport/systrace.pftrace" + ); } - private static List<File> copyFilesAsRoot(Path[] paths) throws Exception { + private static List<File> copyFiles(Path[] paths) throws Exception { ArrayList<File> files = new ArrayList<File>(); for (Path src : paths) { File dst = createTempFile(src.getFileName().toString(), ".copy"); diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java index 9430ba6a939a..1577d9c1c1ab 100644 --- a/core/tests/coretests/src/android/app/NotificationTest.java +++ b/core/tests/coretests/src/android/app/NotificationTest.java @@ -859,6 +859,10 @@ public class NotificationTest { assertEquals(cDay.getTertiaryAccentColor(), cNight.getTertiaryAccentColor()); assertEquals(cDay.getOnTertiaryAccentTextColor(), cNight.getOnTertiaryAccentTextColor()); + assertEquals(cDay.getTertiaryFixedDimAccentColor(), + cNight.getTertiaryFixedDimAccentColor()); + assertEquals(cDay.getOnTertiaryFixedAccentTextColor(), + cNight.getOnTertiaryFixedAccentTextColor()); assertEquals(cDay.getProtectionColor(), cNight.getProtectionColor()); assertEquals(cDay.getContrastColor(), cNight.getContrastColor()); assertEquals(cDay.getRippleAlpha(), cNight.getRippleAlpha()); @@ -1832,6 +1836,8 @@ public class NotificationTest { assertThat(c.getSecondaryAccentColor()).isNotEqualTo(Notification.COLOR_INVALID); assertThat(c.getTertiaryAccentColor()).isNotEqualTo(Notification.COLOR_INVALID); assertThat(c.getOnTertiaryAccentTextColor()).isNotEqualTo(Notification.COLOR_INVALID); + assertThat(c.getTertiaryFixedDimAccentColor()).isNotEqualTo(Notification.COLOR_INVALID); + assertThat(c.getOnTertiaryFixedAccentTextColor()).isNotEqualTo(Notification.COLOR_INVALID); assertThat(c.getErrorColor()).isNotEqualTo(Notification.COLOR_INVALID); assertThat(c.getContrastColor()).isNotEqualTo(Notification.COLOR_INVALID); assertThat(c.getRippleAlpha()).isAtLeast(0x00); @@ -1847,9 +1853,12 @@ public class NotificationTest { // These colors are only used for emphasized buttons; they do not need contrast assertContrastIsAtLeast(c.getSecondaryAccentColor(), c.getBackgroundColor(), 1); assertContrastIsAtLeast(c.getTertiaryAccentColor(), c.getBackgroundColor(), 1); + assertContrastIsAtLeast(c.getTertiaryFixedDimAccentColor(), c.getBackgroundColor(), 1); // The text that is used within the accent color DOES need to have contrast assertContrastIsAtLeast(c.getOnTertiaryAccentTextColor(), c.getTertiaryAccentColor(), 4.5); + assertContrastIsAtLeast(c.getOnTertiaryFixedAccentTextColor(), + c.getTertiaryFixedDimAccentColor(), 4.5); } private void resolveColorsInNightMode(boolean nightMode, Notification.Colors c, int rawColor, diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java index 06b62f85bede..dd116b5b5c35 100644 --- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java @@ -33,6 +33,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.endsWith; +import static org.junit.Assume.assumeFalse; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; @@ -119,6 +120,11 @@ public class AccessibilityShortcutChooserActivityTest { @Before public void setUp() throws Exception { + final PackageManager pm = + InstrumentationRegistry.getInstrumentation().getContext().getPackageManager(); + assumeFalse("AccessibilityShortcutChooserActivity not supported on watch", + pm.hasSystemFeature(PackageManager.FEATURE_WATCH)); + mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); mDevice.wakeUp(); when(mAccessibilityServiceInfo.getResolveInfo()).thenReturn(mResolveInfo); @@ -138,7 +144,9 @@ public class AccessibilityShortcutChooserActivityTest { @After public void cleanUp() { - mScenario.close(); + if (mScenario != null) { + mScenario.close(); + } } @Test diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 49606f0bf485..7743ad55debb 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -1763,6 +1763,15 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return; } + if (container.isFinished()) { + return; + } + + if (container.isOverlay()) { + updateOverlayContainer(wct, container); + return; + } + if (launchPlaceholderIfNecessary(wct, container)) { // Placeholder was launched, the positions will be updated when the activity is added // to the secondary container. @@ -1783,6 +1792,25 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen updateSplitContainerIfNeeded(splitContainer, wct, null /* splitAttributes */); } + + @VisibleForTesting + // Suppress GuardedBy warning because lint ask to mark this method as + // @GuardedBy(mPresenter.mController.mLock), which is mLock itself + @SuppressWarnings("GuardedBy") + @GuardedBy("mLock") + void updateOverlayContainer(@NonNull WindowContainerTransaction wct, + @NonNull TaskFragmentContainer container) { + final TaskContainer taskContainer = container.getTaskContainer(); + // Dismiss the overlay container if it's the only container in the task and there's no + // direct activity in the parent task. + if (taskContainer.getTaskFragmentContainers().size() == 1 + && !taskContainer.hasDirectActivity()) { + container.finish(false /* shouldFinishDependent */, mPresenter, wct, this); + } + + // TODO(b/295805054): Add the logic to update overlay container + } + /** * Updates {@link SplitContainer} with the given {@link SplitAttributes} if the * {@link SplitContainer} is the top most and not finished. If passed {@link SplitAttributes} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java index 9e533808ccc0..eeb3ccf0d4cb 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java @@ -75,6 +75,8 @@ class TaskContainer { private boolean mIsVisible; + private boolean mHasDirectActivity; + /** * TaskFragments that the organizer has requested to be closed. They should be removed when * the organizer receives @@ -102,8 +104,9 @@ class TaskContainer { mConfiguration = taskProperties.getConfiguration(); mDisplayId = taskProperties.getDisplayId(); // Note that it is always called when there's a new Activity is started, which implies - // the host task is visible. + // the host task is visible and has an activity in the task. mIsVisible = true; + mHasDirectActivity = true; } int getTaskId() { @@ -118,6 +121,10 @@ class TaskContainer { return mIsVisible; } + boolean hasDirectActivity() { + return mHasDirectActivity; + } + @NonNull TaskProperties getTaskProperties() { return new TaskProperties(mDisplayId, mConfiguration); @@ -127,6 +134,7 @@ class TaskContainer { mConfiguration.setTo(info.getConfiguration()); mDisplayId = info.getDisplayId(); mIsVisible = info.isVisible(); + mHasDirectActivity = info.hasDirectActivity(); } /** diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java index 405f0b2f51dc..e74d5fb4d0be 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java @@ -58,6 +58,7 @@ import android.os.IBinder; import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; import android.window.TaskFragmentInfo; +import android.window.TaskFragmentParentInfo; import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; @@ -413,6 +414,33 @@ public class OverlayPresentationTest { .isEqualTo(overlayContainer); } + @Test + public void testUpdateContainer_dontInvokeUpdateOverlayForNonOverlayContainer() { + TaskFragmentContainer taskFragmentContainer = createMockTaskFragmentContainer(mActivity); + + mSplitController.updateContainer(mTransaction, taskFragmentContainer); + verify(mSplitController, never()).updateOverlayContainer(any(), any()); + } + + @Test + public void testUpdateOverlayContainer_dismissOverlayIfNeeded() { + TaskFragmentContainer overlayContainer = createTestOverlayContainer(TASK_ID, "test"); + + mSplitController.updateOverlayContainer(mTransaction, overlayContainer); + + final TaskContainer taskContainer = overlayContainer.getTaskContainer(); + assertThat(taskContainer.getTaskFragmentContainers()).containsExactly(overlayContainer); + + taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(Configuration.EMPTY, + DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */)); + + mSplitController.updateOverlayContainer(mTransaction, overlayContainer); + + assertWithMessage("The overlay must be dismissed since there's no activity" + + " in the task and other taskFragment.") + .that(taskContainer.getTaskFragmentContainers()).isEmpty(); + } + /** * A simplified version of {@link SplitController.ActivityStartMonitor * #createOrUpdateOverlayTaskFragmentIfNeeded} diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java index 96839c57d745..02031a67e7e3 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java @@ -1139,7 +1139,7 @@ public class SplitControllerTest { public void testOnTransactionReady_taskFragmentParentInfoChanged() { final TaskFragmentTransaction transaction = new TaskFragmentTransaction(); final TaskFragmentParentInfo parentInfo = new TaskFragmentParentInfo(Configuration.EMPTY, - DEFAULT_DISPLAY, true); + DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */); transaction.addChange(new TaskFragmentTransaction.Change( TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED) .setTaskId(TASK_ID) diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java index 21889960a8b2..e3f51697c284 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java @@ -79,14 +79,14 @@ public class TaskContainerTest { configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration, - DEFAULT_DISPLAY, true /* visible */)); + DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */)); assertEquals(WINDOWING_MODE_MULTI_WINDOW, taskContainer.getWindowingModeForSplitTaskFragment(splitBounds)); configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration, - DEFAULT_DISPLAY, true /* visible */)); + DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */)); assertEquals(WINDOWING_MODE_FREEFORM, taskContainer.getWindowingModeForSplitTaskFragment(splitBounds)); @@ -106,13 +106,13 @@ public class TaskContainerTest { configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration, - DEFAULT_DISPLAY, true /* visible */)); + DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */)); assertFalse(taskContainer.isInPictureInPicture()); configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_PINNED); taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration, - DEFAULT_DISPLAY, true /* visible */)); + DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */)); assertTrue(taskContainer.isInPictureInPicture()); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java index ca2c3b4fbff1..293b66084d28 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java @@ -146,13 +146,13 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { }); } }; + // If the remote is actually in the same process, then make a copy of parameters since + // remote impls assume that they have to clean-up native references. + final SurfaceControl.Transaction remoteStartT = + copyIfLocal(startTransaction, remote.getRemoteTransition()); + final TransitionInfo remoteInfo = + remoteStartT == startTransaction ? info : info.localRemoteCopy(); try { - // If the remote is actually in the same process, then make a copy of parameters since - // remote impls assume that they have to clean-up native references. - final SurfaceControl.Transaction remoteStartT = - copyIfLocal(startTransaction, remote.getRemoteTransition()); - final TransitionInfo remoteInfo = - remoteStartT == startTransaction ? info : info.localRemoteCopy(); handleDeath(remote.asBinder(), finishCallback); remote.getRemoteTransition().startAnimation(transition, remoteInfo, remoteStartT, cb); // assume that remote will apply the start transaction. @@ -160,6 +160,10 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { Transitions.setRunningRemoteTransitionDelegate(remote.getAppThread()); } catch (RemoteException e) { Log.e(Transitions.TAG, "Error running remote transition.", e); + if (remoteStartT != startTransaction) { + remoteStartT.close(); + } + startTransaction.apply(); unhandleDeath(remote.asBinder(), finishCallback); mRequestedRemotes.remove(transition); mMainExecutor.execute(() -> finishCallback.onTransitionFinished(null /* wct */)); diff --git a/location/java/android/location/GnssMeasurement.java b/location/java/android/location/GnssMeasurement.java index e28ad6757967..200d4ef86146 100644 --- a/location/java/android/location/GnssMeasurement.java +++ b/location/java/android/location/GnssMeasurement.java @@ -502,7 +502,7 @@ public final class GnssMeasurement implements Parcelable { * <td colspan="4"><strong>BDS</strong></td> * <td colspan="3"><strong>GAL</strong></td> * <td><strong>SBAS</strong></td> - * <td><strong>IRNSS</strong></td> + * <td><strong>NavIC</strong></td> * </tr> * <tr> * <td><strong>State Flag</strong></td> @@ -1528,17 +1528,17 @@ public final class GnssMeasurement implements Parcelable { * <p>Similar to the Attribute field described in RINEX 4.00, e.g., in Tables 9-16 (see * https://igs.org/wg/rinex/#documents-formats). * - * <p>Returns "A" for GALILEO E1A, GALILEO E6A, IRNSS L5A SPS, IRNSS SA SPS, GLONASS G1a L1OCd, + * <p>Returns "A" for GALILEO E1A, GALILEO E6A, NavIC L5A SPS, NavIC SA SPS, GLONASS G1a L1OCd, * GLONASS G2a L2CSI. * - * <p>Returns "B" for GALILEO E1B, GALILEO E6B, IRNSS L5B RS (D), IRNSS SB RS (D), GLONASS G1a + * <p>Returns "B" for GALILEO E1B, GALILEO E6B, NavIC L5B RS (D), NavIC SB RS (D), GLONASS G1a * L1OCp, GLONASS G2a L2OCp, QZSS L1Sb. * * <p>Returns "C" for GPS L1 C/A, GPS L2 C/A, GLONASS G1 C/A, GLONASS G2 C/A, GALILEO E1C, - * GALILEO E6C, SBAS L1 C/A, QZSS L1 C/A, IRNSS L5C RS (P), IRNSS SC RS (P). + * GALILEO E6C, SBAS L1 C/A, QZSS L1 C/A, NavIC L5C RS (P), NavIC SC RS (P). * * <p>Returns "D" for GPS L2 (L1(C/A) + (P2-P1) (semi-codeless)), QZSS L5S(I), BDS B1C Data, - * BDS B2a Data, BDS B2b Data, BDS B2 (B2a+B2b) Data, BDS B3a Data. + * BDS B2a Data, BDS B2b Data, BDS B2 (B2a+B2b) Data, BDS B3a Data, NavIC L1 Data. * * <p>Returns “E” for QZSS L1 C/B, QZSS L6E. * @@ -1553,7 +1553,7 @@ public final class GnssMeasurement implements Parcelable { * <p>Returns "N" for GPS L1 codeless, GPS L2 codeless. * * <p>Returns "P" for GPS L1P, GPS L2P, GLONASS G1P, GLONASS G2P, BDS B1C Pilot, BDS B2a Pilot, - * BDS B2b Pilot, BDS B2 (B2a+B2b) Pilot, BDS B3a Pilot, QZSS L5S(Q). + * BDS B2b Pilot, BDS B2 (B2a+B2b) Pilot, BDS B3a Pilot, QZSS L5S(Q), NavIC L1 Pilot. * * <p>Returns "Q" for GPS L5 Q, GLONASS G3 Q, GALILEO E5a Q, GALILEO E5b Q, GALILEO E5a+b Q, * SBAS L5 Q, QZSS L5 Q, BDS B1 Q, BDS B2 Q, BDS B3 Q. @@ -1567,7 +1567,8 @@ public final class GnssMeasurement implements Parcelable { * GLONASS G2a L2CSI+L2OCp, GLONASS G3 (I+Q), GALILEO E1 (B+C), GALILEO E5a (I+Q), GALILEO * E5b (I+Q), GALILEO E5a+b (I+Q), GALILEO E6 (B+C), SBAS L5 (I+Q), QZSS L1C (D+P), QZSS L2C * (M+L), QZSS L5 (I+Q), QZSS L6 (D+P), BDS B1 (I+Q), BDS B1C Data+Pilot, BDS B2a Data+Pilot, - * BDS B2 (I+Q), BDS B2 (B2a+B2b) Data+Pilot, BDS B3 (I+Q), IRNSS L5 (B+C), IRNSS S (B+C). + * BDS B2 (I+Q), BDS B2 (B2a+B2b) Data+Pilot, BDS B3 (I+Q), NavIC L5 (B+C), NavIC S (B+C), + * NavIC L1 Data+Pilot. * * <p>Returns "Y" for GPS L1Y, GPS L2Y. * diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig index c9cfa670cf02..386534b80925 100644 --- a/media/java/android/media/flags/media_better_together.aconfig +++ b/media/java/android/media/flags/media_better_together.aconfig @@ -34,3 +34,10 @@ flag { description: "Fallbacks to the default handling for volume adjustment when media session has fixed volume handling and its app is in the foreground and setting a media controller." bug: "293743975" } + +flag { + namespace: "media_solutions" + name: "enable_waiting_state_for_system_session_creation_request" + description: "Introduces a waiting state for the session creation request and prevents it from early failing when the selectedRoute from the bluetooth stack doesn't match the pending request route id." + bug: "307723189" +} diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl index 10c880d8ab06..24efbd14bc02 100644 --- a/media/java/android/media/projection/IMediaProjectionManager.aidl +++ b/media/java/android/media/projection/IMediaProjectionManager.aidl @@ -158,15 +158,12 @@ interface IMediaProjectionManager { in @nullable IMediaProjection projection); /** - * Notifies system server that we are handling a particular state during the consent flow. + * Notifies system server that the permission request was initiated. * * <p>Only used for emitting atoms. * * @param hostUid The uid of the process requesting consent to capture, may be an app or * SystemUI. - * @param state The state that SystemUI is handling during the consent flow. - * Must be a valid - * state defined in the MediaProjectionState enum. * @param sessionCreationSource Only set if the state is MEDIA_PROJECTION_STATE_INITIATED. * Indicates the entry point for requesting the permission. Must be * a valid state defined @@ -175,27 +172,23 @@ interface IMediaProjectionManager { @EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION") @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + ".permission.MANAGE_MEDIA_PROJECTION)") - oneway void notifyPermissionRequestStateChange(int hostUid, int state, int sessionCreationSource); + oneway void notifyPermissionRequestInitiated(int hostUid, int sessionCreationSource); /** - * Notifies system server that the permission request was initiated. + * Notifies system server that the permission request was displayed. * * <p>Only used for emitting atoms. * - * @param hostUid The uid of the process requesting consent to capture, may be an app or - * SystemUI. - * @param sessionCreationSource Only set if the state is MEDIA_PROJECTION_STATE_INITIATED. - * Indicates the entry point for requesting the permission. Must be - * a valid state defined - * in the SessionCreationSource enum. + * @param hostUid The uid of the process requesting consent to capture, may be an app or + * SystemUI. */ @EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION") @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + ".permission.MANAGE_MEDIA_PROJECTION)") - oneway void notifyPermissionRequestInitiated(int hostUid, int sessionCreationSource); + oneway void notifyPermissionRequestDisplayed(int hostUid); /** - * Notifies system server that the permission request was displayed. + * Notifies system server that the permission request was cancelled. * * <p>Only used for emitting atoms. * @@ -205,7 +198,7 @@ interface IMediaProjectionManager { @EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION") @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + ".permission.MANAGE_MEDIA_PROJECTION)") - oneway void notifyPermissionRequestDisplayed(int hostUid); + oneway void notifyPermissionRequestCancelled(int hostUid); /** * Notifies system server that the app selector was displayed. diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt index 477e61d2a7b1..f0fa6c5c4dd2 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -336,7 +336,10 @@ class GetFlowUtils { return result } - private fun parseCredentialEntryFromSlice(slice: Slice): CredentialEntry? { + /** + * @hide + */ + fun parseCredentialEntryFromSlice(slice: Slice): CredentialEntry? { try { when (slice.spec?.type) { TYPE_PASSWORD_CREDENTIAL -> return PasswordCredentialEntry.fromSlice(slice)!! diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt index d35dcb54c36d..81cbd5acaca6 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt @@ -23,6 +23,8 @@ import android.credentials.CredentialOption import android.credentials.GetCandidateCredentialsException import android.credentials.GetCandidateCredentialsResponse import android.credentials.GetCredentialRequest +import android.credentials.ui.GetCredentialProviderData +import android.graphics.drawable.Icon import android.os.Bundle import android.os.CancellationSignal import android.os.OutcomeReceiver @@ -42,6 +44,9 @@ import android.view.autofill.AutofillId import org.json.JSONException import android.widget.inline.InlinePresentationSpec import androidx.autofill.inline.v1.InlineSuggestionUi +import androidx.credentials.provider.CustomCredentialEntry +import androidx.credentials.provider.PasswordCredentialEntry +import androidx.credentials.provider.PublicKeyCredentialEntry import com.android.credentialmanager.GetFlowUtils import com.android.credentialmanager.getflow.CredentialEntryInfo import com.android.credentialmanager.getflow.ProviderDisplayInfo @@ -110,6 +115,34 @@ class CredentialAutofillService : AutofillService() { ) } + private fun getEntryToIconMap( + candidateProviderDataList: MutableList<GetCredentialProviderData> + ): Map<String, Icon> { + val entryIconMap: MutableMap<String, Icon> = mutableMapOf() + candidateProviderDataList.forEach { provider -> + provider.credentialEntries.forEach { entry -> + val credentialEntry = GetFlowUtils.parseCredentialEntryFromSlice(entry.slice) + when (credentialEntry) { + is PasswordCredentialEntry -> { + entryIconMap[entry.key + entry.subkey] = credentialEntry.icon + } + is PublicKeyCredentialEntry -> { + entryIconMap[entry.key + entry.subkey] = credentialEntry.icon + } + is CustomCredentialEntry -> { + entryIconMap[entry.key + entry.subkey] = credentialEntry.icon + } + } + } + } + return entryIconMap + } + + private fun getDefaultIcon(): Icon { + return Icon.createWithResource( + this, com.android.credentialmanager.R.drawable.ic_other_sign_in_24) + } + private fun convertToFillResponse( getCredResponse: GetCandidateCredentialsResponse, filLRequest: FillRequest @@ -120,6 +153,8 @@ class CredentialAutofillService : AutofillService() { if (providerList.isEmpty()) { return null } + val entryIconMap: Map<String, Icon> = + getEntryToIconMap(getCredResponse.candidateProviderDataList) var totalEntryCount = 0 providerList.forEach { provider -> totalEntryCount += provider.credentialEntryList.size @@ -174,6 +209,10 @@ class CredentialAutofillService : AutofillService() { val sliceBuilder = InlineSuggestionUi .newContentBuilder(pendingIntent) .setTitle(credentialEntry.userName) + val icon: Icon = + entryIconMap[credentialEntry.entryKey + credentialEntry.entrySubkey] + ?: getDefaultIcon() + sliceBuilder.setStartIcon(icon) inlinePresentation = InlinePresentation( sliceBuilder.build().slice, spec, /* pinned= */ false) } diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java index c9540c747650..8bdbee38ab85 100644 --- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java @@ -26,7 +26,6 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.PowerManager; -import android.os.UserHandle; import android.provider.Settings; import android.provider.Settings.Global; import android.provider.Settings.Secure; @@ -202,10 +201,10 @@ public class BatterySaverUtils { } private static void setBatterySaverConfirmationAcknowledged(Context context) { - Secure.putIntForUser(context.getContentResolver(), - Secure.LOW_POWER_WARNING_ACKNOWLEDGED, 1, UserHandle.USER_CURRENT); - Secure.putIntForUser(context.getContentResolver(), - Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, 1, UserHandle.USER_CURRENT); + Secure.putInt(context.getContentResolver(), + Secure.LOW_POWER_WARNING_ACKNOWLEDGED, 1); + Secure.putInt(context.getContentResolver(), + Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, 1); } /** diff --git a/packages/SystemUI/res/color/notification_guts_priority_button_bg_stroke.xml b/packages/SystemUI/res/color/notification_guts_priority_button_bg_stroke.xml index 015e9f99212d..d1b8a064724d 100644 --- a/packages/SystemUI/res/color/notification_guts_priority_button_bg_stroke.xml +++ b/packages/SystemUI/res/color/notification_guts_priority_button_bg_stroke.xml @@ -14,8 +14,9 @@ ~ limitations under the License --> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> +<selector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> <item android:state_selected="true" - android:color="?android:attr/colorAccent" /> + android:color="?androidprv:attr/materialColorOnSurfaceVariant" /> <item android:color="@color/notification_guts_priority_button_bg_stroke_color" /> </selector> diff --git a/packages/SystemUI/res/color/notification_guts_priority_contents.xml b/packages/SystemUI/res/color/notification_guts_priority_contents.xml index 42f01896d7a1..cc8c25a2d1ec 100644 --- a/packages/SystemUI/res/color/notification_guts_priority_contents.xml +++ b/packages/SystemUI/res/color/notification_guts_priority_contents.xml @@ -14,8 +14,9 @@ ~ limitations under the License. --> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> +<selector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> <item android:state_selected="true" - android:color="?android:attr/colorAccent" /> + android:color="?androidprv:attr/materialColorOnSurfaceVariant" /> <item android:color="@color/notification_guts_priority_button_content_color" /> </selector> diff --git a/packages/SystemUI/res/color/remote_input_hint.xml b/packages/SystemUI/res/color/remote_input_hint.xml index 7fe58dbcf822..0d90ee6b47c6 100644 --- a/packages/SystemUI/res/color/remote_input_hint.xml +++ b/packages/SystemUI/res/color/remote_input_hint.xml @@ -14,6 +14,7 @@ ~ limitations under the License. --> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:color="?android:attr/textColorPrimary" android:alpha=".6" /> +<selector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> + <item android:color="?androidprv:attr/materialColorOnSurfaceVariant" android:alpha=".6" /> </selector>
\ No newline at end of file diff --git a/packages/SystemUI/res/color/remote_input_send.xml b/packages/SystemUI/res/color/remote_input_send.xml index 4dcd3dd229fb..0acc66b9050f 100644 --- a/packages/SystemUI/res/color/remote_input_send.xml +++ b/packages/SystemUI/res/color/remote_input_send.xml @@ -15,7 +15,8 @@ ~ limitations under the License --> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_enabled="false" android:color="?android:attr/colorAccent" android:alpha=".3" /> - <item android:color="?android:attr/colorAccent" /> +<selector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> + <item android:state_enabled="false" android:color="?androidprv:attr/materialColorPrimary" android:alpha=".3" /> + <item android:color="?androidprv:attr/materialColorPrimary" /> </selector>
\ No newline at end of file diff --git a/packages/SystemUI/res/color/remote_input_text.xml b/packages/SystemUI/res/color/remote_input_text.xml index 13bb1d7cbe7d..bf2c198fe540 100644 --- a/packages/SystemUI/res/color/remote_input_text.xml +++ b/packages/SystemUI/res/color/remote_input_text.xml @@ -15,7 +15,8 @@ ~ limitations under the License --> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_enabled="false" android:color="?android:attr/textColorPrimary" android:alpha=".6" /> - <item android:color="?android:attr/textColorPrimary" /> +<selector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> + <item android:state_enabled="false" android:color="?androidprv:attr/materialColorOnSurfaceVariant" android:alpha=".6" /> + <item android:color="?androidprv:attr/materialColorOnSurfaceVariant" /> </selector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/notif_footer_btn_background.xml b/packages/SystemUI/res/drawable/notif_footer_btn_background.xml index e6266754c0af..b9597375c3df 100644 --- a/packages/SystemUI/res/drawable/notif_footer_btn_background.xml +++ b/packages/SystemUI/res/drawable/notif_footer_btn_background.xml @@ -26,7 +26,7 @@ <padding android:left="20dp" android:right="20dp" /> - <solid android:color="?androidprv:attr/colorSurface" /> + <solid android:color="?androidprv:attr/materialColorSurfaceContainerHigh" /> </shape> </inset> </item> diff --git a/packages/SystemUI/res/drawable/notification_guts_bg.xml b/packages/SystemUI/res/drawable/notification_guts_bg.xml index bd9394bf9f8a..84e2231738d4 100644 --- a/packages/SystemUI/res/drawable/notification_guts_bg.xml +++ b/packages/SystemUI/res/drawable/notification_guts_bg.xml @@ -17,7 +17,7 @@ <shape xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> - <solid android:color="?androidprv:attr/colorSurface" /> + <solid android:color="?androidprv:attr/materialColorSurfaceContainerHigh" /> <!--The radius is 1dp smaller than the notification one, to avoid aliasing bugs on the corners --> <corners android:radius="1dp" /> </shape> diff --git a/packages/SystemUI/res/drawable/notification_material_bg.xml b/packages/SystemUI/res/drawable/notification_material_bg.xml index 3eaa6180ba1b..355e75d0716b 100644 --- a/packages/SystemUI/res/drawable/notification_material_bg.xml +++ b/packages/SystemUI/res/drawable/notification_material_bg.xml @@ -20,7 +20,7 @@ android:color="?android:attr/colorControlHighlight"> <item> <shape> - <solid android:color="?androidprv:attr/colorSurface" /> + <solid android:color="?androidprv:attr/materialColorSurfaceContainerHigh" /> </shape> </item> <item> diff --git a/packages/SystemUI/res/drawable/remote_input_view_text_bg.xml b/packages/SystemUI/res/drawable/remote_input_view_text_bg.xml index 535b35497b5c..45d1a530cd20 100644 --- a/packages/SystemUI/res/drawable/remote_input_view_text_bg.xml +++ b/packages/SystemUI/res/drawable/remote_input_view_text_bg.xml @@ -14,12 +14,13 @@ ~ limitations under the License. --> <shape xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:shape="rectangle"> - <solid android:color="?android:attr/colorBackgroundFloating" /> + <solid android:color="?androidprv:attr/materialColorSurfaceDim" /> <stroke android:width="@dimen/remote_input_view_text_stroke" - android:color="?android:attr/colorAccent"/> + android:color="?androidprv:attr/materialColorPrimary"/> <padding android:bottom="0dp" android:left="12dp" diff --git a/packages/SystemUI/res/drawable/status_bar_notification_section_header_clear_btn.xml b/packages/SystemUI/res/drawable/status_bar_notification_section_header_clear_btn.xml index 8b3408048848..06bed001ae1a 100644 --- a/packages/SystemUI/res/drawable/status_bar_notification_section_header_clear_btn.xml +++ b/packages/SystemUI/res/drawable/status_bar_notification_section_header_clear_btn.xml @@ -15,11 +15,12 @@ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:width="40dp" android:height="40dp" android:viewportWidth="40" android:viewportHeight="40"> <path - android:fillColor="@color/notification_section_clear_all_btn_color" + android:fillColor="?androidprv:attr/materialColorOnSurfaceVariant" android:pathData="M24.6667 16.2733L23.7267 15.3333L20 19.06L16.2734 15.3333L15.3334 16.2733L19.06 20L15.3334 23.7267L16.2734 24.6667L20 20.94L23.7267 24.6667L24.6667 23.7267L20.94 20L24.6667 16.2733Z"/> </vector> diff --git a/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml b/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml index bddcb6abeebe..cf301c96a9f8 100644 --- a/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml +++ b/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml @@ -79,7 +79,4 @@ android:tint="@color/accent_tint_color_selector" android:soundEffectsEnabled="false" /> </LinearLayout> - - <include layout="@layout/volume_dnd_icon"/> - </FrameLayout> diff --git a/packages/SystemUI/res/layout-land/volume_dialog.xml b/packages/SystemUI/res/layout-land/volume_dialog.xml index 3b70dc060e84..5ce2601d407d 100644 --- a/packages/SystemUI/res/layout-land/volume_dialog.xml +++ b/packages/SystemUI/res/layout-land/volume_dialog.xml @@ -70,12 +70,6 @@ android:tint="?android:attr/textColorPrimary" android:layout_gravity="center" android:soundEffectsEnabled="false" /> - - <include layout="@layout/volume_dnd_icon" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginRight="@dimen/volume_dialog_stream_padding" - android:layout_marginTop="6dp"/> </FrameLayout> <LinearLayout diff --git a/packages/SystemUI/res/layout/bluetooth_device_item.xml b/packages/SystemUI/res/layout/bluetooth_device_item.xml index 6d779438ad6c..08eccbbe9669 100644 --- a/packages/SystemUI/res/layout/bluetooth_device_item.xml +++ b/packages/SystemUI/res/layout/bluetooth_device_item.xml @@ -44,10 +44,9 @@ android:paddingTop="10dp" android:maxLines="1" android:ellipsize="end" - app:layout_constraintWidth_percent="0.7" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toEndOf="@+id/bluetooth_device_icon" - app:layout_constraintEnd_toStartOf="@+id/gear_icon" + app:layout_constraintEnd_toStartOf="@+id/guideline" app:layout_constraintBottom_toTopOf="@+id/bluetooth_device_summary" android:gravity="center_vertical" android:textSize="14sp" /> @@ -60,34 +59,35 @@ android:paddingBottom="10dp" android:maxLines="1" android:ellipsize="end" - app:layout_constraintWidth_percent="0.7" app:layout_constraintTop_toBottomOf="@+id/bluetooth_device_name" app:layout_constraintStart_toEndOf="@+id/bluetooth_device_icon" - app:layout_constraintEnd_toStartOf="@+id/gear_icon" + app:layout_constraintEnd_toStartOf="@+id/guideline" app:layout_constraintBottom_toBottomOf="parent" android:gravity="center_vertical" /> + <androidx.constraintlayout.widget.Guideline + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/guideline" + app:layout_constraintGuide_percent="0.8" + android:orientation="vertical"/> + <View android:id="@+id/gear_icon" android:layout_width="0dp" android:layout_height="0dp" android:contentDescription="@string/accessibility_bluetooth_device_settings_gear" - app:layout_constraintStart_toEndOf="@+id/bluetooth_device_name" - app:layout_constraintEnd_toEndOf="@+id/gear_icon_image" + app:layout_constraintStart_toEndOf="@+id/guideline" + app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" /> <ImageView android:id="@+id/gear_icon_image" - android:src="@drawable/ic_settings_24dp" - android:contentDescription="@string/accessibility_bluetooth_device_settings_gear" android:layout_width="0dp" android:layout_height="24dp" - app:layout_constraintStart_toEndOf="@+id/bluetooth_device_name" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toTopOf="parent" + android:src="@drawable/ic_settings_24dp" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintWidth_percent="0.3" - android:gravity="center_vertical" - android:paddingStart="10dp" /> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml index 5d986e00deed..c11a18b795a1 100644 --- a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml +++ b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml @@ -51,147 +51,160 @@ android:textAppearance="@style/TextAppearance.Dialog.Body.Message" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintBottom_toTopOf="@id/bluetooth_toggle_title" + app:layout_constraintBottom_toTopOf="@+id/scroll_view" app:layout_constraintTop_toBottomOf="@id/bluetooth_tile_dialog_title" /> - <TextView - android:id="@+id/bluetooth_toggle_title" - style="@style/BluetoothTileDialog.Device" - android:layout_width="0dp" - android:layout_height="64dp" - android:maxLines="1" - android:ellipsize="end" - android:gravity="center_vertical" - android:layout_marginTop="4dp" - android:text="@string/turn_on_bluetooth" - android:clickable="false" - android:textAppearance="@style/TextAppearance.Dialog.Body.Message" - android:textSize="16sp" - app:layout_constraintEnd_toStartOf="@+id/bluetooth_toggle" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/bluetooth_tile_dialog_subtitle" /> - - <Switch - android:id="@+id/bluetooth_toggle" - style="@style/BluetoothTileDialog.Device" - android:layout_width="0dp" - android:layout_height="48dp" - android:gravity="center|center_vertical" - android:paddingEnd="24dp" - android:layout_marginTop="10dp" - android:contentDescription="@string/turn_on_bluetooth" - android:switchMinWidth="@dimen/settingslib_switch_track_width" - android:theme="@style/MainSwitch.Settingslib" - android:thumb="@drawable/settingslib_thumb_selector" - android:track="@drawable/settingslib_track_selector" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toEndOf="@+id/bluetooth_toggle_title" - app:layout_constraintTop_toBottomOf="@id/bluetooth_tile_dialog_subtitle" /> - - <androidx.recyclerview.widget.RecyclerView - android:id="@+id/device_list" + <androidx.core.widget.NestedScrollView + android:id="@+id/scroll_view" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="20dp" - android:nestedScrollingEnabled="false" - android:overScrollMode="never" - android:scrollbars="vertical" + app:layout_constrainedHeight="true" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toBottomOf="@id/bluetooth_toggle" - app:layout_constraintBottom_toTopOf="@+id/see_all_text" /> + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintTop_toBottomOf="@id/bluetooth_tile_dialog_subtitle"> - <androidx.constraintlayout.widget.Group - android:id="@+id/see_all_layout_group" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:visibility="gone" - app:constraint_referenced_ids="ic_arrow,see_all_text" /> + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/scroll_layout" + android:layout_width="match_parent" + android:layout_height="wrap_content" > - <ImageView - android:id="@+id/ic_arrow" - android:layout_marginStart="36dp" - android:layout_width="24dp" - android:layout_height="24dp" - android:importantForAccessibility="no" - android:gravity="center_vertical" - android:src="@drawable/ic_arrow_forward" - app:layout_constraintBottom_toTopOf="@+id/pair_new_device_text" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toStartOf="@id/see_all_text" - app:layout_constraintTop_toBottomOf="@id/device_list" /> + <TextView + android:id="@+id/bluetooth_toggle_title" + android:layout_width="0dp" + android:layout_height="64dp" + android:maxLines="1" + android:ellipsize="end" + android:gravity="start|center_vertical" + android:paddingEnd="0dp" + android:paddingStart="36dp" + android:text="@string/turn_on_bluetooth" + android:clickable="false" + android:textAppearance="@style/TextAppearance.Dialog.Body.Message" + android:textSize="16sp" + app:layout_constraintEnd_toStartOf="@+id/bluetooth_toggle" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintBottom_toTopOf="@+id/device_list" + app:layout_constraintTop_toTopOf="parent" /> - <TextView - android:id="@+id/see_all_text" - style="@style/BluetoothTileDialog.Device" - android:layout_width="0dp" - android:layout_height="64dp" - android:maxLines="1" - android:ellipsize="end" - android:gravity="center_vertical" - android:layout_marginStart="0dp" - android:paddingStart="20dp" - android:text="@string/see_all_bluetooth_devices" - android:textSize="14sp" - android:textAppearance="@style/TextAppearance.Dialog.Title" - app:layout_constraintBottom_toTopOf="@+id/pair_new_device_text" - app:layout_constraintStart_toEndOf="@+id/ic_arrow" - app:layout_constraintTop_toBottomOf="@id/device_list" - app:layout_constraintEnd_toEndOf="parent" /> + <Switch + android:id="@+id/bluetooth_toggle" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:gravity="start|center_vertical" + android:paddingEnd="40dp" + android:contentDescription="@string/turn_on_bluetooth" + android:switchMinWidth="@dimen/settingslib_switch_track_width" + android:theme="@style/MainSwitch.Settingslib" + android:thumb="@drawable/settingslib_thumb_selector" + android:track="@drawable/settingslib_track_selector" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@+id/bluetooth_toggle_title" + app:layout_constraintBottom_toTopOf="@+id/device_list" + app:layout_constraintTop_toTopOf="parent" /> - <androidx.constraintlayout.widget.Group - android:id="@+id/pair_new_device_layout_group" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:visibility="gone" - app:constraint_referenced_ids="ic_add,pair_new_device_text" /> + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/device_list" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="10dp" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@+id/bluetooth_toggle" + app:layout_constraintBottom_toTopOf="@+id/see_all_text" /> - <ImageView - android:id="@+id/ic_add" - android:layout_width="24dp" - android:layout_height="24dp" - android:layout_marginStart="36dp" - android:gravity="center_vertical" - android:importantForAccessibility="no" - android:src="@drawable/ic_add" - app:layout_constraintBottom_toTopOf="@id/done_button" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toStartOf="@id/pair_new_device_text" - app:layout_constraintTop_toBottomOf="@id/see_all_text" - android:tint="?android:attr/textColorPrimary" /> + <androidx.constraintlayout.widget.Group + android:id="@+id/see_all_layout_group" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + app:constraint_referenced_ids="ic_arrow,see_all_text" /> - <TextView - android:id="@+id/pair_new_device_text" - style="@style/BluetoothTileDialog.Device" - android:layout_width="0dp" - android:layout_height="64dp" - android:maxLines="1" - android:ellipsize="end" - android:gravity="center_vertical" - android:layout_marginStart="0dp" - android:paddingStart="20dp" - android:text="@string/pair_new_bluetooth_devices" - android:textSize="14sp" - android:textAppearance="@style/TextAppearance.Dialog.Title" - app:layout_constraintBottom_toTopOf="@id/done_button" - app:layout_constraintStart_toEndOf="@+id/ic_add" - app:layout_constraintTop_toBottomOf="@id/see_all_text" - app:layout_constraintEnd_toEndOf="parent" /> + <ImageView + android:id="@+id/ic_arrow" + android:layout_marginStart="36dp" + android:layout_width="24dp" + android:layout_height="24dp" + android:importantForAccessibility="no" + android:gravity="center_vertical" + android:src="@drawable/ic_arrow_forward" + app:layout_constraintBottom_toTopOf="@+id/pair_new_device_text" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toStartOf="@id/see_all_text" + app:layout_constraintTop_toBottomOf="@id/device_list" /> - <Button - android:id="@+id/done_button" - style="@style/Widget.Dialog.Button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/dialog_bottom_padding" - android:layout_marginEnd="@dimen/dialog_side_padding" - android:layout_marginStart="@dimen/dialog_side_padding" - android:clickable="true" - android:ellipsize="end" - android:focusable="true" - android:maxLines="1" - android:text="@string/inline_done_button" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toBottomOf="@id/pair_new_device_text" /> + <TextView + android:id="@+id/see_all_text" + style="@style/BluetoothTileDialog.Device" + android:layout_width="0dp" + android:layout_height="64dp" + android:maxLines="1" + android:ellipsize="end" + android:gravity="center_vertical" + android:layout_marginStart="0dp" + android:paddingStart="20dp" + android:text="@string/see_all_bluetooth_devices" + android:textSize="14sp" + android:textAppearance="@style/TextAppearance.Dialog.Title" + app:layout_constraintBottom_toTopOf="@+id/pair_new_device_text" + app:layout_constraintStart_toEndOf="@+id/ic_arrow" + app:layout_constraintTop_toBottomOf="@id/device_list" + app:layout_constraintEnd_toEndOf="parent" /> + + <androidx.constraintlayout.widget.Group + android:id="@+id/pair_new_device_layout_group" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + app:constraint_referenced_ids="ic_add,pair_new_device_text" /> + + <ImageView + android:id="@+id/ic_add" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_marginStart="36dp" + android:gravity="center_vertical" + android:importantForAccessibility="no" + android:src="@drawable/ic_add" + app:layout_constraintBottom_toTopOf="@id/done_button" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toStartOf="@id/pair_new_device_text" + app:layout_constraintTop_toBottomOf="@id/see_all_text" + android:tint="?android:attr/textColorPrimary" /> + + <TextView + android:id="@+id/pair_new_device_text" + style="@style/BluetoothTileDialog.Device" + android:layout_width="0dp" + android:layout_height="64dp" + android:maxLines="1" + android:ellipsize="end" + android:gravity="center_vertical" + android:layout_marginStart="0dp" + android:paddingStart="20dp" + android:text="@string/pair_new_bluetooth_devices" + android:textSize="14sp" + android:textAppearance="@style/TextAppearance.Dialog.Title" + app:layout_constraintStart_toEndOf="@+id/ic_add" + app:layout_constraintTop_toBottomOf="@id/see_all_text" + app:layout_constraintEnd_toEndOf="parent" /> + + <Button + android:id="@+id/done_button" + style="@style/Widget.Dialog.Button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/dialog_bottom_padding" + android:layout_marginEnd="@dimen/dialog_side_padding" + android:layout_marginStart="@dimen/dialog_side_padding" + android:clickable="true" + android:ellipsize="end" + android:focusable="true" + android:maxLines="1" + android:text="@string/inline_done_button" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@id/pair_new_device_text" + app:layout_constraintBottom_toBottomOf="parent" /> + </androidx.constraintlayout.widget.ConstraintLayout> + </androidx.core.widget.NestedScrollView> </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/notif_half_shelf.xml b/packages/SystemUI/res/layout/notif_half_shelf.xml index c70f8e2b1c07..68c8dd96d188 100644 --- a/packages/SystemUI/res/layout/notif_half_shelf.xml +++ b/packages/SystemUI/res/layout/notif_half_shelf.xml @@ -16,6 +16,7 @@ <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:id="@+id/half_shelf_dialog" android:orientation="vertical" android:layout_width="wrap_content" @@ -65,7 +66,7 @@ android:gravity="center_vertical|start" android:ellipsize="end" android:maxLines="2" - android:textColor="?android:attr/textColorPrimary" + android:textColor="?androidprv:attr/materialColorOnSurface" android:fontFamily="@*android:string/config_headlineFontFamilyMedium" android:textSize="16sp" /> diff --git a/packages/SystemUI/res/layout/notif_half_shelf_row.xml b/packages/SystemUI/res/layout/notif_half_shelf_row.xml index 190f9994b1dc..9ef342ce5220 100644 --- a/packages/SystemUI/res/layout/notif_half_shelf_row.xml +++ b/packages/SystemUI/res/layout/notif_half_shelf_row.xml @@ -16,6 +16,7 @@ <com.android.systemui.statusbar.notification.row.ChannelRow xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:id="@+id/half_shelf_row" android:layout_width="match_parent" android:layout_height="wrap_content" @@ -59,7 +60,7 @@ android:ellipsize="end" android:maxLines="1" android:fontFamily="@*android:string/config_headlineFontFamily" - android:textColor="?android:attr/textColorPrimary" + android:textColor="?androidprv:attr/materialColorOnSurface" android:textSize="16sp" /> @@ -74,7 +75,7 @@ android:maxLines="1" android:layout_below="@id/channel_name" android:fontFamily="@*android:string/config_bodyFontFamily" - android:textColor="?android:attr/textColorSecondary" + android:textColor="?androidprv:attr/materialColorOnSurfaceVariant" android:textSize="14sp" /> </RelativeLayout> diff --git a/packages/SystemUI/res/layout/notification_children_divider.xml b/packages/SystemUI/res/layout/notification_children_divider.xml index eb743067933d..13e24a9ea277 100644 --- a/packages/SystemUI/res/layout/notification_children_divider.xml +++ b/packages/SystemUI/res/layout/notification_children_divider.xml @@ -17,7 +17,8 @@ <com.android.systemui.statusbar.AlphaOptimizedView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:id="@+id/notification_more_divider" android:layout_width="match_parent" android:layout_height="@dimen/notification_divider_height" - android:background="@color/notification_divider_color" /> + android:background="?androidprv:attr/materialColorOnSurfaceVariant" /> diff --git a/packages/SystemUI/res/layout/notification_conversation_info.xml b/packages/SystemUI/res/layout/notification_conversation_info.xml index 4f6e88c2266f..3a752c81b95a 100644 --- a/packages/SystemUI/res/layout/notification_conversation_info.xml +++ b/packages/SystemUI/res/layout/notification_conversation_info.xml @@ -17,6 +17,7 @@ <com.android.systemui.statusbar.notification.row.NotificationConversationInfo xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:id="@+id/notification_guts" android:layout_width="match_parent" android:layout_height="wrap_content" @@ -173,7 +174,7 @@ android:contentDescription="@string/notification_more_settings" android:background="@drawable/ripple_drawable_20dp" android:src="@drawable/ic_settings" - android:tint="?android:attr/colorAccent" + android:tint="?androidprv:attr/materialColorPrimary" android:layout_alignParentEnd="true" /> </LinearLayout> diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml index 852db1b8fb91..19a3f2fd521c 100644 --- a/packages/SystemUI/res/layout/notification_info.xml +++ b/packages/SystemUI/res/layout/notification_info.xml @@ -17,6 +17,7 @@ <com.android.systemui.statusbar.notification.row.NotificationInfo xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:id="@+id/notification_guts" android:layout_width="match_parent" android:layout_height="wrap_content" @@ -102,7 +103,7 @@ asked for it --> android:contentDescription="@string/notification_app_settings" android:src="@drawable/ic_info" android:layout_toStartOf="@id/info" - android:tint="@color/notification_guts_link_icon_tint"/> + android:tint="?androidprv:attr/materialColorPrimary"/> <ImageButton android:id="@+id/info" android:layout_width="@dimen/notification_importance_toggle_size" @@ -111,7 +112,7 @@ asked for it --> android:contentDescription="@string/notification_more_settings" android:background="@drawable/ripple_drawable_20dp" android:src="@drawable/ic_settings" - android:tint="?android:attr/colorAccent" + android:tint="?androidprv:attr/materialColorPrimary" android:layout_alignParentEnd="true" /> </LinearLayout> diff --git a/packages/SystemUI/res/layout/notification_snooze.xml b/packages/SystemUI/res/layout/notification_snooze.xml index 8b5368098c00..6e541a7a5f32 100644 --- a/packages/SystemUI/res/layout/notification_snooze.xml +++ b/packages/SystemUI/res/layout/notification_snooze.xml @@ -23,7 +23,7 @@ android:orientation="vertical" android:paddingTop="2dp" android:paddingBottom="2dp" - android:background="?androidprv:attr/colorSurface" + android:background="?androidprv:attr/materialColorSurfaceContainerHigh" android:theme="@style/Theme.SystemUI"> <RelativeLayout @@ -38,7 +38,7 @@ android:layout_alignParentStart="true" android:layout_centerVertical="true" android:paddingStart="@*android:dimen/notification_content_margin_end" - android:textColor="?android:attr/textColorPrimary" + android:textColor="?androidprv:attr/materialColorOnSurface" android:paddingEnd="4dp"/> <ImageView diff --git a/packages/SystemUI/res/layout/notification_snooze_option.xml b/packages/SystemUI/res/layout/notification_snooze_option.xml index d42cc0212fd8..fa6f965198d4 100644 --- a/packages/SystemUI/res/layout/notification_snooze_option.xml +++ b/packages/SystemUI/res/layout/notification_snooze_option.xml @@ -16,10 +16,11 @@ --> <TextView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:layout_width="match_parent" android:layout_height="@*android:dimen/notification_headerless_min_height" android:layout_marginStart="@*android:dimen/notification_content_margin_end" android:layout_marginEnd="@*android:dimen/notification_content_margin_end" android:gravity="center_vertical" android:textSize="14sp" - android:textColor="?android:attr/textColorSecondary"/>
\ No newline at end of file + android:textColor="?androidprv:attr/materialColorOnSurfaceVariant"/>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/status_bar_notification_footer.xml b/packages/SystemUI/res/layout/status_bar_notification_footer.xml index c1bac3151049..72424a13d0f1 100644 --- a/packages/SystemUI/res/layout/status_bar_notification_footer.xml +++ b/packages/SystemUI/res/layout/status_bar_notification_footer.xml @@ -41,7 +41,7 @@ android:layout_height="wrap_content" > <com.android.systemui.statusbar.notification.row.FooterViewButton - style="@style/TextAppearance.NotificationSectionHeaderButton" + style="@style/TextAppearance.NotificationFooterButton" android:id="@+id/manage_text" android:layout_width="wrap_content" android:layout_height="48dp" @@ -54,12 +54,11 @@ app:layout_constraintEnd_toStartOf="@id/dismiss_text" android:background="@drawable/notif_footer_btn_background" android:focusable="true" - android:textColor="@color/notif_pill_text" android:contentDescription="@string/manage_notifications_history_text" android:text="@string/manage_notifications_history_text" /> <com.android.systemui.statusbar.notification.row.FooterViewButton - style="@style/TextAppearance.NotificationSectionHeaderButton" + style="@style/TextAppearance.NotificationFooterButton" android:id="@+id/dismiss_text" android:layout_width="wrap_content" android:layout_height="48dp" @@ -70,7 +69,6 @@ app:layout_constraintStart_toEndOf="@id/manage_text" android:background="@drawable/notif_footer_btn_background" android:focusable="true" - android:textColor="@color/notif_pill_text" android:contentDescription="@string/accessibility_clear_all" android:text="@string/clear_all_notifications_text" /> diff --git a/packages/SystemUI/res/layout/status_bar_notification_section_header.xml b/packages/SystemUI/res/layout/status_bar_notification_section_header.xml index c4d8d55f74e2..53abe87e7c12 100644 --- a/packages/SystemUI/res/layout/status_bar_notification_section_header.xml +++ b/packages/SystemUI/res/layout/status_bar_notification_section_header.xml @@ -40,7 +40,7 @@ android:layout_weight="1"> <TextView - style="@style/TextAppearance.NotificationSectionHeaderButton" + style="@style/TextAppearance.NotificationSectionHeaderLabel" android:id="@+id/header_label" android:layout_width="wrap_content" android:layout_height="wrap_content" diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml index 6a192d4b7e05..39a1f1f9b85d 100644 --- a/packages/SystemUI/res/layout/volume_dialog.xml +++ b/packages/SystemUI/res/layout/volume_dialog.xml @@ -69,12 +69,6 @@ android:tint="?android:attr/textColorPrimary" android:layout_gravity="center" android:soundEffectsEnabled="false" /> - - <include layout="@layout/volume_dnd_icon" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginRight="@dimen/volume_dialog_stream_padding" - android:layout_marginTop="6dp"/> </FrameLayout> <LinearLayout diff --git a/packages/SystemUI/res/layout/volume_dialog_row.xml b/packages/SystemUI/res/layout/volume_dialog_row.xml index c9256ae5123b..f35de0568327 100644 --- a/packages/SystemUI/res/layout/volume_dialog_row.xml +++ b/packages/SystemUI/res/layout/volume_dialog_row.xml @@ -62,7 +62,6 @@ android:background="@null" android:layoutDirection="ltr" android:rotation="270" /> - <include layout="@layout/volume_dnd_icon"/> </FrameLayout> <com.android.keyguard.AlphaOptimizedImageButton diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml index 99311e3d1e1b..d9385c7d24c4 100644 --- a/packages/SystemUI/res/values-night/colors.xml +++ b/packages/SystemUI/res/values-night/colors.xml @@ -33,7 +33,7 @@ <!-- The color of the text inside a notification --> <color name="notification_primary_text_color">@*android:color/notification_primary_text_color_dark</color> - <color name="notif_pill_text">@color/material_dynamic_neutral95</color> + <color name="notif_pill_text">@android:color/system_on_surface_dark</color> <color name="notification_guts_link_icon_tint">@color/GM2_grey_500</color> <color name="notification_guts_sub_text_color">@color/GM2_grey_300</color> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index e9422580ed54..0620355c2f98 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -115,7 +115,7 @@ <!-- Chosen so fill over background matches single tone --> <color name="dark_mode_qs_icon_color_dual_tone_fill">#99000000</color> - <color name="notif_pill_text">@color/material_dynamic_neutral10</color> + <color name="notif_pill_text">@android:color/system_on_surface_light</color> <!-- Keyboard shortcuts colors --> <color name="ksh_application_group_color">#fff44336</color> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 4c41ca4153da..4c5204447db0 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -466,7 +466,7 @@ <string name="accessibility_bluetooth_device_icon">Bluetooth device icon</string> <!-- Content description of the bluetooth device settings gear icon. [CHAR LIMIT=NONE] --> - <string name="accessibility_bluetooth_device_settings_gear">Bluetooth device settings gear</string> + <string name="accessibility_bluetooth_device_settings_gear">Click to configure device detail</string> <!-- Content description of the battery when battery state is unknown for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_battery_unknown">Battery percentage unknown.</string> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 084cb883d766..7ce530fce470 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -597,34 +597,34 @@ <style name="TextAppearance.NotificationImportanceChannel"> <item name="android:textSize">@dimen/notification_importance_channel_text</item> <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item> - <item name="android:textColor">@color/notification_guts_header_text_color</item> + <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item> <item name="android:textSize">@dimen/notification_importance_channel_text</item> </style> <style name="TextAppearance.NotificationImportanceChannelGroup"> <item name="android:textSize">@dimen/notification_importance_channel_group_text</item> <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> - <item name="android:textColor">@color/notification_guts_header_text_color</item> + <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item> <item name="android:textSize">@dimen/notification_importance_channel_group_text</item> </style> <style name="TextAppearance.NotificationImportanceApp"> <item name="android:textSize">@dimen/notification_importance_channel_group_text</item> <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> - <item name="android:textColor">@color/notification_guts_sub_text_color</item> + <item name="android:textColor">?androidprv:attr/materialColorOnSurfaceVariant</item> <item name="android:textSize">@dimen/notification_importance_channel_group_text</item> </style> <style name="TextAppearance.NotificationImportanceHeader"> <item name="android:textSize">@dimen/notification_importance_header_text</item> <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> - <item name="android:textColor">@color/notification_guts_header_text_color</item> + <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item> </style> <style name="TextAppearance.NotificationImportanceDetail"> <item name="android:textSize">@dimen/notification_importance_description_text</item> <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> - <item name="android:textColor">@color/notification_guts_sub_text_color</item> + <item name="android:textColor">?androidprv:attr/materialColorOnSurfaceVariant</item> <item name="android:gravity">center</item> </style> @@ -636,9 +636,27 @@ </style> <style + name="TextAppearance.NotificationSectionHeaderLabel" + parent="@android:style/Widget.DeviceDefault.Button.Borderless"> + <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item> + <item name="android:textAllCaps">false</item> + <item name="android:textSize">14sp</item> + <item name="android:minWidth">0dp</item> + </style> + + <style name="TextAppearance.NotificationSectionHeaderButton" parent="@android:style/Widget.DeviceDefault.Button.Borderless"> - <item name="android:textColor">?android:attr/textColorPrimary</item> + <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item> + <item name="android:textAllCaps">false</item> + <item name="android:textSize">14sp</item> + <item name="android:minWidth">0dp</item> + </style> + + <style + name="TextAppearance.NotificationFooterButton" + parent="@android:style/Widget.DeviceDefault.Button.Borderless"> + <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item> <item name="android:textAllCaps">false</item> <item name="android:textSize">14sp</item> <item name="android:minWidth">0dp</item> diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java index 584357bb739c..0bd4859eefd5 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java @@ -1461,8 +1461,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold mDragView.setColorFilter(filter); } - @VisibleForTesting - void setBounceEffectDuration(int duration) { + private void setBounceEffectDuration(int duration) { mBounceEffectDuration = duration; } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 06ec17ff80d1..f4a9f739d187 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -406,6 +406,10 @@ object Flags { // TODO(b/301610137): Tracking bug @JvmField val NEW_NETWORK_SLICE_UI = unreleasedFlag("new_network_slice_ui", teamfood = true) + // TODO(b/308138154): Tracking bug + val FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS = + releasedFlag("filter_provisioning_network_subscriptions") + // TODO(b/265892345): Tracking Bug val PLUG_IN_STATUS_BAR_CHIP = releasedFlag("plug_in_status_bar_chip") @@ -479,7 +483,7 @@ object Flags { val MEDIA_REMOTE_RESUME = unreleasedFlag("media_remote_resume") // TODO(b/304506662): Tracking Bug - val MEDIA_DEVICE_NAME_FIX = unreleasedFlag("media_device_name_fix", teamfood = true) + val MEDIA_DEVICE_NAME_FIX = releasedFlag("media_device_name_fix") // 1000 - dock val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag("simulate_dock_through_charging") @@ -618,7 +622,7 @@ object Flags { /** TODO(b/295143676): Tracking bug. When enable, captures a screenshot for each display. */ @JvmField - val MULTI_DISPLAY_SCREENSHOT = unreleasedFlag("multi_display_screenshot", teamfood = true) + val MULTI_DISPLAY_SCREENSHOT = releasedFlag("multi_display_screenshot") // 1400 - columbus // TODO(b/254512756): Tracking Bug @@ -722,7 +726,7 @@ object Flags { @JvmField val KEYBOARD_BACKLIGHT_INDICATOR = releasedFlag("keyboard_backlight_indicator") // TODO(b/277192623): Tracking Bug - @JvmField val KEYBOARD_EDUCATION = unreleasedFlag("keyboard_education", teamfood = true) + @JvmField val KEYBOARD_EDUCATION = releasedFlag("keyboard_education") // TODO(b/277201412): Tracking Bug @JvmField @@ -768,7 +772,8 @@ object Flags { @JvmField val PRECOMPUTED_TEXT = releasedFlag("precomputed_text") // TODO(b/302087895): Tracking Bug - @JvmField val CALL_LAYOUT_ASYNC_SET_DATA = unreleasedFlag("call_layout_async_set_data") + @JvmField val CALL_LAYOUT_ASYNC_SET_DATA = + unreleasedFlag("call_layout_async_set_data", teamfood = true) // TODO(b/302144438): Tracking Bug @JvmField val DECOUPLE_REMOTE_INPUT_DELEGATE_AND_CALLBACK_UPDATE = diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index aecfdaa860f3..e768f162c270 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -91,7 +91,7 @@ public class LogModule { @SysUISingleton @NotifInflationLog public static LogBuffer provideNotifInflationLogBuffer(LogBufferFactory factory) { - return factory.create("NotifInflationLog", 100); + return factory.create("NotifInflationLog", 250); } /** Provides a logging buffer for notification interruption calculations. */ diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt index 19621199aad5..98a389669ba9 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt @@ -16,7 +16,6 @@ package com.android.systemui.mediaprojection import android.media.projection.IMediaProjectionManager -import android.os.Process import android.os.RemoteException import android.util.Log import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_APP as METRICS_CREATION_SOURCE_APP @@ -66,56 +65,28 @@ constructor(private val service: IMediaProjectionManager) { } /** - * Request to log that the app selector was displayed. + * Request to log that the permission request was cancelled. * * @param hostUid The UID of the package that initiates MediaProjection. */ - fun notifyAppSelectorDisplayed(hostUid: Int) { + fun notifyProjectionRequestCancelled(hostUid: Int) { try { - service.notifyAppSelectorDisplayed(hostUid) + service.notifyPermissionRequestCancelled(hostUid) } catch (e: RemoteException) { - Log.e(TAG, "Error notifying server of app selector displayed", e) + Log.e(TAG, "Error notifying server of projection cancelled", e) } } /** - * Request to log that the permission request moved to the given state. - * - * Should not be used for the initialization state, since that should use {@link - * MediaProjectionMetricsLogger#notifyProjectionInitiated(Int)} and pass the - * sessionCreationSource. - */ - fun notifyPermissionProgress(state: Int) { - // TODO validate state is valid - notifyToServer(state, SessionCreationSource.UNKNOWN) - } - - /** - * Notifies system server that we are handling a particular state during the consent flow. - * - * Only used for emitting atoms. + * Request to log that the app selector was displayed. * - * @param state The state that SystemUI is handling during the consent flow. Must be a valid - * state defined in the MediaProjectionState enum. - * @param sessionCreationSource Only set if the state is MEDIA_PROJECTION_STATE_INITIATED. - * Indicates the entry point for requesting the permission. Must be a valid state defined in - * the SessionCreationSource enum. + * @param hostUid The UID of the package that initiates MediaProjection. */ - private fun notifyToServer(state: Int, sessionCreationSource: SessionCreationSource) { - Log.v(TAG, "FOO notifyToServer of state $state and source $sessionCreationSource") + fun notifyAppSelectorDisplayed(hostUid: Int) { try { - service.notifyPermissionRequestStateChange( - Process.myUid(), - state, - sessionCreationSource.toMetricsConstant() - ) + service.notifyAppSelectorDisplayed(hostUid) } catch (e: RemoteException) { - Log.e( - TAG, - "Error notifying server of permission flow state $state from source " + - "$sessionCreationSource", - e - ) + Log.e(TAG, "Error notifying server of app selector displayed", e) } } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt index 04d55665f572..50e9e7517a0f 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt @@ -210,6 +210,10 @@ class MediaProjectionAppSelectorActivity( reviewGrantedConsentRequired, /* projection= */ null ) + if (isFinishing) { + // Only log dismissed when actually finishing, and not when changing configuration. + controller.onSelectorDismissed() + } } activityLauncher.destroy() controller.destroy() diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt index 67ef119a7428..575e1986a23f 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt @@ -18,7 +18,6 @@ package com.android.systemui.mediaprojection.appselector import android.content.ComponentName import android.os.UserHandle -import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED as STATE_APP_SELECTOR_DISPLAYED import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger import com.android.systemui.mediaprojection.appselector.data.RecentTask import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider @@ -76,6 +75,10 @@ constructor( scope.cancel() } + fun onSelectorDismissed() { + logger.notifyProjectionRequestCancelled(hostUid) + } + private suspend fun refreshForegroundTaskThumbnails(tasks: List<RecentTask>) { coroutineScope { val thumbnails: List<Deferred<ThumbnailData?>> = diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseScreenSharePermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseScreenSharePermissionDialog.kt index 8b437c322549..eea369f15a16 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseScreenSharePermissionDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseScreenSharePermissionDialog.kt @@ -32,6 +32,7 @@ import androidx.annotation.ColorRes import androidx.annotation.DrawableRes import androidx.annotation.LayoutRes import androidx.annotation.StringRes +import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger import com.android.systemui.res.R import com.android.systemui.statusbar.phone.SystemUIDialog @@ -40,16 +41,31 @@ open class BaseScreenSharePermissionDialog( context: Context, private val screenShareOptions: List<ScreenShareOption>, private val appName: String?, + private val hostUid: Int, + private val mediaProjectionMetricsLogger: MediaProjectionMetricsLogger, @DrawableRes private val dialogIconDrawable: Int? = null, - @ColorRes private val dialogIconTint: Int? = null + @ColorRes private val dialogIconTint: Int? = null, ) : SystemUIDialog(context), AdapterView.OnItemSelectedListener { private lateinit var dialogTitle: TextView private lateinit var startButton: TextView private lateinit var cancelButton: TextView private lateinit var warning: TextView private lateinit var screenShareModeSpinner: Spinner + private var hasCancelBeenLogged: Boolean = false var selectedScreenShareOption: ScreenShareOption = screenShareOptions.first() + override fun dismiss() { + super.dismiss() + + // Dismiss can be called multiple times and we only want to log once. + if (hasCancelBeenLogged) { + return + } + + mediaProjectionMetricsLogger.notifyProjectionRequestCancelled(hostUid) + hasCancelBeenLogged = true + } + public override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) window?.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS) diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java index f7cc589db068..eacfa578ac44 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java @@ -222,13 +222,19 @@ public class MediaProjectionPermissionActivity extends Activity // the correct screen width when in split screen. Context dialogContext = getApplicationContext(); if (isPartialScreenSharingEnabled()) { - mDialog = new MediaProjectionPermissionDialog(dialogContext, getMediaProjectionConfig(), + mDialog = new MediaProjectionPermissionDialog( + dialogContext, + getMediaProjectionConfig(), () -> { MediaProjectionPermissionDialog dialog = (MediaProjectionPermissionDialog) mDialog; ScreenShareOption selectedOption = dialog.getSelectedScreenShareOption(); grantMediaProjectionPermission(selectedOption.getMode()); - }, () -> finish(RECORD_CANCEL, /* projection= */ null), appName); + }, + () -> finish(RECORD_CANCEL, /* projection= */ null), + appName, + mUid, + mMediaProjectionMetricsLogger); } else { AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(dialogContext, R.style.Theme_SystemUI_Dialog) diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt index b9bafd4926fa..cff22b0dc019 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt @@ -18,6 +18,7 @@ package com.android.systemui.mediaprojection.permission import android.content.Context import android.media.projection.MediaProjectionConfig import android.os.Bundle +import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger import com.android.systemui.res.R /** Dialog to select screen recording options */ @@ -26,12 +27,16 @@ class MediaProjectionPermissionDialog( mediaProjectionConfig: MediaProjectionConfig?, private val onStartRecordingClicked: Runnable, private val onCancelClicked: Runnable, - private val appName: String? + private val appName: String?, + hostUid: Int, + mediaProjectionMetricsLogger: MediaProjectionMetricsLogger, ) : BaseScreenSharePermissionDialog( context, createOptionList(context, appName, mediaProjectionConfig), - appName + appName, + hostUid, + mediaProjectionMetricsLogger ) { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt index 409622615b6c..7b4b55702f55 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt @@ -78,7 +78,10 @@ constructor( super.onCreate(savedInstanceState) uiEventLogger.log(BluetoothTileDialogUiEvent.BLUETOOTH_TILE_DIALOG_SHOWN) - setContentView(LayoutInflater.from(context).inflate(R.layout.bluetooth_tile_dialog, null)) + LayoutInflater.from(context).inflate(R.layout.bluetooth_tile_dialog, null).apply { + accessibilityPaneTitle = context.getText(R.string.accessibility_desc_quick_settings) + setContentView(this) + } toggleView = requireViewById(R.id.bluetooth_toggle) subtitleTextView = requireViewById(R.id.bluetooth_tile_dialog_subtitle) as TextView @@ -114,14 +117,22 @@ constructor( } internal fun onBluetoothStateUpdated(isEnabled: Boolean, subtitleResId: Int) { - toggleView.isChecked = isEnabled + toggleView.apply { + isChecked = isEnabled + setEnabled(true) + alpha = ENABLED_ALPHA + } subtitleTextView.text = context.getString(subtitleResId) } private fun setupToggle() { toggleView.isChecked = bluetoothToggleInitialValue - toggleView.setOnCheckedChangeListener { _, isChecked -> + toggleView.setOnCheckedChangeListener { view, isChecked -> mutableBluetoothStateToggle.value = isChecked + view.apply { + isEnabled = false + alpha = DISABLED_ALPHA + } logger.logBluetoothState(BluetoothStateStage.USER_TOGGLED, isChecked.toString()) uiEventLogger.log(BluetoothTileDialogUiEvent.BLUETOOTH_TOGGLE_CLICKED) } @@ -225,5 +236,7 @@ constructor( const val ACTION_PREVIOUSLY_CONNECTED_DEVICE = "com.android.settings.PREVIOUSLY_CONNECTED_DEVICE" const val ACTION_PAIR_NEW_DEVICE = "android.settings.BLUETOOTH_PAIRING_SETTINGS" + const val DISABLED_ALPHA = 0.3f + const val ENABLED_ALPHA = 1f } } diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java index ea1205ae6079..05f125f6f531 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java @@ -163,8 +163,7 @@ public class RecordingController } mMediaProjectionMetricsLogger.notifyProjectionInitiated( - mUserContextProvider.getUserContext().getUserId(), - SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER); + getHostUid(), SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER); return flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING) ? new ScreenRecordPermissionDialog( @@ -174,7 +173,8 @@ public class RecordingController /* controller= */ this, activityStarter, mUserContextProvider, - onStartRecordingClicked) + onStartRecordingClicked, + mMediaProjectionMetricsLogger) : new ScreenRecordDialog( context, /* controller= */ this, diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java index f2e94e94757f..e7481ccd0efd 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java @@ -66,8 +66,10 @@ public class ScreenRecordDialog extends SystemUIDialog { private Switch mAudioSwitch; private Spinner mOptions; - public ScreenRecordDialog(Context context, RecordingController controller, - UserContextProvider userContextProvider, @Nullable Runnable onStartRecordingClicked) { + public ScreenRecordDialog(Context context, + RecordingController controller, + UserContextProvider userContextProvider, + @Nullable Runnable onStartRecordingClicked) { super(context); mController = controller; mUserContextProvider = userContextProvider; @@ -89,7 +91,6 @@ public class ScreenRecordDialog extends SystemUIDialog { TextView cancelBtn = findViewById(R.id.button_cancel); cancelBtn.setOnClickListener(v -> dismiss()); - TextView startBtn = findViewById(R.id.button_start); startBtn.setOnClickListener(v -> { if (mOnStartRecordingClicked != null) { diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt index 3b3aa5334979..f74234bc2e21 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt @@ -33,6 +33,7 @@ import android.widget.Spinner import android.widget.Switch import androidx.annotation.LayoutRes import com.android.systemui.mediaprojection.MediaProjectionCaptureTarget +import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity import com.android.systemui.mediaprojection.permission.BaseScreenSharePermissionDialog import com.android.systemui.mediaprojection.permission.ENTIRE_SCREEN @@ -50,12 +51,15 @@ class ScreenRecordPermissionDialog( private val controller: RecordingController, private val activityStarter: ActivityStarter, private val userContextProvider: UserContextProvider, - private val onStartRecordingClicked: Runnable? + private val onStartRecordingClicked: Runnable?, + mediaProjectionMetricsLogger: MediaProjectionMetricsLogger, ) : BaseScreenSharePermissionDialog( context, createOptionList(), appName = null, + hostUid = hostUid, + mediaProjectionMetricsLogger, R.drawable.ic_screenrecord, R.color.screenrecord_icon_color ) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java index 33c42e043901..ab015efdb297 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java @@ -72,10 +72,10 @@ public class EmptyShadeView extends StackScrollerDecorView { return findViewById(R.id.no_notifications_footer); } - public void setTextColor(@ColorInt int color) { - mEmptyText.setTextColor(color); - mEmptyFooterText.setTextColor(color); - mEmptyFooterText.setCompoundDrawableTintList(ColorStateList.valueOf(color)); + public void setTextColors(@ColorInt int onSurface, @ColorInt int onSurfaceVariant) { + mEmptyText.setTextColor(onSurfaceVariant); + mEmptyFooterText.setTextColor(onSurface); + mEmptyFooterText.setCompoundDrawableTintList(ColorStateList.valueOf(onSurface)); } public void setText(@StringRes int text) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java index ba916542fa67..9c51e3bb8110 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java @@ -234,26 +234,24 @@ public class FooterView extends StackScrollerDecorView { */ public void updateColors() { Resources.Theme theme = mContext.getTheme(); - final @ColorInt int textColor = getResources().getColor(R.color.notif_pill_text, theme); + final @ColorInt int onSurface = Utils.getColorAttrDefaultColor(mContext, + com.android.internal.R.attr.materialColorOnSurface); + final @ColorInt int scHigh = Utils.getColorAttrDefaultColor(mContext, + com.android.internal.R.attr.materialColorSurfaceContainerHigh); final Drawable clearAllBg = theme.getDrawable(R.drawable.notif_footer_btn_background); final Drawable manageBg = theme.getDrawable(R.drawable.notif_footer_btn_background); // TODO(b/282173943): Remove redundant tinting once Resources are thread-safe - final @ColorInt int buttonBgColor = - Utils.getColorAttrDefaultColor(mContext, com.android.internal.R.attr.colorSurface); - final ColorFilter bgColorFilter = new PorterDuffColorFilter(buttonBgColor, SRC_ATOP); - if (buttonBgColor != 0) { + final ColorFilter bgColorFilter = new PorterDuffColorFilter(scHigh, SRC_ATOP); + if (scHigh != 0) { clearAllBg.setColorFilter(bgColorFilter); manageBg.setColorFilter(bgColorFilter); } mClearAllButton.setBackground(clearAllBg); - mClearAllButton.setTextColor(textColor); + mClearAllButton.setTextColor(onSurface); mManageButton.setBackground(manageBg); - mManageButton.setTextColor(textColor); - final @ColorInt int labelTextColor = - Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorPrimary); - mSeenNotifsFooterTextView.setTextColor(labelTextColor); - mSeenNotifsFooterTextView.setCompoundDrawableTintList( - ColorStateList.valueOf(labelTextColor)); + mManageButton.setTextColor(onSurface); + mSeenNotifsFooterTextView.setTextColor(onSurface); + mSeenNotifsFooterTextView.setCompoundDrawableTintList(ColorStateList.valueOf(onSurface)); } private void updateResources() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt new file mode 100644 index 000000000000..4ef80e38bc63 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.notification.interruption + +import com.android.internal.logging.UiEventLogger.UiEventEnum +import com.android.systemui.statusbar.notification.collection.NotificationEntry + +/** + * A reason why visual interruptions might be suppressed. + * + * @see VisualInterruptionCondition + * @see VisualInterruptionFilter + */ +enum class VisualInterruptionType { + /* HUN when awake */ + PEEK, + + /* HUN when dozing */ + PULSE, + + /* Bubble */ + BUBBLE +} + +/** + * A reason why visual interruptions might be suppressed. + * + * @see VisualInterruptionCondition + * @see VisualInterruptionFilter + */ +sealed interface VisualInterruptionSuppressor { + /** The type(s) of interruption that this suppresses. */ + val types: Set<VisualInterruptionType> + + /** A human-readable string to be logged to explain why this suppressed an interruption. */ + val reason: String + + /** An optional UiEvent ID to be recorded when this suppresses an interruption. */ + val uiEventId: UiEventEnum? +} + +/** A reason why visual interruptions might be suppressed regardless of the notification. */ +abstract class VisualInterruptionCondition( + override val types: Set<VisualInterruptionType>, + override val reason: String, + override val uiEventId: UiEventEnum? = null +) : VisualInterruptionSuppressor { + /** @return true if these interruptions should be suppressed right now. */ + abstract fun shouldSuppress(): Boolean +} + +/** A reason why visual interruptions might be suppressed based on the notification. */ +abstract class VisualInterruptionFilter( + override val types: Set<VisualInterruptionType>, + override val reason: String, + override val uiEventId: UiEventEnum? = null +) : VisualInterruptionSuppressor { + /** + * @param entry the notification to consider suppressing + * @return true if these interruptions should be suppressed for this notification right now + */ + abstract fun shouldSuppress(entry: NotificationEntry): Boolean +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java index 661768da8479..8f1e59d20091 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java @@ -121,7 +121,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView private void updateColors() { mNormalColor = Utils.getColorAttrDefaultColor(mContext, - com.android.internal.R.attr.colorSurface); + com.android.internal.R.attr.materialColorSurfaceContainerHigh); mTintedRippleColor = mContext.getColor( R.color.notification_ripple_tinted_color); mNormalRippleColor = mContext.getColor( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java index 892a63505a71..da8c4dc08bf0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java @@ -28,7 +28,6 @@ import android.widget.TextView; import androidx.annotation.ColorInt; -import com.android.internal.util.ContrastColorUtil; import com.android.keyguard.AlphaOptimizedLinearLayout; import com.android.systemui.res.R; import com.android.systemui.statusbar.CrossFadeHelper; @@ -98,8 +97,8 @@ public class HybridNotificationView extends AlphaOptimizedLinearLayout private void resolveThemeTextColors() { try (TypedArray ta = mContext.getTheme().obtainStyledAttributes( android.R.style.Theme_DeviceDefault_DayNight, new int[]{ - android.R.attr.textColorPrimary, - android.R.attr.textColorSecondary + com.android.internal.R.attr.materialColorOnSurface, + com.android.internal.R.attr.materialColorOnSurfaceVariant })) { if (ta != null) { mPrimaryTextColor = ta.getColor(0, mPrimaryTextColor); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java index 62b268b90cf3..14593cb1b381 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java @@ -65,10 +65,10 @@ import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; import com.android.settingslib.notification.ConversationIconFactory; -import com.android.systemui.res.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.people.widget.PeopleSpaceWidgetManager; +import com.android.systemui.res.R; import com.android.systemui.shade.ShadeController; import com.android.systemui.statusbar.notification.NotificationChannelHelper; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -336,10 +336,11 @@ public class NotificationConversationInfo extends LinearLayout implements Drawable person = mIconFactory.getBaseIconDrawable(mShortcutInfo); if (person == null) { person = mContext.getDrawable(R.drawable.ic_person).mutate(); - TypedArray ta = mContext.obtainStyledAttributes(new int[]{android.R.attr.colorAccent}); - int colorAccent = ta.getColor(0, 0); + TypedArray ta = mContext.obtainStyledAttributes( + new int[]{com.android.internal.R.attr.materialColorPrimary}); + int colorPrimary = ta.getColor(0, 0); ta.recycle(); - person.setTint(colorAccent); + person.setTint(colorPrimary); } ImageView image = findViewById(R.id.conversation_icon); image.setImageDrawable(person); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java index ff5b9cbf3c23..cdf178e813f5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java @@ -326,7 +326,8 @@ public abstract class NotificationViewWrapper implements TransformableView { if (customBackgroundColor != 0) { return customBackgroundColor; } - return Utils.getColorAttr(mView.getContext(), android.R.attr.colorBackground) + return Utils.getColorAttr(mView.getContext(), + com.android.internal.R.attr.materialColorSurfaceContainerHigh) .getDefaultColor(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java index a929e4f3ea7f..0236fc265add 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java @@ -1362,7 +1362,7 @@ public class NotificationChildrenContainer extends ViewGroup Resources.Theme theme = new ContextThemeWrapper(mContext, com.android.internal.R.style.Theme_DeviceDefault_DayNight).getTheme(); try (TypedArray ta = theme.obtainStyledAttributes( - new int[]{com.android.internal.R.attr.colorAccent})) { + new int[]{com.android.internal.R.attr.materialColorPrimary})) { color = ta.getColor(0, color); } mHybridGroupManager.setOverflowNumberColor(mOverflowNumber, color); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt index fd064eeab2ab..cfc433a09c4d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt @@ -244,10 +244,10 @@ class NotificationSectionsManager @Inject internal constructor( } } - fun setHeaderForegroundColor(@ColorInt color: Int) { - peopleHeaderView?.setForegroundColor(color) - silentHeaderView?.setForegroundColor(color) - alertingHeaderView?.setForegroundColor(color) + fun setHeaderForegroundColors(@ColorInt onSurface: Int, @ColorInt onSurfaceVariant: Int) { + peopleHeaderView?.setForegroundColors(onSurface, onSurfaceVariant) + silentHeaderView?.setForegroundColors(onSurface, onSurfaceVariant) + alertingHeaderView?.setForegroundColors(onSurface, onSurfaceVariant) } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 1e9cfa8d1d3a..8babcc24043a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -344,7 +344,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private boolean mAnimateNextBackgroundTop; private boolean mAnimateNextBackgroundBottom; private boolean mAnimateNextSectionBoundsChange; - private int mBgColor; + private @ColorInt int mBgColor; private float mDimAmount; private ValueAnimator mDimAnimator; private final ArrayList<ExpandableView> mTmpSortedChildren = new ArrayList<>(); @@ -647,8 +647,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mSections = mSectionsManager.createSectionsForBuckets(); mAmbientState = Dependency.get(AmbientState.class); - mBgColor = Utils.getColorAttr(mContext, android.R.attr.colorBackgroundFloating) - .getDefaultColor(); + mBgColor = Utils.getColorAttr(mContext, + com.android.internal.R.attr.materialColorSurfaceContainerHigh).getDefaultColor(); int minHeight = res.getDimensionPixelSize(R.dimen.notification_min_height); int maxHeight = res.getDimensionPixelSize(R.dimen.notification_max_height); mSplitShadeMinContentHeight = res.getDimensionPixelSize( @@ -790,8 +790,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } void updateBgColor() { - mBgColor = Utils.getColorAttr(mContext, android.R.attr.colorBackgroundFloating) - .getDefaultColor(); + mBgColor = Utils.getColorAttr(mContext, + com.android.internal.R.attr.materialColorSurfaceContainerHigh).getDefaultColor(); updateBackgroundDimming(); for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); @@ -4421,14 +4421,19 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } /** - * Update colors of "dismiss" and "empty shade" views. + * Update colors of section headers, shade footer, and empty shade views. */ void updateDecorViews() { - final @ColorInt int textColor = - Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorPrimary); - mSectionsManager.setHeaderForegroundColor(textColor); + final @ColorInt int onSurface = Utils.getColorAttrDefaultColor( + mContext, com.android.internal.R.attr.materialColorOnSurface); + final @ColorInt int onSurfaceVariant = Utils.getColorAttrDefaultColor( + mContext, com.android.internal.R.attr.materialColorOnSurfaceVariant); + + mSectionsManager.setHeaderForegroundColors(onSurface, onSurfaceVariant); + mFooterView.updateColors(); - mEmptyShadeView.setTextColor(textColor); + + mEmptyShadeView.setTextColors(onSurface, onSurfaceVariant); } void goToFullShade(long delay) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java index 722c28c43e8a..5c1149bafb2f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java @@ -132,8 +132,8 @@ public class SectionHeaderView extends StackScrollerDecorView { mLabelView.setText(resId); } - void setForegroundColor(@ColorInt int color) { - mLabelView.setTextColor(color); - mClearAllButton.setImageTintList(ColorStateList.valueOf(color)); + void setForegroundColors(@ColorInt int onSurface, @ColorInt int onSurfaceVariant) { + mLabelView.setTextColor(onSurface); + mClearAllButton.setImageTintList(ColorStateList.valueOf(onSurfaceVariant)); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 744d70e36972..3e753a589a84 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -1505,8 +1505,9 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump private void updateThemeColors() { if (mScrimBehind == null) return; int background = Utils.getColorAttr(mScrimBehind.getContext(), - android.R.attr.colorBackgroundFloating).getDefaultColor(); - int accent = Utils.getColorAccent(mScrimBehind.getContext()).getDefaultColor(); + com.android.internal.R.attr.materialColorSurfaceDim).getDefaultColor(); + int accent = Utils.getColorAttr(mScrimBehind.getContext(), + com.android.internal.R.attr.materialColorPrimary).getDefaultColor(); mColors.setMainColor(background); mColors.setSecondaryColor(accent); final boolean isBackgroundLight = !ContrastColorUtil.isColorDark(background); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainView.java index a033e1d55333..66ac17eee545 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainView.java @@ -20,6 +20,7 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; +import android.annotation.ColorInt; import android.content.Context; import android.util.AttributeSet; import android.view.View; @@ -28,6 +29,7 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.settingslib.Utils; import com.android.systemui.res.R; import com.android.wm.shell.animation.Interpolators; @@ -49,8 +51,9 @@ public class TapAgainView extends TextView { } void updateColor() { - int textColor = getResources().getColor(R.color.notif_pill_text, mContext.getTheme()); - setTextColor(textColor); + final @ColorInt int onSurface = Utils.getColorAttrDefaultColor(mContext, + com.android.internal.R.attr.materialColorOnSurface); + setTextColor(onSurface); setBackground(getResources().getDrawable(R.drawable.rounded_bg_full, mContext.getTheme())); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt index 27f6df4c26e1..d9d909a49781 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.pipeline.mobile.data.model import android.os.ParcelUuid +import android.telephony.SubscriptionManager.ProfileClass /** * SystemUI representation of [SubscriptionInfo]. Currently we only use two fields on the @@ -37,4 +38,7 @@ data class SubscriptionModel( /** Text representing the name for this connection */ val carrierName: String, + + /** Allow us to filter out PROVISIONING profiles. See [SubscriptionInfo.getProfileClass] */ + @ProfileClass val profileClass: Int ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt index c7987e2f8eb1..205dc1cd66cf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository.demo import android.content.Context import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID +import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET import android.util.Log import com.android.settingslib.SignalIcon import com.android.settingslib.mobile.MobileMappings @@ -96,6 +97,7 @@ constructor( subscriptionId = subId, isOpportunistic = false, carrierName = DEFAULT_CARRIER_NAME, + profileClass = PROFILE_CLASS_UNSET, ) .also { subscriptionInfoCache[subId] = it } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt index ecb80f28de3e..2caf33bd92cf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt @@ -408,6 +408,7 @@ constructor( isOpportunistic = isOpportunistic, groupUuid = groupUuid, carrierName = carrierName.toString(), + profileClass = profileClass, ) companion object { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt index 62150e9a1236..dad409316730 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt @@ -19,10 +19,13 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor import android.content.Context import android.telephony.CarrierConfigManager import android.telephony.SubscriptionManager +import android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING import com.android.settingslib.SignalIcon.MobileIconGroup import com.android.settingslib.mobile.TelephonyIcons import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.flags.FeatureFlagsClassic +import com.android.systemui.flags.Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.logDiffsForTable import com.android.systemui.statusbar.pipeline.dagger.MobileSummaryLog @@ -121,6 +124,7 @@ constructor( userSetupRepo: UserSetupRepository, @Application private val scope: CoroutineScope, private val context: Context, + private val featureFlagsClassic: FeatureFlagsClassic, ) : MobileIconsInteractor { // Weak reference lookup for created interactors @@ -163,6 +167,20 @@ constructor( mobileConnectionsRepo.subscriptions /** + * Any filtering that we can do based purely on the info of each subscription. Currently this + * only applies the ProfileClass-based filter, but if we need other they can go here + */ + private val subscriptionsBasedFilteredSubs = + unfilteredSubscriptions.map { subs -> applyProvisioningFilter(subs) }.distinctUntilChanged() + + private fun applyProvisioningFilter(subs: List<SubscriptionModel>): List<SubscriptionModel> = + if (!featureFlagsClassic.isEnabled(FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS)) { + subs + } else { + subs.filter { it.profileClass != PROFILE_CLASS_PROVISIONING } + } + + /** * Generally, SystemUI wants to show iconography for each subscription that is listed by * [SubscriptionManager]. However, in the case of opportunistic subscriptions, we want to only * show a single representation of the pair of subscriptions. The docs define opportunistic as: @@ -177,48 +195,15 @@ constructor( */ override val filteredSubscriptions: Flow<List<SubscriptionModel>> = combine( - unfilteredSubscriptions, + subscriptionsBasedFilteredSubs, mobileConnectionsRepo.activeMobileDataSubscriptionId, connectivityRepository.vcnSubId, ) { unfilteredSubs, activeId, vcnSubId -> - // Based on the old logic, - if (unfilteredSubs.size != 2) { - return@combine unfilteredSubs - } - - val info1 = unfilteredSubs[0] - val info2 = unfilteredSubs[1] - - // Filtering only applies to subscriptions in the same group - if (info1.groupUuid == null || info1.groupUuid != info2.groupUuid) { - return@combine unfilteredSubs - } - - // If both subscriptions are primary, show both - if (!info1.isOpportunistic && !info2.isOpportunistic) { - return@combine unfilteredSubs - } - - // NOTE: at this point, we are now returning a single SubscriptionInfo - - // If carrier required, always show the icon of the primary subscription. - // Otherwise, show whichever subscription is currently active for internet. - if (carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) { - // return the non-opportunistic info - return@combine if (info1.isOpportunistic) listOf(info2) else listOf(info1) - } else { - // It's possible for the subId of the VCN to disagree with the active subId in - // cases where the system has tried to switch but found no connection. In these - // scenarios, VCN will always have the subId that we want to use, so use that - // value instead of the activeId reported by telephony - val subIdToKeep = vcnSubId ?: activeId - - return@combine if (info1.subscriptionId == subIdToKeep) { - listOf(info1) - } else { - listOf(info2) - } - } + filterSubsBasedOnOpportunistic( + unfilteredSubs, + activeId, + vcnSubId, + ) } .distinctUntilChanged() .logDiffsForTable( @@ -229,6 +214,51 @@ constructor( ) .stateIn(scope, SharingStarted.WhileSubscribed(), listOf()) + private fun filterSubsBasedOnOpportunistic( + subList: List<SubscriptionModel>, + activeId: Int?, + vcnSubId: Int?, + ): List<SubscriptionModel> { + // Based on the old logic, + if (subList.size != 2) { + return subList + } + + val info1 = subList[0] + val info2 = subList[1] + + // Filtering only applies to subscriptions in the same group + if (info1.groupUuid == null || info1.groupUuid != info2.groupUuid) { + return subList + } + + // If both subscriptions are primary, show both + if (!info1.isOpportunistic && !info2.isOpportunistic) { + return subList + } + + // NOTE: at this point, we are now returning a single SubscriptionInfo + + // If carrier required, always show the icon of the primary subscription. + // Otherwise, show whichever subscription is currently active for internet. + if (carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) { + // return the non-opportunistic info + return if (info1.isOpportunistic) listOf(info2) else listOf(info1) + } else { + // It's possible for the subId of the VCN to disagree with the active subId in + // cases where the system has tried to switch but found no connection. In these + // scenarios, VCN will always have the subId that we want to use, so use that + // value instead of the activeId reported by telephony + val subIdToKeep = vcnSubId ?: activeId + + return if (info1.subscriptionId == subIdToKeep) { + listOf(info1) + } else { + listOf(info2) + } + } + } + /** * Copied from the old pipeline. We maintain a 2s period of time where we will keep the * validated bit from the old active network (A) while data is changing to the new one (B). diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java index ceed81a182aa..4864fb8ca634 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java @@ -130,7 +130,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene private ImageView mDelete; private ImageView mDeleteBg; private boolean mColorized; - private int mTint; + private int mLastBackgroundColor; private boolean mResetting; @Nullable private RevealParams mRevealParams; @@ -181,10 +181,9 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene mEditorActionHandler = new EditorActionHandler(); mUiEventLogger = Dependency.get(UiEventLogger.class); TypedArray ta = getContext().getTheme().obtainStyledAttributes(new int[]{ - com.android.internal.R.attr.colorAccent, - com.android.internal.R.attr.colorSurface, + com.android.internal.R.attr.materialColorSurfaceDim, }); - mTint = ta.getColor(0, 0); + mLastBackgroundColor = ta.getColor(0, 0); ta.recycle(); } @@ -210,9 +209,9 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene * @param backgroundColor colorized notification color */ public void setBackgroundTintColor(final int backgroundColor, boolean colorized) { - if (colorized == mColorized && backgroundColor == mTint) return; + if (colorized == mColorized && backgroundColor == mLastBackgroundColor) return; mColorized = colorized; - mTint = backgroundColor; + mLastBackgroundColor = backgroundColor; final int editBgColor; final int deleteBgColor; final int deleteFgColor; @@ -237,8 +236,8 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene hintColor = mContext.getColor(R.color.remote_input_hint); deleteFgColor = textColor.getDefaultColor(); try (TypedArray ta = getContext().getTheme().obtainStyledAttributes(new int[]{ - com.android.internal.R.attr.colorSurfaceHighlight, - com.android.internal.R.attr.colorSurfaceVariant + com.android.internal.R.attr.materialColorSurfaceDim, + com.android.internal.R.attr.materialColorSurfaceVariant })) { editBgColor = ta.getColor(0, backgroundColor); deleteBgColor = ta.getColor(1, Color.GRAY); diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 0ff308e19dd6..280c66a33151 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -99,7 +99,6 @@ import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.DecelerateInterpolator; -import android.widget.FrameLayout; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; @@ -256,7 +255,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, private CaptionsToggleImageButton mODICaptionsIcon; private View mSettingsView; private ImageButton mSettingsIcon; - private FrameLayout mZenIcon; private final List<VolumeRow> mRows = new ArrayList<>(); private ConfigurableTexts mConfigurableTexts; private final SparseBooleanArray mDynamic = new SparseBooleanArray(); @@ -633,7 +631,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, mRinger = mDialog.findViewById(R.id.ringer); if (mRinger != null) { mRingerIcon = mRinger.findViewById(R.id.ringer_icon); - mZenIcon = mRinger.findViewById(R.id.dnd_icon); } mSelectedRingerIcon = mDialog.findViewById(R.id.volume_new_ringer_active_icon); @@ -847,7 +844,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, if (stream == STREAM_ACCESSIBILITY) { row.header.setFilters(new InputFilter[] {new InputFilter.LengthFilter(13)}); } - row.dndIcon = row.view.findViewById(R.id.dnd_icon); row.slider = row.view.findViewById(R.id.volume_row_slider); row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row)); row.number = row.view.findViewById(R.id.volume_number); @@ -1791,27 +1787,13 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, } /** - * Toggles enable state of views in a VolumeRow (not including seekbar or icon) - * Hides/shows zen icon - * @param enable whether to enable volume row views and hide dnd icon - */ - private void enableVolumeRowViewsH(VolumeRow row, boolean enable) { - boolean showDndIcon = !enable; - row.dndIcon.setVisibility(showDndIcon ? VISIBLE : GONE); - } - - /** * Toggles enable state of footer/ringer views - * Hides/shows zen icon - * @param enable whether to enable ringer views and hide dnd icon + * @param enable whether to enable ringer views */ private void enableRingerViewsH(boolean enable) { if (mRingerIcon != null) { mRingerIcon.setEnabled(enable); } - if (mZenIcon != null) { - mZenIcon.setVisibility(enable ? GONE : VISIBLE); - } } private void trimObsoleteH() { @@ -1937,9 +1919,11 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, // update icon final boolean iconEnabled = (mAutomute || ss.muteSupported) && !zenMuted; final int iconRes; - if (isRingVibrate) { + if (zenMuted) { + iconRes = com.android.internal.R.drawable.ic_qs_dnd; + } else if (isRingVibrate) { iconRes = R.drawable.ic_volume_ringer_vibrate; - } else if (isRingSilent || zenMuted) { + } else if (isRingSilent) { iconRes = row.iconMuteRes; } else if (ss.routedToBluetooth) { if (isVoiceCallStream) { @@ -2011,7 +1995,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, if (zenMuted) { row.tracking = false; } - enableVolumeRowViewsH(row, !zenMuted); // update slider final boolean enableSlider = !zenMuted; @@ -2582,7 +2565,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, private ObjectAnimator anim; // slider progress animation for non-touch-related updates private int animTargetProgress; private int lastAudibleLevel = 1; - private FrameLayout dndIcon; void setIcon(int iconRes, Resources.Theme theme) { if (icon != null) { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java index ac40ba617936..603d548694ec 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java @@ -21,7 +21,6 @@ import static android.view.View.INVISIBLE; import static com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR; import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED; import static com.android.systemui.flags.Flags.MIGRATE_KEYGUARD_STATUS_VIEW; -import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.atLeast; @@ -58,7 +57,6 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController; import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore; import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel; -import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.NotificationIconAreaController; import com.android.systemui.statusbar.phone.NotificationIconContainer; @@ -144,7 +142,6 @@ public class KeyguardClockSwitchControllerBaseTest extends SysuiTestCase { public void setup() { MockitoAnnotations.initMocks(this); - setFlagDefault(mSetFlagsRule, NotificationIconContainerRefactor.FLAG_NAME); mFakeDateView.setTag(R.id.tag_smartspace_view, new Object()); mFakeWeatherView.setTag(R.id.tag_smartspace_view, new Object()); mFakeSmartspaceView.setTag(R.id.tag_smartspace_view, new Object()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MockMagnificationAnimationCallback.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MockMagnificationAnimationCallback.java deleted file mode 100644 index 89389b0dd424..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MockMagnificationAnimationCallback.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.accessibility; - -import android.os.RemoteException; -import android.view.accessibility.IRemoteMagnificationAnimationCallback; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicInteger; - -public class MockMagnificationAnimationCallback extends IRemoteMagnificationAnimationCallback.Stub { - - private final CountDownLatch mCountDownLatch; - private final AtomicInteger mSuccessCount; - private final AtomicInteger mFailedCount; - - MockMagnificationAnimationCallback(CountDownLatch countDownLatch) { - mCountDownLatch = countDownLatch; - mSuccessCount = new AtomicInteger(); - mFailedCount = new AtomicInteger(); - } - - public int getSuccessCount() { - return mSuccessCount.get(); - } - - public int getFailedCount() { - return mFailedCount.get(); - } - - @Override - public void onResult(boolean success) throws RemoteException { - if (success) { - mSuccessCount.getAndIncrement(); - } else { - mFailedCount.getAndIncrement(); - } - // It should be put at the last line to avoid making CountDownLatch#await passed without - // updating values. - mCountDownLatch.countDown(); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java index f15164e3fdcc..284c273fa831 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java @@ -35,7 +35,6 @@ import android.graphics.Rect; import android.os.Handler; import android.os.RemoteException; import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper.RunWithLooper; import android.view.SurfaceControl; import android.view.View; import android.view.WindowManager; @@ -68,7 +67,6 @@ import java.util.concurrent.atomic.AtomicReference; @LargeTest @RunWith(AndroidTestingRunner.class) -@RunWithLooper(setAsMainLooper = true) public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { @Rule @@ -135,7 +133,9 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { @After public void tearDown() throws Exception { - mController.deleteWindowMagnification(); + mInstrumentation.runOnMainSync(() -> { + mController.deleteWindowMagnification(); + }); } @Test @@ -170,8 +170,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { final float targetCenterX = DEFAULT_CENTER_X + 100; final float targetCenterY = DEFAULT_CENTER_Y + 100; - mWindowMagnificationAnimationController.enableWindowMagnification(targetScale, - targetCenterX, targetCenterY, null); + enableWindowMagnificationWithoutAnimation(targetScale, targetCenterX, targetCenterY); verifyFinalSpec(targetScale, targetCenterX, targetCenterY); } @@ -179,10 +178,11 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { @Test public void enableWindowMagnificationWithScaleOne_disabled_NoAnimationAndInvokeCallback() throws RemoteException { - mWindowMagnificationAnimationController.enableWindowMagnification(1, + enableWindowMagnificationAndWaitAnimating( + mWaitAnimationDuration, /* targetScale= */ 1.0f, DEFAULT_CENTER_X, DEFAULT_CENTER_Y, mAnimationCallback); - verify(mSpyController).enableWindowMagnificationInternal(1, DEFAULT_CENTER_X, + verify(mSpyController).enableWindowMagnificationInternal(1.0f, DEFAULT_CENTER_X, DEFAULT_CENTER_Y, 0f, 0f); verify(mAnimationCallback).onResult(true); } @@ -196,13 +196,15 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { final float targetCenterX = DEFAULT_CENTER_X + 100; final float targetCenterY = DEFAULT_CENTER_Y + 100; - Mockito.reset(mSpyController); - mWindowMagnificationAnimationController.enableWindowMagnification(targetScale, - targetCenterX, targetCenterY, mAnimationCallback2); - mCurrentScale.set(mController.getScale()); - mCurrentCenterX.set(mController.getCenterX()); - mCurrentCenterY.set(mController.getCenterY()); - advanceTimeBy(mWaitAnimationDuration); + resetMockObjects(); + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationAnimationController.enableWindowMagnification(targetScale, + targetCenterX, targetCenterY, mAnimationCallback2); + mCurrentScale.set(mController.getScale()); + mCurrentCenterX.set(mController.getCenterX()); + mCurrentCenterY.set(mController.getCenterY()); + advanceTimeBy(mWaitAnimationDuration); + }); verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal( mScaleCaptor.capture(), @@ -224,9 +226,8 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration, mAnimationCallback); - mWindowMagnificationAnimationController.enableWindowMagnification(Float.NaN, + enableWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, Float.NaN, Float.NaN, Float.NaN, mAnimationCallback2); - advanceTimeBy(mWaitAnimationDuration); // The callback in 2nd enableWindowMagnification will return true verify(mAnimationCallback2).onResult(true); @@ -245,12 +246,14 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { final float targetCenterY = DEFAULT_CENTER_Y + 100; Mockito.reset(mSpyController); - mWindowMagnificationAnimationController.enableWindowMagnification(targetScale, - targetCenterX, targetCenterY, mAnimationCallback); - mCurrentScale.set(mController.getScale()); - mCurrentCenterX.set(mController.getCenterX()); - mCurrentCenterY.set(mController.getCenterY()); - advanceTimeBy(mWaitAnimationDuration); + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationAnimationController.enableWindowMagnification(targetScale, + targetCenterX, targetCenterY, mAnimationCallback); + mCurrentScale.set(mController.getScale()); + mCurrentCenterX.set(mController.getCenterX()); + mCurrentCenterY.set(mController.getCenterY()); + advanceTimeBy(mWaitAnimationDuration); + }); verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal( mScaleCaptor.capture(), @@ -279,12 +282,14 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { final float targetCenterY = DEFAULT_CENTER_Y + 100; Mockito.reset(mSpyController); - mWindowMagnificationAnimationController.enableWindowMagnification(targetScale, - targetCenterX, targetCenterY, mAnimationCallback); - mCurrentScale.set(mController.getScale()); - mCurrentCenterX.set(mController.getCenterX()); - mCurrentCenterY.set(mController.getCenterY()); - advanceTimeBy(mWaitAnimationDuration); + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationAnimationController.enableWindowMagnification(targetScale, + targetCenterX, targetCenterY, mAnimationCallback); + mCurrentScale.set(mController.getScale()); + mCurrentCenterX.set(mController.getCenterX()); + mCurrentCenterY.set(mController.getCenterY()); + advanceTimeBy(mWaitAnimationDuration); + }); verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal( mScaleCaptor.capture(), @@ -314,8 +319,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { final float targetCenterY = DEFAULT_CENTER_Y + 100; Mockito.reset(mSpyController); - mWindowMagnificationAnimationController.enableWindowMagnification(targetScale, - targetCenterX, targetCenterY, null); + enableWindowMagnificationWithoutAnimation(targetScale, targetCenterX, targetCenterY); verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN); assertEquals(WindowMagnificationAnimationController.STATE_DISABLED, @@ -333,8 +337,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { final float targetCenterY = DEFAULT_CENTER_Y + 100; Mockito.reset(mSpyController); - mWindowMagnificationAnimationController.enableWindowMagnification(targetScale, - targetCenterX, targetCenterY, null); + enableWindowMagnificationWithoutAnimation(targetScale, targetCenterX, targetCenterY); verifyFinalSpec(targetScale, targetCenterX, targetCenterY); verify(mAnimationCallback).onResult(false); @@ -347,9 +350,8 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { mAnimationCallback); Mockito.reset(mSpyController); - mWindowMagnificationAnimationController.enableWindowMagnification(Float.NaN, - Float.NaN, Float.NaN, mAnimationCallback2); - advanceTimeBy(mWaitAnimationDuration); + enableWindowMagnificationAndWaitAnimating( + mWaitAnimationDuration, Float.NaN, Float.NaN, Float.NaN, mAnimationCallback2); verify(mSpyController, never()).enableWindowMagnificationInternal(anyFloat(), anyFloat(), anyFloat()); @@ -368,20 +370,25 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { final float targetCenterY = DEFAULT_CENTER_Y + 100; Mockito.reset(mSpyController); - mWindowMagnificationAnimationController.enableWindowMagnification(targetScale, - targetCenterX, targetCenterY, mAnimationCallback2); - mCurrentScale.set(mController.getScale()); - mCurrentCenterX.set(mController.getCenterX()); - mCurrentCenterY.set(mController.getCenterY()); + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationAnimationController.enableWindowMagnification(targetScale, + targetCenterX, targetCenterY, mAnimationCallback2); + mCurrentScale.set(mController.getScale()); + mCurrentCenterX.set(mController.getCenterX()); + mCurrentCenterY.set(mController.getCenterY()); + }); // Current spec shouldn't match given spec. verify(mAnimationCallback2, never()).onResult(anyBoolean()); verify(mAnimationCallback).onResult(false); - // ValueAnimator.reverse() could not work correctly with the AnimatorTestRule since it is - // using SystemClock in reverse() (b/305731398). Therefore, we call end() on the animator - // directly to verify the result of animation is correct instead of querying the animation - // frame at a specific timing. - mValueAnimator.end(); + + mInstrumentation.runOnMainSync(() -> { + // ValueAnimator.reverse() could not work correctly with the AnimatorTestRule since it + // is using SystemClock in reverse() (b/305731398). Therefore, we call end() on the + // animator directly to verify the result of animation is correct instead of querying + // the animation frame at a specific timing. + mValueAnimator.end(); + }); verify(mSpyController).enableWindowMagnificationInternal( mScaleCaptor.capture(), @@ -410,8 +417,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { final float targetCenterY = DEFAULT_CENTER_Y + 100; Mockito.reset(mSpyController); - mWindowMagnificationAnimationController.enableWindowMagnification(targetScale, - targetCenterX, targetCenterY, null); + enableWindowMagnificationWithoutAnimation(targetScale, targetCenterX, targetCenterY); verify(mAnimationCallback).onResult(false); verifyFinalSpec(targetScale, targetCenterX, targetCenterY); @@ -425,9 +431,8 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { mAnimationCallback); Mockito.reset(mSpyController); - mWindowMagnificationAnimationController.enableWindowMagnification(Float.NaN, + enableWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, Float.NaN, Float.NaN, Float.NaN, mAnimationCallback2); - advanceTimeBy(mWaitAnimationDuration); verify(mSpyController, never()).enableWindowMagnificationInternal(anyFloat(), anyFloat(), anyFloat()); @@ -445,12 +450,14 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { final float targetCenterY = DEFAULT_CENTER_Y + 100; Mockito.reset(mSpyController); - mWindowMagnificationAnimationController.enableWindowMagnification(targetScale, - targetCenterX, targetCenterY, mAnimationCallback2); - mCurrentScale.set(mController.getScale()); - mCurrentCenterX.set(mController.getCenterX()); - mCurrentCenterY.set(mController.getCenterY()); - advanceTimeBy(mWaitAnimationDuration); + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationAnimationController.enableWindowMagnification(targetScale, + targetCenterX, targetCenterY, mAnimationCallback2); + mCurrentScale.set(mController.getScale()); + mCurrentCenterX.set(mController.getCenterX()); + mCurrentCenterY.set(mController.getCenterY()); + advanceTimeBy(mWaitAnimationDuration); + }); verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal( mScaleCaptor.capture(), @@ -471,25 +478,26 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { final Rect windowBounds = new Rect(mWindowManager.getCurrentWindowMetrics().getBounds()); Mockito.reset(mSpyController); - mWindowMagnificationAnimationController.enableWindowMagnification(DEFAULT_SCALE, - windowBounds.exactCenterX(), windowBounds.exactCenterY(), - offsetRatio, offsetRatio, mAnimationCallback); - advanceTimeBy(mWaitAnimationDuration); + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationAnimationController.enableWindowMagnification(DEFAULT_SCALE, + windowBounds.exactCenterX(), windowBounds.exactCenterY(), + offsetRatio, offsetRatio, mAnimationCallback); + advanceTimeBy(mWaitAnimationDuration); + }); - // We delay the time of verifying to wait for the measurement and layout of the view - mHandler.postDelayed(() -> { - final View attachedView = mWindowManager.getAttachedView(); - assertNotNull(attachedView); - final Rect mirrorViewBound = new Rect(); - final View mirrorView = attachedView.findViewById(R.id.surface_view); - assertNotNull(mirrorView); - mirrorView.getBoundsOnScreen(mirrorViewBound); + // Wait for Rects update + waitForIdleSync(); + final View attachedView = mWindowManager.getAttachedView(); + assertNotNull(attachedView); + final Rect mirrorViewBound = new Rect(); + final View mirrorView = attachedView.findViewById(R.id.surface_view); + assertNotNull(mirrorView); + mirrorView.getBoundsOnScreen(mirrorViewBound); - assertEquals((int) (offsetRatio * mirrorViewBound.width() / 2), - (int) (mirrorViewBound.exactCenterX() - windowBounds.exactCenterX())); - assertEquals((int) (offsetRatio * mirrorViewBound.height() / 2), - (int) (mirrorViewBound.exactCenterY() - windowBounds.exactCenterY())); - }, 100); + assertEquals((int) (offsetRatio * mirrorViewBound.width() / 2), + (int) (mirrorViewBound.exactCenterX() - windowBounds.exactCenterX())); + assertEquals((int) (offsetRatio * mirrorViewBound.height() / 2), + (int) (mirrorViewBound.exactCenterY() - windowBounds.exactCenterY())); } @Test @@ -498,9 +506,11 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { final float targetCenterY = DEFAULT_CENTER_Y + 100; enableWindowMagnificationWithoutAnimation(); - mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( - targetCenterX, targetCenterY, mAnimationCallback); - advanceTimeBy(mWaitAnimationDuration); + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( + targetCenterX, targetCenterY, mAnimationCallback); + advanceTimeBy(mWaitAnimationDuration); + }); verify(mAnimationCallback).onResult(true); verify(mAnimationCallback, never()).onResult(false); @@ -512,15 +522,17 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { throws RemoteException { enableWindowMagnificationWithoutAnimation(); - mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( - DEFAULT_CENTER_X + 10, DEFAULT_CENTER_Y + 10, mAnimationCallback); - mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( - DEFAULT_CENTER_X + 20, DEFAULT_CENTER_Y + 20, mAnimationCallback); - mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( - DEFAULT_CENTER_X + 30, DEFAULT_CENTER_Y + 30, mAnimationCallback); - mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( - DEFAULT_CENTER_X + 40, DEFAULT_CENTER_Y + 40, mAnimationCallback2); - advanceTimeBy(mWaitAnimationDuration); + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( + DEFAULT_CENTER_X + 10, DEFAULT_CENTER_Y + 10, mAnimationCallback); + mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( + DEFAULT_CENTER_X + 20, DEFAULT_CENTER_Y + 20, mAnimationCallback); + mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( + DEFAULT_CENTER_X + 30, DEFAULT_CENTER_Y + 30, mAnimationCallback); + mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( + DEFAULT_CENTER_X + 40, DEFAULT_CENTER_Y + 40, mAnimationCallback2); + advanceTimeBy(mWaitAnimationDuration); + }); // only the last one callback will return true verify(mAnimationCallback2).onResult(true); @@ -538,9 +550,11 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration, mAnimationCallback); - mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( - targetCenterX, targetCenterY, mAnimationCallback2); - advanceTimeBy(mWaitAnimationDuration); + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( + targetCenterX, targetCenterY, mAnimationCallback2); + advanceTimeBy(mWaitAnimationDuration); + }); // The callback in moveWindowMagnifierToPosition will return true verify(mAnimationCallback2).onResult(true); @@ -556,9 +570,11 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration, mAnimationCallback); - mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( - Float.NaN, Float.NaN, mAnimationCallback2); - advanceTimeBy(mWaitAnimationDuration); + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationAnimationController.moveWindowMagnifierToPosition( + Float.NaN, Float.NaN, mAnimationCallback2); + advanceTimeBy(mWaitAnimationDuration); + }); // The callback in moveWindowMagnifierToPosition will return true verify(mAnimationCallback2).onResult(true); @@ -584,6 +600,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { throws RemoteException { enableWindowMagnificationWithoutAnimation(); + resetMockObjects(); deleteWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback); verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal( @@ -625,16 +642,18 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { mAnimationCallback); Mockito.reset(mSpyController); - mWindowMagnificationAnimationController.deleteWindowMagnification( - mAnimationCallback2); - mCurrentScale.set(mController.getScale()); - mCurrentCenterX.set(mController.getCenterX()); - mCurrentCenterY.set(mController.getCenterY()); - // ValueAnimator.reverse() could not work correctly with the AnimatorTestRule since it is - // using SystemClock in reverse() (b/305731398). Therefore, we call end() on the animator - // directly to verify the result of animation is correct instead of querying the animation - // frame at a specific timing. - mValueAnimator.end(); + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationAnimationController.deleteWindowMagnification( + mAnimationCallback2); + mCurrentScale.set(mController.getScale()); + mCurrentCenterX.set(mController.getCenterX()); + mCurrentCenterY.set(mController.getCenterY()); + // ValueAnimator.reverse() could not work correctly with the AnimatorTestRule since it + // is using SystemClock in reverse() (b/305731398). Therefore, we call end() on the + // animator directly to verify the result of animation is correct instead of querying + // the animation frame at a specific timing. + mValueAnimator.end(); + }); verify(mSpyController).enableWindowMagnificationInternal( mScaleCaptor.capture(), @@ -661,7 +680,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { mAnimationCallback); Mockito.reset(mSpyController); - mWindowMagnificationAnimationController.deleteWindowMagnification(null); + deleteWindowMagnificationWithoutAnimation(); verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN); verify(mAnimationCallback).onResult(false); @@ -673,6 +692,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { deleteWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration, mAnimationCallback); + resetMockObjects(); deleteWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback2); verify(mSpyController).enableWindowMagnificationInternal( @@ -710,8 +730,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { final float offsetY = (float) Math.ceil(offsetX * WindowMagnificationController.HORIZONTAL_LOCK_BASE) + 1.0f; - - mController.moveWindowMagnifier(offsetX, offsetY); + mInstrumentation.runOnMainSync(()-> mController.moveWindowMagnifier(offsetX, offsetY)); verify(mSpyController).moveWindowMagnifier(offsetX, offsetY); verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y + offsetY); @@ -726,8 +745,8 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { final float offsetY = (float) Math.floor(offsetX * WindowMagnificationController.HORIZONTAL_LOCK_BASE) - 1.0f; - - mController.moveWindowMagnifier(offsetX, offsetY); + mInstrumentation.runOnMainSync(() -> + mController.moveWindowMagnifier(offsetX, offsetY)); verify(mSpyController).moveWindowMagnifier(offsetX, offsetY); verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X + offsetX, DEFAULT_CENTER_Y); @@ -742,8 +761,10 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { (float) Math.ceil(offsetX * WindowMagnificationController.HORIZONTAL_LOCK_BASE); // while diagonal scrolling enabled, // should move with both offsetX and offsetY without regrading offsetY/offsetX - mController.setDiagonalScrolling(true); - mController.moveWindowMagnifier(offsetX, offsetY); + mInstrumentation.runOnMainSync(() -> { + mController.setDiagonalScrolling(true); + mController.moveWindowMagnifier(offsetX, offsetY); + }); verify(mSpyController).moveWindowMagnifier(offsetX, offsetY); verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X + offsetX, DEFAULT_CENTER_Y + offsetY); @@ -755,9 +776,11 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { final float targetCenterY = DEFAULT_CENTER_Y + 100; enableWindowMagnificationWithoutAnimation(); - mController.moveWindowMagnifierToPosition(targetCenterX, targetCenterY, - mAnimationCallback); - advanceTimeBy(mWaitAnimationDuration); + mInstrumentation.runOnMainSync(() -> { + mController.moveWindowMagnifierToPosition(targetCenterX, targetCenterY, + mAnimationCallback); + advanceTimeBy(mWaitAnimationDuration); + }); verifyFinalSpec(DEFAULT_SCALE, targetCenterX, targetCenterY); } @@ -774,24 +797,49 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { } private void enableWindowMagnificationWithoutAnimation() { - Mockito.reset(mSpyController); - mWindowMagnificationAnimationController.enableWindowMagnification(DEFAULT_SCALE, - DEFAULT_CENTER_X, DEFAULT_CENTER_Y, null); + enableWindowMagnificationWithoutAnimation( + DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y); + } + + private void enableWindowMagnificationWithoutAnimation( + float targetScale, float targetCenterX, float targetCenterY) { + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationAnimationController.enableWindowMagnification( + targetScale, targetCenterX, targetCenterY, null); + }); } private void enableWindowMagnificationAndWaitAnimating(long duration, @Nullable IRemoteMagnificationAnimationCallback callback) { - Mockito.reset(mSpyController); - mWindowMagnificationAnimationController.enableWindowMagnification(DEFAULT_SCALE, - DEFAULT_CENTER_X, DEFAULT_CENTER_Y, callback); - advanceTimeBy(duration); + enableWindowMagnificationAndWaitAnimating( + duration, DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y, callback); + } + + private void enableWindowMagnificationAndWaitAnimating( + long duration, + float targetScale, + float targetCenterX, + float targetCenterY, + @Nullable IRemoteMagnificationAnimationCallback callback) { + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationAnimationController.enableWindowMagnification( + targetScale, targetCenterX, targetCenterY, callback); + advanceTimeBy(duration); + }); + } + + private void deleteWindowMagnificationWithoutAnimation() { + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationAnimationController.deleteWindowMagnification(null); + }); } private void deleteWindowMagnificationAndWaitAnimating(long duration, @Nullable IRemoteMagnificationAnimationCallback callback) { - resetMockObjects(); - mWindowMagnificationAnimationController.deleteWindowMagnification(callback); - advanceTimeBy(duration); + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationAnimationController.deleteWindowMagnification(callback); + advanceTimeBy(duration); + }); } private void verifyStartValue(ArgumentCaptor<Float> captor, float startValue) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java index 86ae51768219..06421dbbf2bb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java @@ -45,6 +45,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; @@ -53,6 +54,7 @@ import static org.mockito.Mockito.when; import android.animation.ValueAnimator; import android.annotation.IdRes; +import android.annotation.Nullable; import android.app.Instrumentation; import android.content.Context; import android.content.pm.ActivityInfo; @@ -89,6 +91,7 @@ import androidx.test.filters.LargeTest; import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import com.android.systemui.SysuiTestCase; +import com.android.systemui.animation.AnimatorTestRule; import com.android.systemui.model.SysUiState; import com.android.systemui.res.R; import com.android.systemui.settings.FakeDisplayTracker; @@ -102,6 +105,7 @@ import org.junit.After; import org.junit.Assume; import org.junit.Before; import org.junit.Ignore; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Answers; @@ -111,8 +115,6 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @LargeTest @@ -120,12 +122,10 @@ import java.util.concurrent.atomic.AtomicInteger; @RunWith(AndroidTestingRunner.class) public class WindowMagnificationControllerTest extends SysuiTestCase { + @Rule + public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(); + private static final int LAYOUT_CHANGE_TIMEOUT_MS = 5000; - // The duration couldn't too short, otherwise the animation check on bounce effect - // won't work in expectation. (b/299537784) - private static final int BOUNCE_EFFECT_DURATION_MS = 2000; - private static final long ANIMATION_DURATION_MS = 300; - private final long mWaitingAnimationPeriod = 2 * ANIMATION_DURATION_MS; @Mock private SfVsyncFrameCallbackProvider mSfVsyncFrameProvider; @Mock @@ -134,11 +134,16 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { private WindowMagnifierCallback mWindowMagnifierCallback; @Mock IRemoteMagnificationAnimationCallback mAnimationCallback; + @Mock + IRemoteMagnificationAnimationCallback mAnimationCallback2; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction(); @Mock private SecureSettings mSecureSettings; + private long mWaitAnimationDuration; + private long mWaitBounceEffectDuration; + private Handler mHandler; private TestableWindowManager mWindowManager; private SysUiState mSysUiState; @@ -194,6 +199,13 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mResources.getConfiguration().orientation = ORIENTATION_PORTRAIT; } + // Using the animation duration in WindowMagnificationAnimationController for testing. + mWaitAnimationDuration = mResources.getInteger( + com.android.internal.R.integer.config_longAnimTime); + // Using the bounce effect duration in WindowMagnificationController for testing. + mWaitBounceEffectDuration = mResources.getInteger( + com.android.internal.R.integer.config_shortAnimTime); + mWindowMagnificationAnimationController = new WindowMagnificationAnimationController( mContext, mValueAnimator); mWindowMagnificationController = @@ -208,7 +220,6 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mSysUiState, () -> mWindowSessionSpy, mSecureSettings); - mWindowMagnificationController.setBounceEffectDuration(BOUNCE_EFFECT_DURATION_MS); verify(mMirrorWindowControl).setWindowDelegate( any(MirrorWindowControl.MirrorWindowDelegate.class)); @@ -281,9 +292,9 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { /* magnificationFrameOffsetRatioY= */ 0, Mockito.mock(IRemoteMagnificationAnimationCallback.class)); }); + advanceTimeBy(LAYOUT_CHANGE_TIMEOUT_MS); - verify(mSfVsyncFrameProvider, - timeout(LAYOUT_CHANGE_TIMEOUT_MS).atLeast(2)).postFrameCallback(any()); + verify(mSfVsyncFrameProvider, atLeast(2)).postFrameCallback(any()); } @Test @@ -401,14 +412,12 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { @Test public void moveWindowMagnifierToPositionWithAnimation_expectedValuesAndInvokeCallback() - throws InterruptedException { + throws RemoteException { mInstrumentation.runOnMainSync(() -> { mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN, Float.NaN, 0, 0, null); }); - final CountDownLatch countDownLatch = new CountDownLatch(1); - final MockMagnificationAnimationCallback animationCallback = - new MockMagnificationAnimationCallback(countDownLatch); + final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class); verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS)) .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture()); @@ -417,12 +426,12 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mInstrumentation.runOnMainSync(() -> { mWindowMagnificationController.moveWindowMagnifierToPosition( - targetCenterX, targetCenterY, animationCallback); + targetCenterX, targetCenterY, mAnimationCallback); }); + advanceTimeBy(mWaitAnimationDuration); - assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS)); - assertEquals(1, animationCallback.getSuccessCount()); - assertEquals(0, animationCallback.getFailedCount()); + verify(mAnimationCallback, times(1)).onResult(eq(true)); + verify(mAnimationCallback, never()).onResult(eq(false)); verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS)) .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture()); assertEquals(mWindowMagnificationController.getCenterX(), @@ -435,14 +444,12 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { @Test public void moveWindowMagnifierToPositionMultipleTimes_expectedValuesAndInvokeCallback() - throws InterruptedException { + throws RemoteException { mInstrumentation.runOnMainSync(() -> { mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN, Float.NaN, 0, 0, null); }); - final CountDownLatch countDownLatch = new CountDownLatch(4); - final MockMagnificationAnimationCallback animationCallback = - new MockMagnificationAnimationCallback(countDownLatch); + final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class); verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS)) .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture()); @@ -451,20 +458,20 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mInstrumentation.runOnMainSync(() -> { mWindowMagnificationController.moveWindowMagnifierToPosition( - centerX + 10, centerY + 10, animationCallback); + centerX + 10, centerY + 10, mAnimationCallback); mWindowMagnificationController.moveWindowMagnifierToPosition( - centerX + 20, centerY + 20, animationCallback); + centerX + 20, centerY + 20, mAnimationCallback); mWindowMagnificationController.moveWindowMagnifierToPosition( - centerX + 30, centerY + 30, animationCallback); + centerX + 30, centerY + 30, mAnimationCallback); mWindowMagnificationController.moveWindowMagnifierToPosition( - centerX + 40, centerY + 40, animationCallback); + centerX + 40, centerY + 40, mAnimationCallback2); }); + advanceTimeBy(mWaitAnimationDuration); - assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS)); // only the last one callback will return true - assertEquals(1, animationCallback.getSuccessCount()); + verify(mAnimationCallback2).onResult(eq(true)); // the others will return false - assertEquals(3, animationCallback.getFailedCount()); + verify(mAnimationCallback, times(3)).onResult(eq(false)); verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS)) .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture()); assertEquals(mWindowMagnificationController.getCenterX(), @@ -1078,27 +1085,16 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { final View mirrorView = mWindowManager.getAttachedView(); - final long timeout = SystemClock.uptimeMillis() + 5000; final AtomicDouble maxScaleX = new AtomicDouble(); - final Runnable onAnimationFrame = new Runnable() { - @Override - public void run() { - // For some reason the fancy way doesn't compile... -// maxScaleX.getAndAccumulate(mirrorView.getScaleX(), Math::max); - final double oldMax = maxScaleX.get(); - final double newMax = Math.max(mirrorView.getScaleX(), oldMax); - assertTrue(maxScaleX.compareAndSet(oldMax, newMax)); - - if (SystemClock.uptimeMillis() < timeout) { - mirrorView.postOnAnimation(this); - } - } - }; - mirrorView.postOnAnimation(onAnimationFrame); - - waitForIdleSync(); + advanceTimeBy(mWaitBounceEffectDuration, /* runnableOnEachRefresh= */ () -> { + // For some reason the fancy way doesn't compile... + // maxScaleX.getAndAccumulate(mirrorView.getScaleX(), Math::max); + final double oldMax = maxScaleX.get(); + final double newMax = Math.max(mirrorView.getScaleX(), oldMax); + assertTrue(maxScaleX.compareAndSet(oldMax, newMax)); + }); - ReferenceTestUtils.waitForCondition(() -> maxScaleX.get() > 1.0); + assertTrue(maxScaleX.get() > 1.0); } @Test @@ -1455,4 +1451,23 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { return newRotation; } + // advance time based on the device frame refresh rate + private void advanceTimeBy(long timeDelta) { + advanceTimeBy(timeDelta, /* runnableOnEachRefresh= */ null); + } + + // advance time based on the device frame refresh rate, and trigger runnable on each refresh + private void advanceTimeBy(long timeDelta, @Nullable Runnable runnableOnEachRefresh) { + final float frameRate = mContext.getDisplay().getRefreshRate(); + final int timeSlot = (int) (1000 / frameRate); + int round = (int) Math.ceil((double) timeDelta / timeSlot); + for (; round >= 0; round--) { + mInstrumentation.runOnMainSync(() -> { + mAnimatorTestRule.advanceTimeBy(timeSlot); + if (runnableOnEachRefresh != null) { + runnableOnEachRefresh.run(); + } + }); + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java index daa6070b5403..3da72618fb60 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java @@ -19,8 +19,6 @@ package com.android.systemui.accessibility.floatingmenu; import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU; import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR; -import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults; - import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -89,7 +87,6 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase { @Before public void setUp() throws Exception { - setFlagDefaults(mSetFlagsRule); MockitoAnnotations.initMocks(this); mContextWrapper = new ContextWrapper(mContext) { @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java index 56664356f964..fd258e38a00f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java @@ -16,8 +16,6 @@ package com.android.systemui.accessibility.floatingmenu; -import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults; - import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -58,7 +56,6 @@ public class DismissAnimationControllerTest extends SysuiTestCase { @Before public void setUp() throws Exception { - setFlagDefaults(mSetFlagsRule); final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class); final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager, mock(SecureSettings.class)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java index e8329409ff10..2e75480a7391 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java @@ -16,8 +16,6 @@ package com.android.systemui.accessibility.floatingmenu; -import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults; - import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.any; @@ -77,7 +75,6 @@ public class MenuAnimationControllerTest extends SysuiTestCase { @Before public void setUp() throws Exception { - setFlagDefaults(mSetFlagsRule); final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class); final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext, stubWindowManager); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java index e8192c43f6f8..34a2e8719f0b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java @@ -16,8 +16,6 @@ package com.android.systemui.accessibility.floatingmenu; -import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults; - import static com.google.common.truth.Truth.assertThat; import android.content.res.Resources; @@ -45,7 +43,6 @@ public class MenuEduTooltipViewTest extends SysuiTestCase { @Before public void setUp() throws Exception { - setFlagDefaults(mSetFlagsRule); final WindowManager windowManager = mContext.getSystemService(WindowManager.class); mMenuViewAppearance = new MenuViewAppearance(mContext, windowManager); mMenuEduTooltipView = new MenuEduTooltipView(mContext, mMenuViewAppearance); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java index 62cb9a01ac84..4ac18d04cd75 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java @@ -19,8 +19,6 @@ package com.android.systemui.accessibility.floatingmenu; import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS; import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS; -import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults; - import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.doReturn; @@ -74,7 +72,6 @@ public class MenuItemAccessibilityDelegateTest extends SysuiTestCase { @Before public void setUp() { - setFlagDefaults(mSetFlagsRule); final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class); final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext, stubWindowManager); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java index 32487534423d..f0a497d5c9f0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java @@ -18,8 +18,6 @@ package com.android.systemui.accessibility.floatingmenu; import static android.view.View.OVER_SCROLL_NEVER; -import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults; - import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyFloat; @@ -82,7 +80,6 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase { @Before public void setUp() throws Exception { - setFlagDefaults(mSetFlagsRule); final WindowManager windowManager = mContext.getSystemService(WindowManager.class); final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager, mock(SecureSettings.class)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java index 03a4ba78a208..31824ecaa432 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java @@ -19,8 +19,6 @@ package com.android.systemui.accessibility.floatingmenu; import static android.view.WindowInsets.Type.displayCutout; import static android.view.WindowInsets.Type.systemBars; -import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults; - import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.verify; @@ -75,7 +73,6 @@ public class MenuViewLayerControllerTest extends SysuiTestCase { @Before public void setUp() throws Exception { - setFlagDefaults(mSetFlagsRule); final WindowManager wm = mContext.getSystemService(WindowManager.class); doAnswer(invocation -> wm.getMaximumWindowMetrics()).when( mWindowManager).getMaximumWindowMetrics(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java index 76094c1cf185..0f1364d951e7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java @@ -23,7 +23,6 @@ import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsets.Type.systemBars; import static com.android.systemui.accessibility.floatingmenu.MenuViewLayer.LayerIndex; -import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults; import static com.google.common.truth.Truth.assertThat; @@ -122,7 +121,6 @@ public class MenuViewLayerTest extends SysuiTestCase { @Before public void setUp() throws Exception { - setFlagDefaults(mSetFlagsRule); final Rect mDisplayBounds = new Rect(); mDisplayBounds.set(/* left= */ 0, /* top= */ 0, DISPLAY_WINDOW_WIDTH, DISPLAY_WINDOW_HEIGHT); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java index b9fd5d0f5e84..5cd0fd0bb42d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java @@ -18,8 +18,6 @@ package com.android.systemui.accessibility.floatingmenu; import static android.app.UiModeManager.MODE_NIGHT_YES; -import static com.android.systemui.accessibility.utils.FlagUtils.setFlagDefaults; - import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.mock; @@ -69,7 +67,6 @@ public class MenuViewTest extends SysuiTestCase { @Before public void setUp() throws Exception { - setFlagDefaults(mSetFlagsRule); mUiModeManager = mContext.getSystemService(UiModeManager.class); mNightMode = mUiModeManager.getNightMode(); mUiModeManager.setNightMode(MODE_NIGHT_YES); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/FlagUtils.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/FlagUtils.java deleted file mode 100644 index c7bb0f5d3906..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/FlagUtils.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.accessibility.utils; - -import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; - -import android.platform.test.flag.junit.SetFlagsRule; - -import com.android.systemui.Flags; - -public class FlagUtils { - /** - * Populates a setFlagsRule with every SystemUI a11y feature flag. - * This function should be updated when new flags are added. - * - * @param setFlagsRule set flags rule from the test environment. - */ - public static void setFlagDefaults(SetFlagsRule setFlagsRule) { - setFlagDefault(setFlagsRule, Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG); - setFlagDefault(setFlagsRule, Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt index f4d2cfd837f0..f4f05d85540e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt @@ -18,52 +18,7 @@ package com.android.systemui.flags import android.platform.test.flag.junit.SetFlagsRule -/** - * Set the given flag's value to the real value for the current build configuration. This prevents - * test code from crashing because it is reading an unspecified flag value. - * - * REMINDER: You should always test your code with your flag in both configurations, so generally - * you should be explicitly enabling or disabling your flag. This method is for situations where the - * flag needs to be read (e.g. in the class constructor), but its value shouldn't affect the actual - * test cases. In those cases, it's mildly safer to use this method than to hard-code `false` or - * `true` because then at least if you're wrong, and the flag value *does* matter, you'll notice - * when the flag is flipped and tests start failing. - */ -fun SetFlagsRule.setFlagDefault(flagName: String) { - if (getFlagDefault(flagName)) { - enableFlags(flagName) - } else { - disableFlags(flagName) - } -} - -/** - * Set the given flag to an explicit value, or, if null, to the real value for the current build - * configuration. This allows for convenient provisioning in tests where certain tests don't care - * what the value is (`setFlagValue(FLAG_FOO, null)`), and others want an explicit value. - */ -fun SetFlagsRule.setFlagValue(name: String, value: Boolean?) { - when (value) { - null -> setFlagDefault(name) - true -> enableFlags(name) - false -> disableFlags(name) - } -} - -// NOTE: This code uses reflection to gain access to private members of aconfig generated -// classes (in the same way SetFlagsRule does internally) because this is the only way to get -// at the underlying information and read the current value of the flag. -// If aconfig had flag constants with accessible default values, this would be unnecessary. -private fun getFlagDefault(name: String): Boolean { - val flagPackage = name.substringBeforeLast(".") - val featureFlagsImplClass = Class.forName("$flagPackage.FeatureFlagsImpl") - val featureFlagsImpl = featureFlagsImplClass.getConstructor().newInstance() - val flagMethodName = name.substringAfterLast(".").snakeToCamelCase() - val flagGetter = featureFlagsImplClass.getDeclaredMethod(flagMethodName) - return flagGetter.invoke(featureFlagsImpl) as Boolean -} - -private fun String.snakeToCamelCase(): String { - val pattern = "_[a-z]".toRegex() - return replace(pattern) { it.value.last().uppercase() } +/** Set the given flag to an explicit value. */ +fun SetFlagsRule.setFlagValue(name: String, value: Boolean) { + if (value) enableFlags(name) else disableFlags(name) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt index fd1e2c7b19a5..da448aa20289 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt @@ -74,6 +74,15 @@ class MediaProjectionMetricsLoggerTest : SysuiTestCase() { } @Test + fun notifyProjectionCancelled_forwardsToServiceWithMetricsValue() { + val hostUid = 123 + + logger.notifyProjectionRequestCancelled(hostUid) + + verify(service).notifyPermissionRequestCancelled(hostUid) + } + + @Test fun notifyAppSelectorDisplayed_forwardsToService() { val hostUid = 654 diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt index 5255f71b9c09..44798ea99bee 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt @@ -4,7 +4,6 @@ import android.content.ComponentName import android.os.UserHandle import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest -import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED as STATE_APP_SELECTOR_DISPLAYED import com.android.systemui.SysuiTestCase import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger import com.android.systemui.mediaprojection.appselector.data.RecentTask @@ -214,7 +213,7 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { @Test fun init_firstStart_logsAppSelectorDisplayed() { val hostUid = 123456789 - val controller = createController(isFirstStart = true, hostUid) + val controller = createController(isFirstStart = true, hostUid) controller.init() @@ -231,6 +230,15 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { verify(logger, never()).notifyAppSelectorDisplayed(hostUid) } + @Test + fun onSelectorDismissed_logsProjectionRequestCancelled() { + val hostUid = 123 + + createController(hostUid = hostUid).onSelectorDismissed() + + verify(logger).notifyProjectionRequestCancelled(hostUid) + } + private fun givenCaptureAllowed(isAllow: Boolean) { whenever(policyResolver.isScreenCaptureAllowed(any(), any())).thenReturn(isAllow) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java index c439cfe6270f..49049bc51460 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java @@ -16,6 +16,8 @@ package com.android.systemui.screenrecord; +import static android.os.Process.myUid; + import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertFalse; @@ -31,8 +33,8 @@ import android.app.Dialog; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; -import android.os.Looper; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import androidx.test.filters.SmallTest; @@ -60,6 +62,7 @@ import org.mockito.MockitoAnnotations; @SmallTest @RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) /** * Tests for exception handling and bitmap configuration in adding smart actions to Screenshot * Notification. @@ -117,10 +120,6 @@ public class RecordingControllerTest extends SysuiTestCase { // starting, and notifies listeners. @Test public void testCancelCountdown() { - if (Looper.myLooper() == null) { - Looper.prepare(); - } - mController.startCountdown(100, 10, null, null); assertTrue(mController.isStarting()); @@ -137,10 +136,6 @@ public class RecordingControllerTest extends SysuiTestCase { // Test that when recording is started, the start intent is sent and listeners are notified. @Test public void testStartRecording() throws PendingIntent.CanceledException { - if (Looper.myLooper() == null) { - Looper.prepare(); - } - PendingIntent startIntent = Mockito.mock(PendingIntent.class); mController.startCountdown(0, 0, startIntent, null); @@ -151,10 +146,6 @@ public class RecordingControllerTest extends SysuiTestCase { // Test that when recording is stopped, the stop intent is sent and listeners are notified. @Test public void testStopRecording() throws PendingIntent.CanceledException { - if (Looper.myLooper() == null) { - Looper.prepare(); - } - PendingIntent startIntent = Mockito.mock(PendingIntent.class); PendingIntent stopIntent = Mockito.mock(PendingIntent.class); @@ -182,10 +173,6 @@ public class RecordingControllerTest extends SysuiTestCase { // Test that broadcast will update state @Test public void testUpdateStateBroadcast() { - if (Looper.myLooper() == null) { - Looper.prepare(); - } - // When a recording has started PendingIntent startIntent = Mockito.mock(PendingIntent.class); mController.startCountdown(0, 0, startIntent, null); @@ -211,10 +198,6 @@ public class RecordingControllerTest extends SysuiTestCase { // Test that switching users will stop an ongoing recording @Test public void testUserChange() { - if (Looper.myLooper() == null) { - Looper.prepare(); - } - // If we are recording PendingIntent startIntent = Mockito.mock(PendingIntent.class); PendingIntent stopIntent = Mockito.mock(PendingIntent.class); @@ -231,10 +214,6 @@ public class RecordingControllerTest extends SysuiTestCase { @Test public void testPoliciesFlagDisabled_screenCapturingNotAllowed_returnsNullDevicePolicyDialog() { - if (Looper.myLooper() == null) { - Looper.prepare(); - } - mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true); mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, false); when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(true); @@ -247,10 +226,6 @@ public class RecordingControllerTest extends SysuiTestCase { @Test public void testPartialScreenSharingDisabled_returnsLegacyDialog() { - if (Looper.myLooper() == null) { - Looper.prepare(); - } - mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, false); mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, false); @@ -262,10 +237,6 @@ public class RecordingControllerTest extends SysuiTestCase { @Test public void testPoliciesFlagEnabled_screenCapturingNotAllowed_returnsDevicePolicyDialog() { - if (Looper.myLooper() == null) { - Looper.prepare(); - } - mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true); mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true); when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(true); @@ -278,10 +249,6 @@ public class RecordingControllerTest extends SysuiTestCase { @Test public void testPoliciesFlagEnabled_screenCapturingAllowed_returnsNullDevicePolicyDialog() { - if (Looper.myLooper() == null) { - Looper.prepare(); - } - mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true); mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true); when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(false); @@ -294,9 +261,6 @@ public class RecordingControllerTest extends SysuiTestCase { @Test public void testPoliciesFlagEnabled_screenCapturingAllowed_logsProjectionInitiated() { - if (Looper.myLooper() == null) { - Looper.prepare(); - } mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true); mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true); when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(false); @@ -306,7 +270,7 @@ public class RecordingControllerTest extends SysuiTestCase { verify(mMediaProjectionMetricsLogger) .notifyProjectionInitiated( - TEST_USER_ID, + /* hostUid= */ myUid(), SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt index bf12d7deb076..fd381392c3e9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt @@ -26,6 +26,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity import com.android.systemui.mediaprojection.permission.ENTIRE_SCREEN import com.android.systemui.mediaprojection.permission.SINGLE_APP @@ -41,7 +42,6 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito import org.mockito.Mockito.eq import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever @@ -57,6 +57,7 @@ class ScreenRecordPermissionDialogTest : SysuiTestCase() { @Mock private lateinit var userContextProvider: UserContextProvider @Mock private lateinit var flags: FeatureFlags @Mock private lateinit var onStartRecordingClicked: Runnable + @Mock private lateinit var mediaProjectionMetricsLogger: MediaProjectionMetricsLogger private lateinit var dialog: ScreenRecordPermissionDialog @@ -72,7 +73,8 @@ class ScreenRecordPermissionDialogTest : SysuiTestCase() { controller, starter, userContextProvider, - onStartRecordingClicked + onStartRecordingClicked, + mediaProjectionMetricsLogger, ) dialog.onCreate(null) whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true) @@ -149,6 +151,28 @@ class ScreenRecordPermissionDialogTest : SysuiTestCase() { assertThat(dialog.isShowing).isFalse() } + @Test + fun showDialog_cancelClickedMultipleTimes_projectionRequestCancelledIsLoggedOnce() { + dialog.show() + + clickOnCancel() + clickOnCancel() + + verify(mediaProjectionMetricsLogger).notifyProjectionRequestCancelled(TEST_HOST_UID) + } + + @Test + fun dismissDialog_dismissCalledMultipleTimes_projectionRequestCancelledIsLoggedOnce() { + dialog.show() + + TestableLooper.get(this).runWithLooper { + dialog.dismiss() + dialog.dismiss() + } + + verify(mediaProjectionMetricsLogger).notifyProjectionRequestCancelled(TEST_HOST_UID) + } + private fun clickOnCancel() { dialog.requireViewById<View>(android.R.id.button2).performClick() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt index df38f936f4f2..f98267b63986 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt @@ -1,5 +1,6 @@ package com.android.systemui.shade.ui.viewmodel +import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -151,12 +152,14 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { subscriptionId = 1, isOpportunistic = false, carrierName = "Carrier 1", + profileClass = PROFILE_CLASS_UNSET, ) private val SUB_2 = SubscriptionModel( subscriptionId = 2, isOpportunistic = false, carrierName = "Carrier 2", + profileClass = PROFILE_CLASS_UNSET, ) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java index a4c12f6d6b71..2bee7b85eb98 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java @@ -16,8 +16,6 @@ package com.android.systemui.statusbar; -import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; - import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyInt; @@ -43,7 +41,6 @@ import com.android.systemui.plugins.PluginManager; import com.android.systemui.statusbar.NotificationListener.NotificationHandler; import com.android.systemui.statusbar.data.repository.NotificationListenerSettingsRepository; import com.android.systemui.statusbar.domain.interactor.SilentNotificationStatusIconsVisibilityInteractor; -import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; @@ -72,7 +69,6 @@ public class NotificationListenerTest extends SysuiTestCase { @Before public void setUp() { MockitoAnnotations.initMocks(this); - setFlagDefault(mSetFlagsRule, NotificationIconContainerRefactor.FLAG_NAME); mListener = new NotificationListener( mContext, mNotificationManager, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt index e81207e096e3..428574bb15f8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt @@ -63,11 +63,11 @@ class StackCoordinatorTest : SysuiTestCase() { @Before fun setUp() { initMocks(this) - setUp(NotificationIconContainerRefactor.FLAG_NAME to null) entry = NotificationEntryBuilder().setSection(section).build() + setUpWithFlags() } - private fun setUp(vararg flags: Pair<String, Boolean?>) { + private fun setUpWithFlags(vararg flags: Pair<String, Boolean>) { flags.forEach { (name, value) -> mSetFlagsRule.setFlagValue(name, value) } reset(pipeline) coordinator = @@ -84,14 +84,14 @@ class StackCoordinatorTest : SysuiTestCase() { @Test fun testUpdateNotificationIcons() { - setUp(NotificationIconContainerRefactor.FLAG_NAME to false) + setUpWithFlags(NotificationIconContainerRefactor.FLAG_NAME to false) afterRenderListListener.onAfterRenderList(listOf(entry), stackController) verify(notificationIconAreaController).updateNotificationIcons(eq(listOf(entry))) } @Test fun testSetRenderedListOnInteractor() { - setUp(NotificationIconContainerRefactor.FLAG_NAME to true) + setUpWithFlags(NotificationIconContainerRefactor.FLAG_NAME to true) afterRenderListListener.onAfterRenderList(listOf(entry), stackController) verify(renderListInteractor).setRenderedList(eq(listOf(entry))) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt index cbb08946a1b0..947bcfb5011b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt @@ -2,7 +2,6 @@ package com.android.systemui.statusbar.notification.interruption import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE @@ -19,7 +18,30 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidTestingRunner::class) -class NotificationInterruptStateProviderWrapperTest : SysuiTestCase() { +class NotificationInterruptStateProviderWrapperTest : VisualInterruptionDecisionProviderTestBase() { + override val provider: VisualInterruptionDecisionProvider + get() = + NotificationInterruptStateProviderWrapper( + NotificationInterruptStateProviderImpl( + context.contentResolver, + powerManager, + ambientDisplayConfiguration, + batteryController, + statusBarStateController, + keyguardStateController, + headsUpManager, + logger, + mainHandler, + flags, + keyguardNotificationVisibilityProvider, + uiEventLogger, + userTracker, + deviceProvisionedController + ) + .also { it.mUseHeadsUp = true } + ) + + // Tests of internals of the wrapper: @Test fun decisionOfTrue() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt new file mode 100644 index 000000000000..6f4bbd5e21fc --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt @@ -0,0 +1,221 @@ +package com.android.systemui.statusbar.notification.interruption + +import android.app.ActivityManager +import android.app.Notification +import android.app.Notification.BubbleMetadata +import android.app.NotificationChannel +import android.app.NotificationManager.IMPORTANCE_DEFAULT +import android.app.NotificationManager.IMPORTANCE_HIGH +import android.app.NotificationManager.VISIBILITY_NO_OVERRIDE +import android.app.PendingIntent +import android.app.PendingIntent.FLAG_MUTABLE +import android.content.Intent +import android.content.pm.UserInfo +import android.graphics.drawable.Icon +import android.hardware.display.FakeAmbientDisplayConfiguration +import android.os.Handler +import android.os.PowerManager +import com.android.internal.logging.testing.UiEventLoggerFake +import com.android.systemui.SysuiTestCase +import com.android.systemui.res.R +import com.android.systemui.settings.FakeUserTracker +import com.android.systemui.statusbar.FakeStatusBarStateController +import com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking +import com.android.systemui.statusbar.StatusBarState.KEYGUARD +import com.android.systemui.statusbar.StatusBarState.SHADE +import com.android.systemui.statusbar.notification.NotifPipelineFlags +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.statusbar.policy.DeviceProvisionedController +import com.android.systemui.statusbar.policy.HeadsUpManager +import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.mock +import com.android.systemui.utils.leaks.FakeBatteryController +import com.android.systemui.utils.leaks.LeakCheckedTest +import junit.framework.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.mockito.Mockito.`when` as whenever + +abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { + private val leakCheck = LeakCheckedTest.SysuiLeakCheck() + + protected val ambientDisplayConfiguration = FakeAmbientDisplayConfiguration(context) + protected val batteryController = FakeBatteryController(leakCheck) + protected val deviceProvisionedController: DeviceProvisionedController = mock() + protected val flags: NotifPipelineFlags = mock() + protected val headsUpManager: HeadsUpManager = mock() + protected val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider = + mock() + protected val keyguardStateController: KeyguardStateController = mock() + protected val logger: NotificationInterruptLogger = mock() + protected val mainHandler: Handler = mock() + protected val powerManager: PowerManager = mock() + protected val statusBarStateController = FakeStatusBarStateController() + protected val uiEventLogger = UiEventLoggerFake() + protected val userTracker = FakeUserTracker() + + protected abstract val provider: VisualInterruptionDecisionProvider + + @Before + fun setUp() { + val user = UserInfo(ActivityManager.getCurrentUser(), "Current user", /* flags = */ 0) + userTracker.set(listOf(user), /* currentUserIndex = */ 0) + + whenever(headsUpManager.isSnoozed(any())).thenReturn(false) + whenever(keyguardNotificationVisibilityProvider.shouldHideNotification(any())) + .thenReturn(false) + } + + @Test + fun testShouldPeek() { + ensureStateForPeek() + + assertTrue(provider.makeUnloggedHeadsUpDecision(createPeekEntry()).shouldInterrupt) + } + + @Test + fun testShouldPulse() { + ensureStateForPulse() + + assertTrue(provider.makeUnloggedHeadsUpDecision(createPulseEntry()).shouldInterrupt) + } + + @Test + fun testShouldFsi_awake() { + ensureStateForAwakeFsi() + + assertTrue(provider.makeUnloggedFullScreenIntentDecision(createFsiEntry()).shouldInterrupt) + } + + @Test + fun testShouldFsi_dreaming() { + ensureStateForDreamingFsi() + + assertTrue(provider.makeUnloggedFullScreenIntentDecision(createFsiEntry()).shouldInterrupt) + } + + @Test + fun testShouldFsi_keyguard() { + ensureStateForKeyguardFsi() + + assertTrue(provider.makeUnloggedFullScreenIntentDecision(createFsiEntry()).shouldInterrupt) + } + + @Test + fun testShouldBubble() { + assertTrue(provider.makeAndLogBubbleDecision(createBubbleEntry()).shouldInterrupt) + } + + private fun ensureStateForPeek() { + whenever(powerManager.isScreenOn).thenReturn(true) + statusBarStateController.dozing = false + statusBarStateController.dreaming = false + } + + private fun ensureStateForPulse() { + ambientDisplayConfiguration.fakePulseOnNotificationEnabled = true + batteryController.setIsAodPowerSave(false) + statusBarStateController.dozing = true + } + + private fun ensureStateForAwakeFsi() { + whenever(powerManager.isInteractive).thenReturn(false) + statusBarStateController.dreaming = false + statusBarStateController.state = SHADE + } + + private fun ensureStateForDreamingFsi() { + whenever(powerManager.isInteractive).thenReturn(true) + statusBarStateController.dreaming = true + statusBarStateController.state = SHADE + } + + private fun ensureStateForKeyguardFsi() { + whenever(powerManager.isInteractive).thenReturn(true) + statusBarStateController.dreaming = false + statusBarStateController.state = KEYGUARD + } + + private fun createNotif( + hasFsi: Boolean = false, + bubbleMetadata: BubbleMetadata? = null + ): Notification { + return Notification.Builder(context, TEST_CHANNEL_ID) + .apply { + setContentTitle(TEST_CONTENT_TITLE) + setContentText(TEST_CONTENT_TEXT) + + if (hasFsi) { + setFullScreenIntent(mock(), /* highPriority = */ true) + } + + if (bubbleMetadata != null) { + setBubbleMetadata(bubbleMetadata) + } + } + .setContentTitle(TEST_CONTENT_TITLE) + .setContentText(TEST_CONTENT_TEXT) + .build() + } + + private fun createBubbleMetadata(): BubbleMetadata { + val pendingIntent = + PendingIntent.getActivity( + context, + /* requestCode = */ 0, + Intent().setPackage(context.packageName), + FLAG_MUTABLE + ) + + val icon = Icon.createWithResource(context.resources, R.drawable.android) + + return BubbleMetadata.Builder(pendingIntent, icon).build() + } + + private fun createEntry( + notif: Notification, + importance: Int = IMPORTANCE_DEFAULT, + canBubble: Boolean? = null + ): NotificationEntry { + return NotificationEntryBuilder() + .apply { + setPkg(TEST_PACKAGE) + setOpPkg(TEST_PACKAGE) + setTag(TEST_TAG) + setChannel(NotificationChannel(TEST_CHANNEL_ID, TEST_CHANNEL_NAME, importance)) + setNotification(notif) + setImportance(importance) + + if (canBubble != null) { + setCanBubble(canBubble) + } + } + .build() + } + + private fun createPeekEntry() = createEntry(notif = createNotif(), importance = IMPORTANCE_HIGH) + + private fun createPulseEntry() = + createEntry(notif = createNotif(), importance = IMPORTANCE_HIGH).also { + modifyRanking(it).setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build() + } + + private fun createFsiEntry() = + createEntry(notif = createNotif(hasFsi = true), importance = IMPORTANCE_HIGH) + + private fun createBubbleEntry() = + createEntry( + notif = createNotif(bubbleMetadata = createBubbleMetadata()), + importance = IMPORTANCE_HIGH, + canBubble = true + ) +} + +private const val TEST_CONTENT_TITLE = "Test Content Title" +private const val TEST_CONTENT_TEXT = "Test content text" +private const val TEST_CHANNEL_ID = "test_channel" +private const val TEST_CHANNEL_NAME = "Test Channel" +private const val TEST_PACKAGE = "test_package" +private const val TEST_TAG = "test_tag" diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt index ccef1d56c6a0..9b9cb8213c91 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt @@ -61,8 +61,9 @@ class ActivatableNotificationViewTest : SysuiTestCase() { else -> null } as T? } - mNormalColor = - Utils.getColorAttrDefaultColor(mContext, com.android.internal.R.attr.colorSurface) + + mNormalColor = Utils.getColorAttrDefaultColor(mContext, + com.android.internal.R.attr.materialColorSurfaceContainerHigh) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index 8f36d4f5bf6c..033c96ae84b0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -19,8 +19,6 @@ package com.android.systemui.statusbar.notification.stack; import static android.view.View.GONE; import static android.view.WindowInsets.Type.ime; -import static com.android.systemui.Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR; -import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.RUBBER_BAND_FACTOR_NORMAL; @@ -167,11 +165,6 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { mFeatureFlags.setDefault(Flags.NOTIFICATION_SHELF_REFACTOR); mFeatureFlags.setDefault(Flags.NEW_AOD_TRANSITION); mFeatureFlags.setDefault(Flags.UNCLEARED_TRANSIENT_HUN_FIX); - // Some tests in this file test the FooterView. Since we're refactoring the FooterView - // business logic out of the NSSL, the behavior tested in this file will eventually be - // tested directly in the new FooterView stack. For now, we just want to make sure that the - // old behavior is preserved when the flag is off. - setFlagDefault(mSetFlagsRule, FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR); // Inject dependencies before initializing the layout mDependency.injectTestDependency(FeatureFlags.class, mFeatureFlags); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index 6478a3e9894b..05fd6d22f961 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -21,7 +21,6 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; -import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.StatusBarState.SHADE; @@ -164,7 +163,6 @@ import com.android.systemui.statusbar.notification.interruption.KeyguardNotifica import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; -import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment; @@ -337,7 +335,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mFeatureFlags.set(Flags.WM_ENABLE_PREDICTIVE_BACK_SYSUI, false); // Set default value to avoid IllegalStateException. mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, false); - setFlagDefault(mSetFlagsRule, NotificationIconContainerRefactor.FLAG_NAME); // For the Shade to respond to Back gesture, we must enable the event routing mFeatureFlags.set(Flags.WM_SHADE_ALLOW_BACK_GESTURE, true); // For the Shade to animate during the Back gesture, we must enable the animation flag. diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java index 19215e3ca336..53cb8a7eb81b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java @@ -16,8 +16,6 @@ package com.android.systemui.statusbar.phone; -import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; - import static org.junit.Assert.assertFalse; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -54,7 +52,6 @@ import com.android.systemui.statusbar.PulseExpansionHandler; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; -import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.HeadsUpManager; @@ -107,7 +104,6 @@ public class DozeServiceHostTest extends SysuiTestCase { @Before public void setup() { MockitoAnnotations.initMocks(this); - setFlagDefault(mSetFlagsRule, NotificationIconContainerRefactor.FLAG_NAME); mDozeServiceHost = new DozeServiceHost(mDozeLog, mPowerManager, mWakefullnessLifecycle, mStatusBarStateController, mDeviceProvisionedController, mFeatureFlags, mHeadsUpManager, mBatteryController, mScrimController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java index 1fad2a2861a5..c1ef1ad9eef9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java @@ -16,8 +16,6 @@ package com.android.systemui.statusbar.phone; -import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -48,7 +46,6 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationTestHelper; -import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor; import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.policy.Clock; @@ -92,7 +89,6 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase { @Before public void setUp() throws Exception { allowTestableLooperAsMainThread(); - setFlagDefault(mSetFlagsRule, NotificationIconContainerRefactor.FLAG_NAME); mTestHelper = new NotificationTestHelper( mContext, mDependency, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java index 92e40dfe2c46..c24d9adf1dbd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java @@ -15,8 +15,6 @@ */ package com.android.systemui.statusbar.phone; -import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; - import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.verify; @@ -30,7 +28,6 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.SetFlagsRuleExtensionsKt; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationListener; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt index 2b28562c4dd5..62d8f7fa894d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt @@ -20,23 +20,18 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.flags.setFlagDefault import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN -import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor import junit.framework.Assert.assertEquals import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue -import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.mock import org.mockito.Mockito.`when` as whenever -/** - * Tests for {@link NotificationIconContainer}. - */ +/** Tests for {@link NotificationIconContainer}. */ @SmallTest @RunWith(AndroidTestingRunner::class) @RunWithLooper @@ -44,15 +39,12 @@ class NotificationIconContainerTest : SysuiTestCase() { private val iconContainer = NotificationIconContainer(context, /* attrs= */ null) - @Before - fun setup() { - mSetFlagsRule.setFlagDefault(NotificationIconContainerRefactor.FLAG_NAME) - } - @Test fun calculateWidthFor_zeroIcons_widthIsZero() { - assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 0f), - /* actual= */ 0f) + assertEquals( + /* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 0f), + /* actual= */ 0f + ) } @Test @@ -61,8 +53,10 @@ class NotificationIconContainerTest : SysuiTestCase() { iconContainer.setActualPaddingEnd(10f) iconContainer.setIconSize(10) - assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 1f), - /* actual= */ 30f) + assertEquals( + /* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 1f), + /* actual= */ 30f + ) } @Test @@ -71,8 +65,10 @@ class NotificationIconContainerTest : SysuiTestCase() { iconContainer.setActualPaddingEnd(10f) iconContainer.setIconSize(10) - assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 4f), - /* actual= */ 60f) + assertEquals( + /* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 4f), + /* actual= */ 60f + ) } @Test @@ -80,8 +76,10 @@ class NotificationIconContainerTest : SysuiTestCase() { iconContainer.setActualPaddingStart(10f) iconContainer.setActualPaddingEnd(10f) iconContainer.setIconSize(10) - assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 5f), - /* actual= */ 60f) + assertEquals( + /* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 5f), + /* actual= */ 60f + ) } @Test @@ -214,112 +212,121 @@ class NotificationIconContainerTest : SysuiTestCase() { @Test fun shouldForceOverflow_appearingAboveSpeedBump_true() { - val forceOverflow = iconContainer.shouldForceOverflow( + val forceOverflow = + iconContainer.shouldForceOverflow( /* i= */ 1, /* speedBumpIndex= */ 0, /* iconAppearAmount= */ 1f, /* maxVisibleIcons= */ 5 - ) + ) assertTrue(forceOverflow) } @Test fun shouldForceOverflow_moreThanMaxVisible_true() { - val forceOverflow = iconContainer.shouldForceOverflow( + val forceOverflow = + iconContainer.shouldForceOverflow( /* i= */ 10, /* speedBumpIndex= */ 11, /* iconAppearAmount= */ 0f, /* maxVisibleIcons= */ 5 - ) + ) assertTrue(forceOverflow) } @Test fun shouldForceOverflow_belowSpeedBumpAndLessThanMaxVisible_false() { - val forceOverflow = iconContainer.shouldForceOverflow( + val forceOverflow = + iconContainer.shouldForceOverflow( /* i= */ 0, /* speedBumpIndex= */ 11, /* iconAppearAmount= */ 0f, /* maxVisibleIcons= */ 5 - ) + ) assertFalse(forceOverflow) } @Test fun isOverflowing_lastChildXLessThanLayoutEnd_false() { - val isOverflowing = iconContainer.isOverflowing( + val isOverflowing = + iconContainer.isOverflowing( /* isLastChild= */ true, /* translationX= */ 0f, /* layoutEnd= */ 10f, /* iconSize= */ 2f, - ) + ) assertFalse(isOverflowing) } - @Test fun isOverflowing_lastChildXEqualToLayoutEnd_true() { - val isOverflowing = iconContainer.isOverflowing( + val isOverflowing = + iconContainer.isOverflowing( /* isLastChild= */ true, /* translationX= */ 10f, /* layoutEnd= */ 10f, /* iconSize= */ 2f, - ) + ) assertTrue(isOverflowing) } @Test fun isOverflowing_lastChildXGreaterThanDotX_true() { - val isOverflowing = iconContainer.isOverflowing( + val isOverflowing = + iconContainer.isOverflowing( /* isLastChild= */ true, /* translationX= */ 9f, /* layoutEnd= */ 10f, /* iconSize= */ 2f, - ) + ) assertTrue(isOverflowing) } @Test fun isOverflowing_lastChildXGreaterThanLayoutEnd_true() { - val isOverflowing = iconContainer.isOverflowing( + val isOverflowing = + iconContainer.isOverflowing( /* isLastChild= */ true, /* translationX= */ 20f, /* layoutEnd= */ 10f, /* iconSize= */ 2f, - ) + ) assertTrue(isOverflowing) } @Test fun isOverflowing_notLastChildXLessThanDotX_false() { - val isOverflowing = iconContainer.isOverflowing( + val isOverflowing = + iconContainer.isOverflowing( /* isLastChild= */ false, /* translationX= */ 0f, /* layoutEnd= */ 10f, /* iconSize= */ 2f, - ) + ) assertFalse(isOverflowing) } @Test fun isOverflowing_notLastChildXGreaterThanDotX_true() { - val isOverflowing = iconContainer.isOverflowing( + val isOverflowing = + iconContainer.isOverflowing( /* isLastChild= */ false, /* translationX= */ 20f, /* layoutEnd= */ 10f, /* iconSize= */ 2f, - ) + ) assertTrue(isOverflowing) } @Test fun isOverflowing_notLastChildXEqualToDotX_true() { - val isOverflowing = iconContainer.isOverflowing( + val isOverflowing = + iconContainer.isOverflowing( /* isLastChild= */ false, /* translationX= */ 8f, /* layoutEnd= */ 10f, /* iconSize= */ 2f, - ) + ) assertTrue(isOverflowing) } @@ -335,4 +342,4 @@ class NotificationIconContainerTest : SysuiTestCase() { whenever(iconView.notification).thenReturn(sbn) return iconView } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt index 4d4f33b63f3f..9b6940e14415 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository import android.telephony.SubscriptionInfo import android.telephony.SubscriptionManager +import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET import android.telephony.TelephonyManager import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -249,11 +250,13 @@ class MobileRepositorySwitcherTest : SysuiTestCase() { mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) whenever(it.carrierName).thenReturn(SUB_1_NAME) + whenever(it.profileClass).thenReturn(PROFILE_CLASS_UNSET) } private val MODEL_1 = SubscriptionModel( subscriptionId = SUB_1_ID, carrierName = SUB_1_NAME, + profileClass = PROFILE_CLASS_UNSET, ) private const val SUB_2_ID = 2 @@ -262,11 +265,13 @@ class MobileRepositorySwitcherTest : SysuiTestCase() { mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) whenever(it.carrierName).thenReturn(SUB_2_NAME) + whenever(it.profileClass).thenReturn(PROFILE_CLASS_UNSET) } private val MODEL_2 = SubscriptionModel( subscriptionId = SUB_2_ID, carrierName = SUB_2_NAME, + profileClass = PROFILE_CLASS_UNSET, ) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt index 1c21ebe05d84..787a26614909 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod import android.net.ConnectivityManager import android.telephony.ServiceState import android.telephony.SignalStrength +import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET import android.telephony.TelephonyCallback import android.telephony.TelephonyManager import androidx.test.filters.SmallTest @@ -88,6 +89,7 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { SubscriptionModel( subscriptionId = SUB_ID, carrierName = DEFAULT_NAME, + profileClass = PROFILE_CLASS_UNSET, ) ) @@ -737,7 +739,7 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { private companion object { const val SUB_ID = 42 - private val DEFAULT_NAME = "default name" + private const val DEFAULT_NAME = "default name" private val DEFAULT_NAME_MODEL = NetworkNameModel.Default(DEFAULT_NAME) private const val SEP = "-" private const val BUFFER_SEPARATOR = "|" diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt index ba6426586ebd..a90bd48a5bce 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt @@ -32,13 +32,13 @@ import android.telephony.ServiceState import android.telephony.ServiceState.STATE_IN_SERVICE import android.telephony.ServiceState.STATE_OUT_OF_SERVICE import android.telephony.SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX +import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET import android.telephony.TelephonyCallback import android.telephony.TelephonyCallback.DataActivityListener import android.telephony.TelephonyCallback.ServiceStateListener import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE import android.telephony.TelephonyManager -import android.telephony.TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED import android.telephony.TelephonyManager.DATA_ACTIVITY_DORMANT import android.telephony.TelephonyManager.DATA_ACTIVITY_IN import android.telephony.TelephonyManager.DATA_ACTIVITY_INOUT @@ -132,6 +132,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { SubscriptionModel( subscriptionId = SUB_1_ID, carrierName = DEFAULT_NAME, + profileClass = PROFILE_CLASS_UNSET, ) ) @@ -677,6 +678,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { SubscriptionModel( subscriptionId = SUB_1_ID, carrierName = DEFAULT_NAME, + profileClass = PROFILE_CLASS_UNSET, ) assertThat(latest?.name).isEqualTo(DEFAULT_NAME) @@ -686,6 +688,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { SubscriptionModel( subscriptionId = SUB_1_ID, carrierName = updatedName, + profileClass = PROFILE_CLASS_UNSET, ) assertThat(latest?.name).isEqualTo(updatedName) @@ -980,9 +983,9 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { companion object { private const val SUB_1_ID = 1 - private val DEFAULT_NAME = "Fake Mobile Network" + private const val DEFAULT_NAME = "Fake Mobile Network" private val DEFAULT_NAME_MODEL = NetworkNameModel.Default(DEFAULT_NAME) - private val SEP = "-" + private const val SEP = "-" private const val SPN = "testSpn" private const val PLMN = "testPlmn" diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt index 18ba6c4f5d9f..936c58ecaffb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt @@ -31,6 +31,7 @@ import android.telephony.CarrierConfigManager import android.telephony.SubscriptionInfo import android.telephony.SubscriptionManager import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID +import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET import android.telephony.TelephonyCallback import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener import android.telephony.TelephonyManager @@ -1223,12 +1224,14 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { whenever(it.subscriptionId).thenReturn(SUB_1_ID) whenever(it.groupUuid).thenReturn(GROUP_1) whenever(it.carrierName).thenReturn(SUB_1_NAME) + whenever(it.profileClass).thenReturn(PROFILE_CLASS_UNSET) } private val MODEL_1 = SubscriptionModel( subscriptionId = SUB_1_ID, groupUuid = GROUP_1, carrierName = SUB_1_NAME, + profileClass = PROFILE_CLASS_UNSET, ) // Subscription 2 @@ -1240,12 +1243,14 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { whenever(it.subscriptionId).thenReturn(SUB_2_ID) whenever(it.groupUuid).thenReturn(GROUP_2) whenever(it.carrierName).thenReturn(SUB_2_NAME) + whenever(it.profileClass).thenReturn(PROFILE_CLASS_UNSET) } private val MODEL_2 = SubscriptionModel( subscriptionId = SUB_2_ID, groupUuid = GROUP_2, carrierName = SUB_2_NAME, + profileClass = PROFILE_CLASS_UNSET, ) // Subs 3 and 4 are considered to be in the same group ------------------------------------ @@ -1257,6 +1262,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_3_ID_GROUPED) whenever(it.groupUuid).thenReturn(GROUP_ID_3_4) + whenever(it.profileClass).thenReturn(PROFILE_CLASS_UNSET) } // Subscription 4 @@ -1265,6 +1271,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_4_ID_GROUPED) whenever(it.groupUuid).thenReturn(GROUP_ID_3_4) + whenever(it.profileClass).thenReturn(PROFILE_CLASS_UNSET) } // Subs 3 and 4 are considered to be in the same group ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -1279,9 +1286,14 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_CM_ID) whenever(it.carrierName).thenReturn(SUB_CM_NAME) + whenever(it.profileClass).thenReturn(PROFILE_CLASS_UNSET) } private val MODEL_CM = - SubscriptionModel(subscriptionId = SUB_CM_ID, carrierName = SUB_CM_NAME) + SubscriptionModel( + subscriptionId = SUB_CM_ID, + carrierName = SUB_CM_NAME, + profileClass = PROFILE_CLASS_UNSET, + ) private val WIFI_INFO_CM = mock<WifiInfo>().apply { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt index e2f91194cf40..20d5c5de3ffa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor import android.telephony.CellSignalStrength +import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN import androidx.test.filters.SmallTest import com.android.settingslib.mobile.MobileIconCarrierIdOverrides @@ -65,6 +66,7 @@ class MobileIconInteractorTest : SysuiTestCase() { SubscriptionModel( subscriptionId = SUB_1_ID, carrierName = DEFAULT_NAME, + profileClass = PROFILE_CLASS_UNSET, ) ) @@ -649,9 +651,9 @@ class MobileIconInteractorTest : SysuiTestCase() { private const val SUB_1_ID = 1 - private val DEFAULT_NAME = "test default name" + private const val DEFAULT_NAME = "test default name" private val DEFAULT_NAME_MODEL = NetworkNameModel.Default(DEFAULT_NAME) - private val DERIVED_NAME = "test derived name" + private const val DERIVED_NAME = "test derived name" private val DERIVED_NAME_MODEL = NetworkNameModel.IntentDerived(DERIVED_NAME) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt index b4c7578241e8..2060288c28a4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt @@ -17,10 +17,16 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor import android.os.ParcelUuid +import android.telephony.SubscriptionManager import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID +import android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING +import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET import androidx.test.filters.SmallTest import com.android.settingslib.mobile.MobileMappings import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.FakeFeatureFlagsClassic +import com.android.systemui.flags.Flags import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository @@ -57,6 +63,10 @@ class MobileIconsInteractorTest : SysuiTestCase() { private lateinit var connectionsRepository: FakeMobileConnectionsRepository private val userSetupRepository = FakeUserSetupRepository() private val mobileMappingsProxy = FakeMobileMappingsProxy() + private val flags = + FakeFeatureFlagsClassic().apply { + set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, true) + } private val testDispatcher = UnconfinedTestDispatcher() private val testScope = TestScope(testDispatcher) @@ -99,6 +109,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { userSetupRepository, testScope.backgroundScope, context, + flags, ) } @@ -318,6 +329,123 @@ class MobileIconsInteractorTest : SysuiTestCase() { } @Test + fun filteredSubscriptions_doesNotFilterProvisioningWhenFlagIsFalse() = + testScope.runTest { + // GIVEN the flag is false + flags.set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, false) + + // GIVEN 1 sub that is in PROFILE_CLASS_PROVISIONING + val sub1 = + SubscriptionModel( + subscriptionId = SUB_1_ID, + isOpportunistic = false, + carrierName = "Carrier 1", + profileClass = PROFILE_CLASS_PROVISIONING, + ) + + connectionsRepository.setSubscriptions(listOf(sub1)) + + // WHEN filtering is applied + val latest by collectLastValue(underTest.filteredSubscriptions) + + // THEN the provisioning sub is still present (unfiltered) + assertThat(latest).isEqualTo(listOf(sub1)) + } + + @Test + fun filteredSubscriptions_filtersOutProvisioningSubs() = + testScope.runTest { + val sub1 = + SubscriptionModel( + subscriptionId = SUB_1_ID, + isOpportunistic = false, + carrierName = "Carrier 1", + profileClass = PROFILE_CLASS_UNSET, + ) + val sub2 = + SubscriptionModel( + subscriptionId = SUB_2_ID, + isOpportunistic = false, + carrierName = "Carrier 2", + profileClass = SubscriptionManager.PROFILE_CLASS_PROVISIONING, + ) + + connectionsRepository.setSubscriptions(listOf(sub1, sub2)) + + val latest by collectLastValue(underTest.filteredSubscriptions) + + assertThat(latest).isEqualTo(listOf(sub1)) + } + + /** Note: I'm not sure if this will ever be the case, but we can test it at least */ + @Test + fun filteredSubscriptions_filtersOutProvisioningSubsBeforeOpportunistic() = + testScope.runTest { + // This is a contrived test case, where the active subId is the one that would + // also be filtered by opportunistic filtering. + + // GIVEN grouped, opportunistic subscriptions + val groupUuid = ParcelUuid(UUID.randomUUID()) + val sub1 = + SubscriptionModel( + subscriptionId = 1, + isOpportunistic = true, + groupUuid = groupUuid, + carrierName = "Carrier 1", + profileClass = PROFILE_CLASS_PROVISIONING, + ) + + val sub2 = + SubscriptionModel( + subscriptionId = 2, + isOpportunistic = true, + groupUuid = groupUuid, + carrierName = "Carrier 2", + profileClass = PROFILE_CLASS_UNSET, + ) + + // GIVEN active subId is 1 + connectionsRepository.setSubscriptions(listOf(sub1, sub2)) + connectionsRepository.setActiveMobileDataSubscriptionId(1) + + // THEN filtering of provisioning subs takes place first, and we result in sub2 + + val latest by collectLastValue(underTest.filteredSubscriptions) + + assertThat(latest).isEqualTo(listOf(sub2)) + } + + @Test + fun filteredSubscriptions_groupedPairAndNonProvisioned_groupedFilteringStillHappens() = + testScope.runTest { + // Grouped filtering only happens when the list of subs is length 2. In this case + // we'll show that filtering of provisioning subs happens before, and thus grouped + // filtering happens even though the unfiltered list is length 3 + val (sub1, sub3) = + createSubscriptionPair( + subscriptionIds = Pair(SUB_1_ID, SUB_3_ID), + opportunistic = Pair(true, true), + grouped = true, + ) + + val sub2 = + SubscriptionModel( + subscriptionId = 2, + isOpportunistic = true, + groupUuid = null, + carrierName = "Carrier 2", + profileClass = PROFILE_CLASS_PROVISIONING, + ) + + connectionsRepository.setSubscriptions(listOf(sub1, sub2, sub3)) + connectionsRepository.setActiveMobileDataSubscriptionId(1) + + val latest by collectLastValue(underTest.filteredSubscriptions) + + assertThat(latest).isEqualTo(listOf(sub1)) + } + + @Test fun activeDataConnection_turnedOn() = testScope.runTest { CONNECTION_1.setDataEnabled(true) @@ -806,7 +934,8 @@ class MobileIconsInteractorTest : SysuiTestCase() { subscriptionId = subscriptionIds.first, isOpportunistic = opportunistic.first, groupUuid = groupUuid, - carrierName = "Carrier ${subscriptionIds.first}" + carrierName = "Carrier ${subscriptionIds.first}", + profileClass = PROFILE_CLASS_UNSET, ) val sub2 = @@ -814,7 +943,8 @@ class MobileIconsInteractorTest : SysuiTestCase() { subscriptionId = subscriptionIds.second, isOpportunistic = opportunistic.second, groupUuid = groupUuid, - carrierName = "Carrier ${opportunistic.second}" + carrierName = "Carrier ${opportunistic.second}", + profileClass = PROFILE_CLASS_UNSET, ) return Pair(sub1, sub2) @@ -824,12 +954,20 @@ class MobileIconsInteractorTest : SysuiTestCase() { private const val SUB_1_ID = 1 private val SUB_1 = - SubscriptionModel(subscriptionId = SUB_1_ID, carrierName = "Carrier $SUB_1_ID") + SubscriptionModel( + subscriptionId = SUB_1_ID, + carrierName = "Carrier $SUB_1_ID", + profileClass = PROFILE_CLASS_UNSET, + ) private val CONNECTION_1 = FakeMobileConnectionRepository(SUB_1_ID, mock()) private const val SUB_2_ID = 2 private val SUB_2 = - SubscriptionModel(subscriptionId = SUB_2_ID, carrierName = "Carrier $SUB_2_ID") + SubscriptionModel( + subscriptionId = SUB_2_ID, + carrierName = "Carrier $SUB_2_ID", + profileClass = PROFILE_CLASS_UNSET, + ) private val CONNECTION_2 = FakeMobileConnectionRepository(SUB_2_ID, mock()) private const val SUB_3_ID = 3 @@ -838,7 +976,8 @@ class MobileIconsInteractorTest : SysuiTestCase() { subscriptionId = SUB_3_ID, isOpportunistic = true, groupUuid = ParcelUuid(UUID.randomUUID()), - carrierName = "Carrier $SUB_3_ID" + carrierName = "Carrier $SUB_3_ID", + profileClass = PROFILE_CLASS_UNSET, ) private val CONNECTION_3 = FakeMobileConnectionRepository(SUB_3_ID, mock()) @@ -848,7 +987,8 @@ class MobileIconsInteractorTest : SysuiTestCase() { subscriptionId = SUB_4_ID, isOpportunistic = true, groupUuid = ParcelUuid(UUID.randomUUID()), - carrierName = "Carrier $SUB_4_ID" + carrierName = "Carrier $SUB_4_ID", + profileClass = PROFILE_CLASS_UNSET, ) private val CONNECTION_4 = FakeMobileConnectionRepository(SUB_4_ID, mock()) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt index 1d5487f31e55..6a69d1fea993 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt @@ -70,7 +70,11 @@ class LocationBasedMobileIconViewModelTest : SysuiTestCase() { private lateinit var airplaneModeInteractor: AirplaneModeInteractor private val connectivityRepository = FakeConnectivityRepository() - private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) } + private val flags = + FakeFeatureFlagsClassic().also { + it.set(Flags.NEW_NETWORK_SLICE_UI, false) + it.set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, true) + } @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags @Mock private lateinit var constants: ConnectivityConstants @@ -114,6 +118,7 @@ class LocationBasedMobileIconViewModelTest : SysuiTestCase() { FakeUserSetupRepository(), testScope.backgroundScope, context, + flags, ) interactor = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt index c831e62dd709..b39fc5b8fe21 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt @@ -82,7 +82,11 @@ class MobileIconViewModelTest : SysuiTestCase() { @Mock private lateinit var tableLogBuffer: TableLogBuffer @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker - private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) } + private val flags = + FakeFeatureFlagsClassic().also { + it.set(Flags.NEW_NETWORK_SLICE_UI, false) + it.set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, true) + } private val testDispatcher = UnconfinedTestDispatcher() private val testScope = TestScope(testDispatcher) @@ -120,6 +124,7 @@ class MobileIconViewModelTest : SysuiTestCase() { FakeUserSetupRepository(), testScope.backgroundScope, context, + flags, ) interactor = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt index f3e334ed8a22..f029152da0fc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel +import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.mobile.TelephonyIcons @@ -102,6 +103,7 @@ class MobileIconsViewModelTest : SysuiTestCase() { subscriptionId = 1, isOpportunistic = false, carrierName = "Carrier 1", + profileClass = PROFILE_CLASS_UNSET, ), ) assertThat(latest).isEqualTo(listOf(1)) @@ -112,16 +114,19 @@ class MobileIconsViewModelTest : SysuiTestCase() { subscriptionId = 2, isOpportunistic = false, carrierName = "Carrier 2", + profileClass = PROFILE_CLASS_UNSET, ), SubscriptionModel( subscriptionId = 5, isOpportunistic = true, carrierName = "Carrier 5", + profileClass = PROFILE_CLASS_UNSET, ), SubscriptionModel( subscriptionId = 7, isOpportunistic = true, carrierName = "Carrier 7", + profileClass = PROFILE_CLASS_UNSET, ), ) assertThat(latest).isEqualTo(listOf(2, 5, 7)) @@ -335,18 +340,21 @@ class MobileIconsViewModelTest : SysuiTestCase() { subscriptionId = 1, isOpportunistic = false, carrierName = "Carrier 1", + profileClass = PROFILE_CLASS_UNSET, ) private val SUB_2 = SubscriptionModel( subscriptionId = 2, isOpportunistic = false, carrierName = "Carrier 2", + profileClass = PROFILE_CLASS_UNSET, ) private val SUB_3 = SubscriptionModel( subscriptionId = 3, isOpportunistic = false, carrierName = "Carrier 3", + profileClass = PROFILE_CLASS_UNSET, ) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt index c935dbb0ca1c..8405fb43e16a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt @@ -22,6 +22,8 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription import com.android.systemui.common.shared.model.Text import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.FakeFeatureFlagsClassic +import com.android.systemui.flags.Flags import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon import com.android.systemui.res.R @@ -75,6 +77,11 @@ class InternetTileViewModelTest : SysuiTestCase() { private val mobileConnectionRepository = FakeMobileConnectionRepository(SUB_1_ID, tableLogBuffer) + private val flags = + FakeFeatureFlagsClassic().also { + it.set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, true) + } + private val internet = context.getString(R.string.quick_settings_internet_label) @Before @@ -101,6 +108,7 @@ class InternetTileViewModelTest : SysuiTestCase() { userSetupRepo, testScope.backgroundScope, context, + flags, ) underTest = diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java index c4c7472ba39c..7456e00e948d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -26,6 +26,8 @@ import static com.android.systemui.volume.Events.SHOW_REASON_UNKNOWN; import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS; import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNotSame; import static junit.framework.Assert.assertTrue; @@ -40,6 +42,9 @@ import static org.mockito.Mockito.when; import android.app.KeyguardManager; import android.content.res.Configuration; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.os.SystemClock; import android.provider.Settings; @@ -52,6 +57,7 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityManager; +import android.widget.ImageButton; import androidx.test.core.view.MotionEventBuilder; import androidx.test.filters.SmallTest; @@ -90,6 +96,7 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import java.util.Arrays; import java.util.function.Predicate; @SmallTest @@ -757,6 +764,86 @@ public class VolumeDialogImplTest extends SysuiTestCase { foundCaptionLog); } + @Test + public void turnOnDnD_volumeSliderIconChangesToDnd() { + State state = createShellState(); + state.zenMode = Settings.Global.ZEN_MODE_NO_INTERRUPTIONS; + + mDialog.onStateChangedH(state); + mTestableLooper.processAllMessages(); + + boolean foundDnDIcon = findDndIconAmongVolumeRows(); + assertTrue(foundDnDIcon); + } + + @Test + public void turnOffDnD_volumeSliderIconIsNotDnd() { + State state = createShellState(); + state.zenMode = Settings.Global.ZEN_MODE_OFF; + + mDialog.onStateChangedH(state); + mTestableLooper.processAllMessages(); + + boolean foundDnDIcon = findDndIconAmongVolumeRows(); + assertFalse(foundDnDIcon); + } + + /** + * @return true if at least one volume row has the DND icon + */ + private boolean findDndIconAmongVolumeRows() { + ViewGroup volumeDialogRows = mDialog.getDialogView().findViewById(R.id.volume_dialog_rows); + assumeNotNull(volumeDialogRows); + Drawable expected = getContext().getDrawable(com.android.internal.R.drawable.ic_qs_dnd); + boolean foundDnDIcon = false; + final int rowCount = volumeDialogRows.getChildCount(); + // we don't make assumptions about the position of the dnd row + for (int i = 0; i < rowCount && !foundDnDIcon; i++) { + View volumeRow = volumeDialogRows.getChildAt(i); + ImageButton rowIcon = volumeRow.findViewById(R.id.volume_row_icon); + assertNotNull(rowIcon); + + // VolumeDialogImpl changes tint and alpha in a private method, so we clear those here. + rowIcon.setImageTintList(null); + rowIcon.setAlpha(0xFF); + + Drawable actual = rowIcon.getDrawable(); + foundDnDIcon |= areDrawablesEqual(expected, actual); + } + return foundDnDIcon; + } + + private boolean areDrawablesEqual(Drawable drawable1, Drawable drawable2) { + int size = 100; + Bitmap bm1 = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); + Bitmap bm2 = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); + + Canvas canvas1 = new Canvas(bm1); + Canvas canvas2 = new Canvas(bm2); + + drawable1.setBounds(0, 0, size, size); + drawable2.setBounds(0, 0, size, size); + + drawable1.draw(canvas1); + drawable2.draw(canvas2); + + boolean areBitmapsEqual = areBitmapsEqual(bm1, bm2); + bm1.recycle(); + bm2.recycle(); + return areBitmapsEqual; + } + + private boolean areBitmapsEqual(Bitmap a, Bitmap b) { + if (a.getWidth() != b.getWidth() || a.getHeight() != b.getHeight()) return false; + int w = a.getWidth(); + int h = a.getHeight(); + int[] aPix = new int[w * h]; + int[] bPix = new int[w * h]; + a.getPixels(aPix, 0, w, 0, 0, w, h); + b.getPixels(bPix, 0, w, 0, 0, w, h); + return Arrays.equals(aPix, bPix); + } + @After public void teardown() { // Detailed logs to track down timeout issues in b/299491332 diff --git a/packages/SystemUI/tests/utils/src/android/hardware/display/FakeAmbientDisplayConfiguration.kt b/packages/SystemUI/tests/utils/src/android/hardware/display/FakeAmbientDisplayConfiguration.kt new file mode 100644 index 000000000000..cdd0ff7c38f7 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/android/hardware/display/FakeAmbientDisplayConfiguration.kt @@ -0,0 +1,68 @@ +package android.hardware.display + +import android.content.Context + +class FakeAmbientDisplayConfiguration(context: Context) : AmbientDisplayConfiguration(context) { + var fakePulseOnNotificationEnabled = true + + override fun pulseOnNotificationEnabled(user: Int) = fakePulseOnNotificationEnabled + + override fun pulseOnNotificationAvailable() = TODO("Not yet implemented") + + override fun pickupGestureEnabled(user: Int) = TODO("Not yet implemented") + + override fun dozePickupSensorAvailable() = TODO("Not yet implemented") + + override fun tapGestureEnabled(user: Int) = TODO("Not yet implemented") + + override fun tapSensorAvailable() = TODO("Not yet implemented") + + override fun doubleTapGestureEnabled(user: Int) = TODO("Not yet implemented") + + override fun doubleTapSensorAvailable() = TODO("Not yet implemented") + + override fun quickPickupSensorEnabled(user: Int) = TODO("Not yet implemented") + + override fun screenOffUdfpsEnabled(user: Int) = TODO("Not yet implemented") + + override fun wakeScreenGestureAvailable() = TODO("Not yet implemented") + + override fun wakeLockScreenGestureEnabled(user: Int) = TODO("Not yet implemented") + + override fun wakeDisplayGestureEnabled(user: Int) = TODO("Not yet implemented") + + override fun getWakeLockScreenDebounce() = TODO("Not yet implemented") + + override fun doubleTapSensorType() = TODO("Not yet implemented") + + override fun tapSensorTypeMapping() = TODO("Not yet implemented") + + override fun longPressSensorType() = TODO("Not yet implemented") + + override fun udfpsLongPressSensorType() = TODO("Not yet implemented") + + override fun quickPickupSensorType() = TODO("Not yet implemented") + + override fun pulseOnLongPressEnabled(user: Int) = TODO("Not yet implemented") + + override fun alwaysOnEnabled(user: Int) = TODO("Not yet implemented") + + override fun alwaysOnAvailable() = TODO("Not yet implemented") + + override fun alwaysOnAvailableForUser(user: Int) = TODO("Not yet implemented") + + override fun ambientDisplayComponent() = TODO("Not yet implemented") + + override fun accessibilityInversionEnabled(user: Int) = TODO("Not yet implemented") + + override fun ambientDisplayAvailable() = TODO("Not yet implemented") + + override fun dozeSuppressed(user: Int) = TODO("Not yet implemented") + + override fun disableDozeSettings(userId: Int) = TODO("Not yet implemented") + + override fun disableDozeSettings(shouldDisableNonUserConfigurable: Boolean, userId: Int) = + TODO("Not yet implemented") + + override fun restoreDozeSettings(userId: Int) = TODO("Not yet implemented") +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java index e6e6b7bb6e3e..29e737eac99b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java @@ -15,7 +15,7 @@ */ package com.android.systemui; -import static com.android.systemui.Flags.FLAG_EXAMPLE_FLAG; +import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @@ -65,7 +65,7 @@ public abstract class SysuiTestCase { new AndroidXAnimatorIsolationRule(); @Rule - public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT); @Rule public SysuiTestableContext mContext = new SysuiTestableContext( @@ -88,9 +88,6 @@ public abstract class SysuiTestCase { if (isRobolectricTest()) { mContext = mContext.createDefaultDisplayContext(); } - // Set the value of a single gantry flag inside the com.android.systemui package to - // ensure all flags in that package are faked (and thus require a value to be set). - mSetFlagsRule.disableFlags(FLAG_EXAMPLE_FLAG); mSysuiDependency = new SysuiTestDependency(mContext, shouldFailOnLeakedReceiver()); mDependency = mSysuiDependency.install(); mRealInstrumentation = InstrumentationRegistry.getInstrumentation(); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/FakeStatusBarStateController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/FakeStatusBarStateController.kt new file mode 100644 index 000000000000..19fdb6ddad02 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/FakeStatusBarStateController.kt @@ -0,0 +1,159 @@ +package com.android.systemui.statusbar + +import android.view.View +import com.android.systemui.plugins.statusbar.StatusBarStateController + +class FakeStatusBarStateController : SysuiStatusBarStateController { + @JvmField var state = StatusBarState.SHADE + + @JvmField var upcomingState = StatusBarState.SHADE + + @JvmField var lastState = StatusBarState.SHADE + + @JvmField var dozing = false + + @JvmField var expanded = false + + @JvmField var pulsing = false + + @JvmField var dreaming = false + + @JvmField var dozeAmount = 0.0f + + @JvmField var interpolatedDozeAmount = 0.0f + + @JvmField var dozeAmountTarget = 0.0f + + @JvmField var leaveOpen = false + + @JvmField var keyguardRequested = false + + var lastSetDozeAmountView: View? = null + private set + + var lastSetDozeAmountAnimated = false + private set + + var lastSystemBarAppearance = 0 + private set + + var lastSystemBarBehavior = 0 + private set + + var lastSystemBarRequestedVisibleTypes = 0 + private set + + var lastSystemBarPackageName: String? = null + private set + + private val _callbacks = mutableSetOf<StatusBarStateController.StateListener>() + + @JvmField val callbacks: Set<StatusBarStateController.StateListener> = _callbacks + + private var fullscreen = false + + override fun start() {} + + override fun getState() = state + + override fun setState(newState: Int, force: Boolean): Boolean { + val oldState = this.state + newState != oldState || force || return false + + callbacks.forEach { it.onStatePreChange(oldState, newState) } + this.lastState = oldState + this.state = newState + setUpcomingState(newState) + callbacks.forEach { it.onStateChanged(newState) } + callbacks.forEach { it.onStatePostChange() } + return true + } + + override fun getCurrentOrUpcomingState() = upcomingState + + override fun setUpcomingState(upcomingState: Int) { + upcomingState != this.upcomingState || return + this.upcomingState = upcomingState + callbacks.forEach { it.onUpcomingStateChanged(upcomingState) } + } + + override fun isDozing() = dozing + + override fun setIsDozing(dozing: Boolean): Boolean { + dozing != this.dozing || return false + this.dozing = dozing + callbacks.forEach { it.onDozingChanged(dozing) } + return true + } + + override fun isExpanded() = expanded + + fun fakeShadeExpansionFullyChanged(expanded: Boolean) { + expanded != this.expanded || return + this.expanded = expanded + callbacks.forEach { it.onExpandedChanged(expanded) } + } + + override fun isPulsing() = pulsing + + override fun setPulsing(pulsing: Boolean) { + pulsing != this.pulsing || return + this.pulsing = pulsing + callbacks.forEach { it.onPulsingChanged(pulsing) } + } + + override fun isDreaming() = dreaming + + override fun setIsDreaming(drreaming: Boolean): Boolean { + dreaming != this.dreaming || return false + this.dreaming = dreaming + callbacks.forEach { it.onDreamingChanged(dreaming) } + return true + } + + override fun getDozeAmount() = dozeAmount + + override fun setAndInstrumentDozeAmount(view: View?, dozeAmount: Float, animated: Boolean) { + dozeAmountTarget = dozeAmount + lastSetDozeAmountView = view + lastSetDozeAmountAnimated = animated + if (!animated) { + this.dozeAmount = dozeAmount + } + } + + override fun leaveOpenOnKeyguardHide() = leaveOpen + + override fun setLeaveOpenOnKeyguardHide(leaveOpen: Boolean) { + this.leaveOpen = leaveOpen + } + + override fun getInterpolatedDozeAmount() = interpolatedDozeAmount + + fun fakeInterpolatedDozeAmountChanged(interpolatedDozeAmount: Float) { + this.interpolatedDozeAmount = interpolatedDozeAmount + callbacks.forEach { it.onDozeAmountChanged(dozeAmount, interpolatedDozeAmount) } + } + + override fun goingToFullShade() = state == StatusBarState.SHADE && leaveOpen + + override fun fromShadeLocked() = lastState == StatusBarState.SHADE_LOCKED + + override fun isKeyguardRequested(): Boolean = keyguardRequested + + override fun setKeyguardRequested(keyguardRequested: Boolean) { + this.keyguardRequested = keyguardRequested + } + + override fun addCallback(listener: StatusBarStateController.StateListener?) { + _callbacks.add(listener!!) + } + + override fun addCallback(listener: StatusBarStateController.StateListener?, rank: Int) { + throw RuntimeException("addCallback with rank unsupported") + } + + override fun removeCallback(listener: StatusBarStateController.StateListener?) { + _callbacks.remove(listener!!) + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java index eaa109d672f8..209cac6688a2 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java @@ -25,6 +25,7 @@ import java.io.PrintWriter; public class FakeBatteryController extends BaseLeakChecker<BatteryStateChangeCallback> implements BatteryController { + private boolean mIsAodPowerSave = false; private boolean mWirelessCharging; public FakeBatteryController(LeakCheck test) { @@ -63,7 +64,7 @@ public class FakeBatteryController extends BaseLeakChecker<BatteryStateChangeCal @Override public boolean isAodPowerSave() { - return false; + return mIsAodPowerSave; } @Override @@ -71,6 +72,10 @@ public class FakeBatteryController extends BaseLeakChecker<BatteryStateChangeCal return mWirelessCharging; } + public void setIsAodPowerSave(boolean isAodPowerSave) { + mIsAodPowerSave = isAodPowerSave; + } + public void setWirelessCharging(boolean wirelessCharging) { mWirelessCharging = wirelessCharging; } diff --git a/services/core/Android.bp b/services/core/Android.bp index 961e9d3dcfff..3985a0e7f46c 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -155,6 +155,7 @@ java_library_static { "service-permission.stubs.system_server", "service-rkp.stubs.system_server", "service-sdksandbox.stubs.system_server", + "device_policy_aconfig_flags_lib", ], plugins: ["ImmutabilityAnnotationProcessor"], diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 210c18d78c23..e88d0c6baf26 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -15934,7 +15934,7 @@ public class ActivityManagerService extends IActivityManager.Stub try { sdkSandboxInfo = sandboxManagerLocal.getSdkSandboxApplicationInfoForInstrumentation( - sdkSandboxClientAppInfo, isSdkInSandbox); + sdkSandboxClientAppInfo, userId, isSdkInSandbox); } catch (NameNotFoundException e) { reportStartInstrumentationFailureLocked( watcher, className, "Can't find SdkSandbox package"); diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 516293b6fa2e..052b0c2078d1 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -2680,8 +2680,17 @@ public class AppOpsService extends IAppOpsService.Stub { .filterAppAccess(packageName, callingUid, userId); } + /** @deprecated Use {@link #noteProxyOperationWithState} instead. */ @Override public SyncNotedAppOp noteProxyOperation(int code, + AttributionSource attributionSource, boolean shouldCollectAsyncNotedOp, + String message, boolean shouldCollectMessage, boolean skipProxyOperation) { + return mCheckOpsDelegateDispatcher.noteProxyOperation(code, attributionSource, + shouldCollectAsyncNotedOp, message, shouldCollectMessage, skipProxyOperation); + } + + @Override + public SyncNotedAppOp noteProxyOperationWithState(int code, AttributionSourceState attributionSourceState, boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage, boolean skipProxyOperation) { AttributionSource attributionSource = new AttributionSource(attributionSourceState); @@ -3212,8 +3221,21 @@ public class AppOpsService extends IAppOpsService.Stub { attributionChainId); } + /** @deprecated Use {@link #startProxyOperationWithState} instead. */ @Override public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code, + @NonNull AttributionSource attributionSource, boolean startIfModeDefault, + boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage, + boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags, + @AttributionFlags int proxiedAttributionFlags, int attributionChainId) { + return mCheckOpsDelegateDispatcher.startProxyOperation(clientId, code, attributionSource, + startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage, + skipProxyOperation, proxyAttributionFlags, proxiedAttributionFlags, + attributionChainId); + } + + @Override + public SyncNotedAppOp startProxyOperationWithState(@NonNull IBinder clientId, int code, @NonNull AttributionSourceState attributionSourceState, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage, boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags, @@ -3513,8 +3535,16 @@ public class AppOpsService extends IAppOpsService.Stub { finishOperationUnchecked(clientId, code, uid, resolvedPackageName, attributionTag); } + /** @deprecated Use {@link #finishProxyOperationWithState} instead. */ @Override public void finishProxyOperation(@NonNull IBinder clientId, int code, + @NonNull AttributionSource attributionSource, boolean skipProxyOperation) { + mCheckOpsDelegateDispatcher.finishProxyOperation(clientId, code, attributionSource, + skipProxyOperation); + } + + @Override + public void finishProxyOperationWithState(@NonNull IBinder clientId, int code, @NonNull AttributionSourceState attributionSourceState, boolean skipProxyOperation) { AttributionSource attributionSource = new AttributionSource(attributionSourceState); mCheckOpsDelegateDispatcher.finishProxyOperation(clientId, code, attributionSource, diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java index c29d9bd19149..f085647c9676 100644 --- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java +++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java @@ -108,7 +108,7 @@ class PreAuthInfo { final boolean credentialRequested = Utils.isCredentialRequested(promptInfo); final boolean credentialAvailable = trustManager.isDeviceSecure(userId, - context.getAssociatedDisplayId()); + context.getDeviceId()); // Assuming that biometric authenticators are listed in priority-order, the rest of this // function will attempt to find the first authenticator that's as strong or stronger than diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java index 76dde5463534..e3c0cf7365ce 100644 --- a/services/core/java/com/android/server/clipboard/ClipboardService.java +++ b/services/core/java/com/android/server/clipboard/ClipboardService.java @@ -646,7 +646,7 @@ public class ClipboardService extends SystemService { intendingUid, intendingUserId, intendingDeviceId) - || isDeviceLocked(intendingUserId)) { + || isDeviceLocked(intendingUserId, deviceId)) { return null; } synchronized (mLock) { @@ -686,7 +686,7 @@ public class ClipboardService extends SystemService { intendingUserId, intendingDeviceId, false) - || isDeviceLocked(intendingUserId)) { + || isDeviceLocked(intendingUserId, deviceId)) { return null; } synchronized (mLock) { @@ -710,7 +710,7 @@ public class ClipboardService extends SystemService { intendingUserId, intendingDeviceId, false) - || isDeviceLocked(intendingUserId)) { + || isDeviceLocked(intendingUserId, deviceId)) { return false; } synchronized (mLock) { @@ -785,7 +785,7 @@ public class ClipboardService extends SystemService { intendingUserId, intendingDeviceId, false) - || isDeviceLocked(intendingUserId)) { + || isDeviceLocked(intendingUserId, deviceId)) { return false; } synchronized (mLock) { @@ -814,7 +814,7 @@ public class ClipboardService extends SystemService { intendingUserId, intendingDeviceId, false) - || isDeviceLocked(intendingUserId)) { + || isDeviceLocked(intendingUserId, deviceId)) { return null; } synchronized (mLock) { @@ -1150,12 +1150,12 @@ public class ClipboardService extends SystemService { && text.equals(clipboard.primaryClip.getItemAt(0).getText()); } - private boolean isDeviceLocked(@UserIdInt int userId) { + private boolean isDeviceLocked(@UserIdInt int userId, int deviceId) { final long token = Binder.clearCallingIdentity(); try { final KeyguardManager keyguardManager = getContext().getSystemService( KeyguardManager.class); - return keyguardManager != null && keyguardManager.isDeviceLocked(userId); + return keyguardManager != null && keyguardManager.isDeviceLocked(userId, deviceId); } finally { Binder.restoreCallingIdentity(token); } diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index 67a1ccd17835..78077a831622 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -40,6 +40,7 @@ import android.util.Log; import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import com.android.media.flags.Flags; import java.util.List; import java.util.Objects; @@ -358,21 +359,8 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { RoutingSessionInfo newSessionInfo = builder.setProviderId(mUniqueId).build(); - if (mPendingSessionCreationRequest != null) { - SessionCreationRequest sessionCreationRequest; - synchronized (mRequestLock) { - sessionCreationRequest = mPendingSessionCreationRequest; - mPendingSessionCreationRequest = null; - } - if (sessionCreationRequest != null) { - if (TextUtils.equals(mSelectedRouteId, sessionCreationRequest.mRouteId)) { - mCallback.onSessionCreated(this, - sessionCreationRequest.mRequestId, newSessionInfo); - } else { - mCallback.onRequestFailed(this, sessionCreationRequest.mRequestId, - MediaRoute2ProviderService.REASON_UNKNOWN_ERROR); - } - } + synchronized (mRequestLock) { + reportPendingSessionRequestResultLockedIfNeeded(newSessionInfo); } if (Objects.equals(oldSessionInfo, newSessionInfo)) { @@ -395,6 +383,59 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { } } + @GuardedBy("mRequestLock") + private void reportPendingSessionRequestResultLockedIfNeeded( + RoutingSessionInfo newSessionInfo) { + if (mPendingSessionCreationRequest == null) { + // No pending request, nothing to report. + return; + } + + long pendingRequestId = mPendingSessionCreationRequest.mRequestId; + if (TextUtils.equals(mSelectedRouteId, mPendingSessionCreationRequest.mRouteId)) { + if (DEBUG) { + Slog.w( + TAG, + "Session creation success to route " + + mPendingSessionCreationRequest.mRouteId); + } + mPendingSessionCreationRequest = null; + mCallback.onSessionCreated(this, pendingRequestId, newSessionInfo); + } else { + boolean isRequestedRouteConnectedBtRoute = isRequestedRouteConnectedBtRoute(); + if (!Flags.enableWaitingStateForSystemSessionCreationRequest() + || !isRequestedRouteConnectedBtRoute) { + if (DEBUG) { + Slog.w( + TAG, + "Session creation failed to route " + + mPendingSessionCreationRequest.mRouteId); + } + mPendingSessionCreationRequest = null; + mCallback.onRequestFailed( + this, pendingRequestId, MediaRoute2ProviderService.REASON_UNKNOWN_ERROR); + } else if (DEBUG) { + Slog.w( + TAG, + "Session creation waiting state to route " + + mPendingSessionCreationRequest.mRouteId); + } + } + } + + @GuardedBy("mRequestLock") + private boolean isRequestedRouteConnectedBtRoute() { + // Using AllRoutes instead of TransferableRoutes as BT Stack sends an intermediate update + // where two BT routes are active so the transferable routes list is empty. + // See b/307723189 for context + for (MediaRoute2Info btRoute : mBluetoothRouteController.getAllBluetoothRoutes()) { + if (TextUtils.equals(btRoute.getId(), mPendingSessionCreationRequest.mRouteId)) { + return true; + } + } + return false; + } + void publishProviderState() { updateProviderState(); notifyProviderState(); diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java index 58927d14ee23..893ed6119f9f 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java @@ -470,6 +470,11 @@ public final class MediaProjectionManagerService extends SystemService } @VisibleForTesting + void notifyPermissionRequestCancelled(int hostUid) { + mMediaProjectionMetricsLogger.logProjectionPermissionRequestCancelled(hostUid); + } + + @VisibleForTesting void notifyAppSelectorDisplayed(int hostUid) { mMediaProjectionMetricsLogger.logAppSelectorDisplayed(hostUid); } @@ -852,12 +857,12 @@ public final class MediaProjectionManagerService extends SystemService @Override // Binder call @EnforcePermission(MANAGE_MEDIA_PROJECTION) - public void notifyPermissionRequestStateChange(int hostUid, int state, - int sessionCreationSource) { - notifyPermissionRequestStateChange_enforcePermission(); + public void notifyPermissionRequestInitiated(int hostUid, int sessionCreationSource) { + notifyPermissionRequestInitiated_enforcePermission(); final long token = Binder.clearCallingIdentity(); try { - mMediaProjectionMetricsLogger.notifyProjectionStateChange(hostUid, state, sessionCreationSource); + MediaProjectionManagerService.this.notifyPermissionRequestInitiated( + hostUid, sessionCreationSource); } finally { Binder.restoreCallingIdentity(token); } @@ -865,12 +870,11 @@ public final class MediaProjectionManagerService extends SystemService @Override // Binder call @EnforcePermission(MANAGE_MEDIA_PROJECTION) - public void notifyPermissionRequestInitiated(int hostUid, int sessionCreationSource) { - notifyPermissionRequestInitiated_enforcePermission(); + public void notifyPermissionRequestDisplayed(int hostUid) { + notifyPermissionRequestDisplayed_enforcePermission(); final long token = Binder.clearCallingIdentity(); try { - MediaProjectionManagerService.this.notifyPermissionRequestInitiated( - hostUid, sessionCreationSource); + MediaProjectionManagerService.this.notifyPermissionRequestDisplayed(hostUid); } finally { Binder.restoreCallingIdentity(token); } @@ -878,11 +882,11 @@ public final class MediaProjectionManagerService extends SystemService @Override // Binder call @EnforcePermission(MANAGE_MEDIA_PROJECTION) - public void notifyPermissionRequestDisplayed(int hostUid) { - notifyPermissionRequestDisplayed_enforcePermission(); + public void notifyPermissionRequestCancelled(int hostUid) { + notifyPermissionRequestCancelled_enforcePermission(); final long token = Binder.clearCallingIdentity(); try { - MediaProjectionManagerService.this.notifyPermissionRequestDisplayed(hostUid); + MediaProjectionManagerService.this.notifyPermissionRequestCancelled(hostUid); } finally { Binder.restoreCallingIdentity(token); } diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java b/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java index 55a30bf225a3..d7fefeb0b1fe 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java @@ -118,6 +118,23 @@ public class MediaProjectionMetricsLogger { } /** + * Logs that requesting permission for media projection was cancelled by the user. + * + * @param hostUid UID of the package that initiates MediaProjection. + */ + public void logProjectionPermissionRequestCancelled(int hostUid) { + write( + mSessionIdGenerator.getCurrentSessionId(), + FrameworkStatsLog + .MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CANCELLED, + hostUid, + TARGET_UID_UNKNOWN, + TIME_SINCE_LAST_ACTIVE_UNKNOWN, + FrameworkStatsLog + .MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN); + } + + /** * Logs that the app selector dialog is shown for the user. * * @param hostUid UID of the package that initiates MediaProjection. @@ -174,23 +191,6 @@ public class MediaProjectionMetricsLogger { } } - public void notifyProjectionStateChange(int hostUid, int state, int sessionCreationSource) { - write(hostUid, state, sessionCreationSource); - } - - private void write(int hostUid, int state, int sessionCreationSource) { - mFrameworkStatsLogWrapper.write( - /* code */ FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED, - /* session_id */ 123, - /* state */ state, - /* previous_state */ FrameworkStatsLog - .MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN, - /* host_uid */ hostUid, - /* target_uid */ -1, - /* time_since_last_active */ 0, - /* creation_source */ sessionCreationSource); - } - private void write( int sessionId, int state, diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java index 1134714bce55..e57ea0f65804 100644 --- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java +++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java @@ -16,6 +16,8 @@ package com.android.server.os; +import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled; + import android.Manifest; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -52,8 +54,10 @@ import com.android.server.utils.Slogf; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.HashSet; import java.util.Objects; import java.util.OptionalInt; +import java.util.Set; /** * Implementation of the service that provides a privileged API to capture and consume bugreports. @@ -101,10 +105,12 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { private final ArrayMap<Pair<Integer, String>, ArraySet<String>> mBugreportFiles = new ArrayMap<>(); + @GuardedBy("mLock") + private final Set<String> mBugreportFilesToPersist = new HashSet<>(); + /** * Checks that a given file was generated on behalf of the given caller. If the file was - * generated on behalf of the caller, it is removed from the bugreport mapping so that it - * may not be retrieved again. If the file was not generated on behalf of the caller, an + * not generated on behalf of the caller, an * {@link IllegalArgumentException} is thrown. * * @param callingInfo a (uid, package name) pair identifying the caller @@ -114,35 +120,76 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { * @throws IllegalArgumentException if {@code bugreportFile} is not associated with * {@code callingInfo}. */ + @RequiresPermission(value = android.Manifest.permission.INTERACT_ACROSS_USERS, + conditional = true) void ensureCallerPreviouslyGeneratedFile( - Pair<Integer, String> callingInfo, String bugreportFile) { + Context context, Pair<Integer, String> callingInfo, int userId, + String bugreportFile) { synchronized (mLock) { - ArraySet<String> bugreportFilesForCaller = mBugreportFiles.get(callingInfo); - if (bugreportFilesForCaller != null - && bugreportFilesForCaller.contains(bugreportFile)) { - bugreportFilesForCaller.remove(bugreportFile); - if (bugreportFilesForCaller.isEmpty()) { - mBugreportFiles.remove(callingInfo); + if (onboardingBugreportV2Enabled()) { + final int uidForUser = Binder.withCleanCallingIdentity(() -> { + try { + return context.getPackageManager() + .getPackageUidAsUser(callingInfo.second, userId); + } catch (PackageManager.NameNotFoundException exception) { + throwInvalidBugreportFileForCallerException( + bugreportFile, callingInfo.second); + return -1; + } + }); + if (uidForUser != callingInfo.first && context.checkCallingOrSelfPermission( + Manifest.permission.INTERACT_ACROSS_USERS) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException( + callingInfo.second + " does not hold the " + + "INTERACT_ACROSS_USERS permission to access " + + "cross-user bugreports."); + } + ArraySet<String> bugreportFilesForUid = mBugreportFiles.get( + new Pair<>(uidForUser, callingInfo.second)); + if (bugreportFilesForUid == null + || !bugreportFilesForUid.contains(bugreportFile)) { + throwInvalidBugreportFileForCallerException( + bugreportFile, callingInfo.second); } } else { - throw new IllegalArgumentException( - "File " + bugreportFile + " was not generated" - + " on behalf of calling package " + callingInfo.second); + ArraySet<String> bugreportFilesForCaller = mBugreportFiles.get(callingInfo); + if (bugreportFilesForCaller != null + && bugreportFilesForCaller.contains(bugreportFile)) { + bugreportFilesForCaller.remove(bugreportFile); + if (bugreportFilesForCaller.isEmpty()) { + mBugreportFiles.remove(callingInfo); + } + } else { + throwInvalidBugreportFileForCallerException( + bugreportFile, callingInfo.second); + + } } } } + private static void throwInvalidBugreportFileForCallerException( + String bugreportFile, String packageName) { + throw new IllegalArgumentException("File " + bugreportFile + " was not generated on" + + " behalf of calling package " + packageName); + } + /** * Associates a bugreport file with a caller, which is identified as a * (uid, package name) pair. */ - void addBugreportFileForCaller(Pair<Integer, String> caller, String bugreportFile) { + void addBugreportFileForCaller( + Pair<Integer, String> caller, String bugreportFile, boolean keepOnRetrieval) { synchronized (mLock) { if (!mBugreportFiles.containsKey(caller)) { mBugreportFiles.put(caller, new ArraySet<>()); } ArraySet<String> bugreportFilesForCaller = mBugreportFiles.get(caller); bugreportFilesForCaller.add(bugreportFile); + if ((onboardingBugreportV2Enabled()) && keepOnRetrieval) { + mBugreportFilesToPersist.add(bugreportFile); + } } } } @@ -246,16 +293,17 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { } @Override - @RequiresPermission(Manifest.permission.DUMP) - public void retrieveBugreport(int callingUidUnused, String callingPackage, - FileDescriptor bugreportFd, String bugreportFile, IDumpstateListener listener) { + @RequiresPermission(value = Manifest.permission.DUMP, conditional = true) + public void retrieveBugreport(int callingUidUnused, String callingPackage, int userId, + FileDescriptor bugreportFd, String bugreportFile, + boolean keepBugreportOnRetrievalUnused, IDumpstateListener listener) { int callingUid = Binder.getCallingUid(); enforcePermission(callingPackage, callingUid, false); Slogf.i(TAG, "Retrieving bugreport for %s / %d", callingPackage, callingUid); try { mBugreportFileManager.ensureCallerPreviouslyGeneratedFile( - new Pair<>(callingUid, callingPackage), bugreportFile); + mContext, new Pair<>(callingUid, callingPackage), userId, bugreportFile); } catch (IllegalArgumentException e) { Slog.e(TAG, e.getMessage()); reportError(listener, IDumpstateListener.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE); @@ -281,10 +329,17 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { // Wrap the listener so we can intercept binder events directly. DumpstateListener myListener = new DumpstateListener(listener, ds, new Pair<>(callingUid, callingPackage), /* reportFinishedFile= */ true); + + boolean keepBugreportOnRetrieval = false; + if (onboardingBugreportV2Enabled()) { + keepBugreportOnRetrieval = mBugreportFileManager.mBugreportFilesToPersist.contains( + bugreportFile); + } + setCurrentDumpstateListenerLocked(myListener); try { - ds.retrieveBugreport(callingUid, callingPackage, bugreportFd, - bugreportFile, myListener); + ds.retrieveBugreport(callingUid, callingPackage, userId, bugreportFd, + bugreportFile, keepBugreportOnRetrieval, myListener); } catch (RemoteException e) { Slog.e(TAG, "RemoteException in retrieveBugreport", e); } @@ -317,7 +372,8 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { private void validateBugreportFlags(int flags) { flags = clearBugreportFlag(flags, BugreportParams.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA - | BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT); + | BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT + | BugreportParams.BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL); if (flags != 0) { Slog.w(TAG, "Unknown bugreport flags: " + flags); throw new IllegalArgumentException("Unknown bugreport flags: " + flags); @@ -482,6 +538,9 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { boolean reportFinishedFile = (bugreportFlags & BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT) != 0; + boolean keepBugreportOnRetrieval = + (bugreportFlags & BugreportParams.BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL) != 0; + IDumpstate ds = startAndGetDumpstateBinderServiceLocked(); if (ds == null) { Slog.w(TAG, "Unable to get bugreport service"); @@ -490,7 +549,8 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { } DumpstateListener myListener = new DumpstateListener(listener, ds, - new Pair<>(callingUid, callingPackage), reportFinishedFile); + new Pair<>(callingUid, callingPackage), reportFinishedFile, + keepBugreportOnRetrieval); setCurrentDumpstateListenerLocked(myListener); try { ds.startBugreport(callingUid, callingPackage, bugreportFd, screenshotFd, bugreportMode, @@ -646,9 +706,16 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { private final boolean mReportFinishedFile; private int mProgress; // used for debugging purposes only private boolean mDone; + private boolean mKeepBugreportOnRetrieval; DumpstateListener(IDumpstateListener listener, IDumpstate ds, Pair<Integer, String> caller, boolean reportFinishedFile) { + this(listener, ds, caller, reportFinishedFile, /* keepBugreportOnRetrieval= */ false); + } + + DumpstateListener(IDumpstateListener listener, IDumpstate ds, + Pair<Integer, String> caller, boolean reportFinishedFile, + boolean keepBugreportOnRetrieval) { if (DEBUG) { Slogf.d(TAG, "Starting DumpstateListener(id=%d) for caller %s", mId, caller); } @@ -656,6 +723,7 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { mDs = ds; mCaller = caller; mReportFinishedFile = reportFinishedFile; + mKeepBugreportOnRetrieval = keepBugreportOnRetrieval; try { mDs.asBinder().linkToDeath(this, 0); } catch (RemoteException e) { @@ -690,7 +758,8 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { reportFinishedLocked("File: " + bugreportFile); } if (mReportFinishedFile) { - mBugreportFileManager.addBugreportFileForCaller(mCaller, bugreportFile); + mBugreportFileManager.addBugreportFileForCaller( + mCaller, bugreportFile, mKeepBugreportOnRetrieval); } else if (DEBUG) { Slog.d(TAG, "Not reporting finished file"); } diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 7c32cde6c1d8..2951ef630eb3 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -2374,12 +2374,6 @@ final class InstallPackageHelper { permissionParamsBuilder.setAutoRevokePermissionsMode(autoRevokePermissionsMode); mPm.mPermissionManager.onPackageInstalled(pkg, installRequest.getPreviousAppId(), permissionParamsBuilder.build(), userId); - // Apply restricted settings on potentially dangerous packages. - if (installRequest.getPackageSource() == PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE - || installRequest.getPackageSource() - == PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE) { - enableRestrictedSettings(pkgName, pkg.getUid()); - } } installRequest.setName(pkgName); installRequest.setAppId(pkg.getUid()); @@ -2394,16 +2388,13 @@ final class InstallPackageHelper { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } - private void enableRestrictedSettings(String pkgName, int appId) { + private void enableRestrictedSettings(String pkgName, int appId, int userId) { final AppOpsManager appOpsManager = mPm.mContext.getSystemService(AppOpsManager.class); - final int[] allUsersList = mPm.mUserManager.getUserIds(); - for (int userId : allUsersList) { - final int uid = UserHandle.getUid(userId, appId); - appOpsManager.setMode(AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, - uid, - pkgName, - AppOpsManager.MODE_ERRORED); - } + final int uid = UserHandle.getUid(userId, appId); + appOpsManager.setMode(AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, + uid, + pkgName, + AppOpsManager.MODE_ERRORED); } /** @@ -2820,13 +2811,10 @@ final class InstallPackageHelper { mPm.mRequiredInstallerPackage, /* packageSender= */ mPm, launchedForRestore, killApp, update, archived); - // Work that needs to happen on first install within each user - if (firstUserIds.length > 0) { - for (int userId : firstUserIds) { - mPm.restorePermissionsAndUpdateRolesForNewUserInstall(packageName, - userId); - } + for (int userId : firstUserIds) { + mPm.restorePermissionsAndUpdateRolesForNewUserInstall(packageName, + userId); } if (request.isAllNewUsers() && !update) { @@ -2835,6 +2823,16 @@ final class InstallPackageHelper { mPm.notifyPackageChanged(packageName, request.getAppId()); } + for (int userId : firstUserIds) { + // Apply restricted settings on potentially dangerous packages. Needs to happen + // after appOpsManager is notified of the new package + if (request.getPackageSource() == PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE + || request.getPackageSource() + == PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE) { + enableRestrictedSettings(packageName, request.getAppId(), userId); + } + } + // Log current value of "unknown sources" setting EventLog.writeEvent(EventLogTags.UNKNOWN_SOURCES_ENABLED, getUnknownSourcesSettings()); diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index c260be9a5124..8b82a1c6e7c9 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -506,6 +506,9 @@ public class LauncherAppsService extends SystemService { if (!canAccessProfile(userId, "cannot get shouldHideFromSuggestions")) { return false; } + if (Flags.archiving() && packageName != null && isPackageArchived(packageName, user)) { + return true; + } if (mPackageManagerInternal.filterAppAccess( packageName, Binder.getCallingUid(), userId)) { return false; @@ -537,7 +540,7 @@ public class LauncherAppsService extends SystemService { == 0) { return launcherActivities; } - if (launcherActivities == null || launcherActivities.getList().isEmpty()) { + if (launcherActivities == null) { // Cannot access profile, so we don't even return any hidden apps. return null; } @@ -758,6 +761,10 @@ public class LauncherAppsService extends SystemService { } } + private boolean isPackageArchived(@NonNull String packageName, UserHandle user) { + return !getApplicationInfoForArchivedApp(packageName, user).isEmpty(); + } + @NonNull private List<LauncherActivityInfoInternal> generateLauncherActivitiesForArchivedApp( @Nullable String packageName, UserHandle user) { @@ -969,11 +976,17 @@ public class LauncherAppsService extends SystemService { final int callingUid = injectBinderCallingUid(); final long ident = Binder.clearCallingIdentity(); try { - final PackageInfo info = mPackageManagerInternal.getPackageInfo(packageName, + long callingFlag = PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, - callingUid, user.getIdentifier()); - return info != null && info.applicationInfo.enabled; + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE; + if (Flags.archiving()) { + callingFlag |= PackageManager.MATCH_ARCHIVED_PACKAGES; + } + final PackageInfo info = + mPackageManagerInternal.getPackageInfo( + packageName, callingFlag, callingUid, user.getIdentifier()); + return info != null + && (info.applicationInfo.enabled || info.applicationInfo.isArchived); } finally { Binder.restoreCallingIdentity(ident); } @@ -1439,7 +1452,18 @@ public class LauncherAppsService extends SystemService { if (!canAccessProfile(user.getIdentifier(), "Cannot check component")) { return false; } - + if (Flags.archiving() && component != null && component.getPackageName() != null) { + List<LauncherActivityInfoInternal> archiveActivities = + generateLauncherActivitiesForArchivedApp(component.getPackageName(), user); + if (!archiveActivities.isEmpty()) { + for (int i = 0; i < archiveActivities.size(); i++) { + if (archiveActivities.get(i).getComponentName().equals(component)) { + return true; + } + } + return false; + } + } final int callingUid = injectBinderCallingUid(); final int state = mPackageManagerInternal.getComponentEnabledSetting(component, callingUid, user.getIdentifier()); diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java index 6a2ddc8f94b0..ea082cf77987 100644 --- a/services/core/java/com/android/server/pm/OtaDexoptService.java +++ b/services/core/java/com/android/server/pm/OtaDexoptService.java @@ -159,6 +159,9 @@ public class OtaDexoptService extends IOtaDexopt.Stub { if (pkgSetting.getPkg().isCoreApp()) { throw new IllegalStateException("Found a core app that's not important"); } + // Use REASON_FIRST_BOOT to query "pm.dexopt.first-boot" for the compiler filter, but + // the reason itself won't make it into the actual compiler reason because it will be + // overridden in otapreopt.cpp. mDexoptCommands.addAll(generatePackageDexopts(pkgSetting.getPkg(), pkgSetting, PackageManagerService.REASON_FIRST_BOOT)); } diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index b9b590833f4a..305e3536ad25 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -44,6 +44,7 @@ import android.content.IntentSender; import android.content.IntentSender.SendIntentException; import android.content.pm.ApplicationInfo; import android.content.pm.ArchivedPackageParcel; +import android.content.pm.Flags; import android.content.pm.IPackageInstaller; import android.content.pm.IPackageInstallerCallback; import android.content.pm.IPackageInstallerSession; @@ -745,6 +746,22 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements params.installFlags &= ~PackageManager.INSTALL_DISABLE_VERIFICATION; } + if (Flags.rollbackLifetime()) { + if (params.rollbackLifetimeMillis > 0) { + if ((params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) == 0) { + throw new IllegalArgumentException( + "Can't set rollbackLifetimeMillis when rollback is not enabled"); + } + if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_ROLLBACKS) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException( + "Setting rollback lifetime requires the MANAGE_ROLLBACKS permission"); + } + } else if (params.rollbackLifetimeMillis < 0) { + throw new IllegalArgumentException("rollbackLifetimeMillis 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 5dc7dab73141..1be28ca9e915 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -1282,6 +1282,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { info.whitelistedRestrictedPermissions = params.whitelistedRestrictedPermissions; info.autoRevokePermissionsMode = params.autoRevokePermissionsMode; info.installFlags = params.installFlags; + info.rollbackLifetimeMillis = params.rollbackLifetimeMillis; info.isMultiPackage = params.isMultiPackage; info.isStaged = params.isStaged; info.rollbackDataPolicy = params.rollbackDataPolicy; diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 978d8e4a0e8a..3430bb4dd7c2 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -2138,6 +2138,19 @@ public class UserManagerService extends IUserManager.Stub { } @Override + public boolean isForegroundUserAdmin() { + // No permission requirements for this API. + synchronized (mUsersLock) { + final int currentUserId = getCurrentUserId(); + if (currentUserId != UserHandle.USER_NULL) { + final UserInfo userInfo = getUserInfoLU(currentUserId); + return userInfo != null && userInfo.isAdmin(); + } + } + return false; + } + + @Override public @NonNull String getUserName() { final int callingUid = Binder.getCallingUid(); if (!hasQueryOrCreateUsersPermission() diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java index 85d93f4f76c4..a5b90f12a5cc 100644 --- a/services/core/java/com/android/server/rollback/Rollback.java +++ b/services/core/java/com/android/server/rollback/Rollback.java @@ -174,6 +174,11 @@ class Rollback { @Nullable private final String mInstallerPackageName; /** + * Time after which rollback expires. + */ + private long mRollbackLifetimeMillis = 0; + + /** * Session ids for all packages in the install. For multi-package sessions, this is the list * of child session ids. For normal sessions, this list is a single element with the normal * session id. @@ -286,6 +291,24 @@ class Rollback { } /** + * Sets rollback lifetime in milliseconds, for purposes of expiring rollback data. + */ + @WorkerThread + void setRollbackLifetimeMillis(long lifetimeMillis) { + assertInWorkerThread(); + mRollbackLifetimeMillis = lifetimeMillis; + } + + /** + * Returns rollback lifetime in milliseconds, for purposes of expiring rollback data. + */ + @WorkerThread + long getRollbackLifetimeMillis() { + assertInWorkerThread(); + return mRollbackLifetimeMillis; + } + + /** * Returns the session ID associated with this rollback, or {@code -1} if unknown. */ @AnyThread @@ -930,6 +953,7 @@ class Rollback { ipw.println("-state: " + getStateAsString()); ipw.println("-stateDescription: " + mStateDescription); ipw.println("-timestamp: " + getTimestamp()); + ipw.println("-rollbackLifetimeMillis: " + getRollbackLifetimeMillis()); ipw.println("-isStaged: " + isStaged()); ipw.println("-originalSessionId: " + getOriginalSessionId()); ipw.println("-packages:"); diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index 720c773a032a..8d934089524c 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -28,6 +28,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; import android.content.pm.ApplicationInfo; +import android.content.pm.Flags; import android.content.pm.ModuleInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; @@ -702,6 +703,15 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba // Schedules future expiration as appropriate. @WorkerThread private void runExpiration() { + if (Flags.rollbackLifetime()) { + runExpirationCustomRollbackLifetime(); + } else { + runExpirationDefaultRollbackLifetime(); + } + } + + @WorkerThread + private void runExpirationDefaultRollbackLifetime() { getHandler().removeCallbacks(mRunExpiration); assertInWorkerThread(); Instant now = Instant.now(); @@ -729,6 +739,44 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba } } + @WorkerThread + private void runExpirationCustomRollbackLifetime() { + getHandler().removeCallbacks(mRunExpiration); + assertInWorkerThread(); + Instant now = Instant.now(); + long minDelay = 0; + Iterator<Rollback> iter = mRollbacks.iterator(); + while (iter.hasNext()) { + Rollback rollback = iter.next(); + if (!rollback.isAvailable() && !rollback.isCommitted()) { + continue; + } + long rollbackLifetimeMillis = rollback.getRollbackLifetimeMillis(); + if (rollbackLifetimeMillis <= 0) { + rollbackLifetimeMillis = mRollbackLifetimeDurationInMillis; + } + + Instant rollbackExpiryTimestamp = rollback.getTimestamp() + .plusMillis(rollbackLifetimeMillis); + if (!now.isBefore(rollbackExpiryTimestamp)) { + Slog.i(TAG, "runExpiration id=" + rollback.info.getRollbackId()); + iter.remove(); + deleteRollback(rollback, "Expired by timeout"); + continue; + } + + long delay = now.until( + rollbackExpiryTimestamp, ChronoUnit.MILLIS); + if (minDelay == 0 || delay < minDelay) { + minDelay = delay; + } + } + + if (minDelay != 0) { + getHandler().postDelayed(mRunExpiration, minDelay); + } + } + @AnyThread private Handler getHandler() { return mHandler; @@ -1277,6 +1325,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba } final Rollback rollback; + if (parentSession.isStaged()) { rollback = mRollbackStore.createStagedRollback(rollbackId, parentSessionId, userId, installerPackageName, packageSessionIds, getExtensionVersions()); @@ -1285,6 +1334,11 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba installerPackageName, packageSessionIds, getExtensionVersions()); } + if (Flags.rollbackLifetime()) { + rollback.setRollbackLifetimeMillis(parentSession.rollbackLifetimeMillis); + } + + mRollbacks.add(rollback); return rollback; } diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java index 8068c6f46733..0af137faf5b4 100644 --- a/services/core/java/com/android/server/rollback/RollbackStore.java +++ b/services/core/java/com/android/server/rollback/RollbackStore.java @@ -19,6 +19,7 @@ package com.android.server.rollback; import static com.android.server.rollback.Rollback.rollbackStateFromString; import android.annotation.NonNull; +import android.content.pm.Flags; import android.content.pm.PackageManager; import android.content.pm.VersionedPackage; import android.content.rollback.PackageRollbackInfo; @@ -312,6 +313,9 @@ class RollbackStore { JSONObject dataJson = new JSONObject(); dataJson.put("info", rollbackInfoToJson(rollback.info)); dataJson.put("timestamp", rollback.getTimestamp().toString()); + if (Flags.rollbackLifetime()) { + dataJson.put("rollbackLifetimeMillis", rollback.getRollbackLifetimeMillis()); + } dataJson.put("originalSessionId", rollback.getOriginalSessionId()); dataJson.put("state", rollback.getStateAsString()); dataJson.put("stateDescription", rollback.getStateDescription()); @@ -375,7 +379,7 @@ class RollbackStore { @VisibleForTesting static Rollback rollbackFromJson(JSONObject dataJson, File backupDir) throws JSONException, ParseException { - return new Rollback( + Rollback rollback = new Rollback( rollbackInfoFromJson(dataJson.getJSONObject("info")), backupDir, Instant.parse(dataJson.getString("timestamp")), @@ -388,6 +392,10 @@ class RollbackStore { dataJson.optInt("userId", UserHandle.SYSTEM.getIdentifier()), dataJson.optString("installerPackageName", ""), extensionVersionsFromJson(dataJson.optJSONArray("extensionVersions"))); + if (Flags.rollbackLifetime()) { + rollback.setRollbackLifetimeMillis(dataJson.optLong("rollbackLifetimeMillis")); + } + return rollback; } private static JSONObject toJson(VersionedPackage pkg) throws JSONException { diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index 635e11be3a16..f82f08bfc851 100644 --- a/services/core/java/com/android/server/trust/TrustManagerService.java +++ b/services/core/java/com/android/server/trust/TrustManagerService.java @@ -70,7 +70,6 @@ import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.Xml; -import android.view.Display; import android.view.IWindowManager; import android.view.WindowManagerGlobal; @@ -79,7 +78,6 @@ import com.android.internal.content.PackageMonitor; import com.android.internal.infra.AndroidFuture; import com.android.internal.util.DumpUtils; import com.android.internal.widget.LockPatternUtils; -import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.companion.virtual.VirtualDeviceManagerInternal; @@ -1523,57 +1521,13 @@ public class TrustManagerService extends SystemService { mHandler.obtainMessage(MSG_UNREGISTER_LISTENER, trustListener).sendToTarget(); } - /** - * @param uid: uid of the calling app (obtained via getCallingUid()) - * @param displayId: the id of a Display - * @return Returns true if both of the following conditions hold - - * 1) the uid belongs to an app instead of a system core component; and - * 2) either the uid is running on a virtual device or the displayId - * is owned by a virtual device - */ - private boolean isAppOrDisplayOnAnyVirtualDevice(int uid, int displayId) { - if (UserHandle.isCore(uid)) { - return false; - } - - if (mVirtualDeviceManager == null) { - mVirtualDeviceManager = LocalServices.getService( - VirtualDeviceManagerInternal.class); - if (mVirtualDeviceManager == null) { - // VirtualDeviceManager service may not have been published - return false; - } - } - - switch (displayId) { - case Display.INVALID_DISPLAY: - // There is no Display object associated with the Context of the calling app. - if (mVirtualDeviceManager.isAppRunningOnAnyVirtualDevice(uid)) { - return true; - } - break; - case Display.DEFAULT_DISPLAY: - // The DEFAULT_DISPLAY is by definition not virtual. - break; - default: - // Other display IDs can belong to logical displays created for other purposes. - if (mVirtualDeviceManager.isDisplayOwnedByAnyVirtualDevice(displayId)) { - return true; - } - break; - } - return false; - } - @Override - public boolean isDeviceLocked(int userId, int displayId) throws RemoteException { - int uid = getCallingUid(); - if (isAppOrDisplayOnAnyVirtualDevice(uid, displayId)) { - // Virtual displays are considered insecure because they may be used for streaming - // to other devices. + public boolean isDeviceLocked(int userId, int deviceId) throws RemoteException { + if (deviceId != Context.DEVICE_ID_DEFAULT) { + // Virtual devices are considered insecure. return false; } - userId = ActivityManager.handleIncomingUser(getCallingPid(), uid, userId, + userId = ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId, false /* allowAll */, true /* requireFull */, "isDeviceLocked", null); final long token = Binder.clearCallingIdentity(); @@ -1588,15 +1542,12 @@ public class TrustManagerService extends SystemService { } @Override - public boolean isDeviceSecure(int userId, int displayId) throws RemoteException { - int uid = getCallingUid(); - if (isAppOrDisplayOnAnyVirtualDevice(uid, displayId)) { - // Virtual displays are considered insecure because they may be used for streaming - // to other devices. + public boolean isDeviceSecure(int userId, int deviceId) throws RemoteException { + if (deviceId != Context.DEVICE_ID_DEFAULT) { + // Virtual devices are considered insecure. return false; } - - userId = ActivityManager.handleIncomingUser(getCallingPid(), uid, userId, + userId = ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId, false /* allowAll */, true /* requireFull */, "isDeviceSecure", null); final long token = Binder.clearCallingIdentity(); diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index e7893da60847..f31490060fc8 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -4716,6 +4716,17 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp scheduleAnimation(); mWmService.mH.post(() -> InputMethodManagerInternal.get().onImeParentChanged()); + } else if (mImeControlTarget != null && mImeControlTarget == mImeLayeringTarget) { + // Even if the IME surface parent is not changed, the layer target belonging to the + // parent may have changes. Then attempt to reassign if the IME control target is + // possible to be the relative layer. + final SurfaceControl lastRelativeLayer = mImeWindowsContainer.getLastRelativeLayer(); + if (lastRelativeLayer != mImeLayeringTarget.mSurfaceControl) { + assignRelativeLayerForIme(getSyncTransaction(), false /* forceUpdate */); + if (lastRelativeLayer != mImeWindowsContainer.getLastRelativeLayer()) { + scheduleAnimation(); + } + } } } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index d2f6d1698f1a..6cad16c7db40 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -486,8 +486,6 @@ class Task extends TaskFragment { private boolean mForceShowForAllUsers; - private boolean mForceTranslucent = false; - // The display category name for this task. String mRequiredDisplayCategory; @@ -3643,7 +3641,7 @@ class Task extends TaskFragment { */ TaskFragmentParentInfo getTaskFragmentParentInfo() { return new TaskFragmentParentInfo(getConfiguration(), getDisplayId(), - shouldBeVisible(null /* starting */)); + shouldBeVisible(null /* starting */), hasNonFinishingDirectActivity()); } @Override @@ -4502,10 +4500,6 @@ class Task extends TaskFragment { return true; } - void setForceTranslucent(boolean set) { - mForceTranslucent = set; - } - @Override public boolean isAlwaysOnTop() { return !isForceHidden() && super.isAlwaysOnTop(); @@ -4523,11 +4517,6 @@ class Task extends TaskFragment { } @Override - protected boolean isForceTranslucent() { - return mForceTranslucent; - } - - @Override long getProtoFieldId() { return TASK; } @@ -6062,6 +6051,11 @@ class Task extends TaskFragment { // Non-root task position changed. mRootWindowContainer.invalidateTaskLayers(); } + + if (child.asActivityRecord() != null) { + // Send for TaskFragmentParentInfo#hasDirectActivity change. + sendTaskFragmentParentInfoChangedIfNeeded(); + } } void reparent(TaskDisplayArea newParent, boolean onTop) { diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 82d34246f857..00f2b8963350 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -374,6 +374,8 @@ class TaskFragment extends WindowContainer<WindowContainer> { @interface FlagForceHidden {} protected int mForceHiddenFlags = 0; + private boolean mForceTranslucent = false; + final Point mLastSurfaceSize = new Point(); private final Rect mTmpBounds = new Rect(); @@ -843,8 +845,12 @@ class TaskFragment extends WindowContainer<WindowContainer> { return true; } - protected boolean isForceTranslucent() { - return false; + boolean isForceTranslucent() { + return mForceTranslucent; + } + + void setForceTranslucent(boolean set) { + mForceTranslucent = set; } boolean isLeafTaskFragment() { @@ -1049,6 +1055,10 @@ class TaskFragment extends WindowContainer<WindowContainer> { return getActivity(ActivityRecord::canBeTopRunning); } + /** + * Reports non-finishing activity count including this TaskFragment's child embedded + * TaskFragments' children activities. + */ int getNonFinishingActivityCount() { final int[] runningActivityCount = new int[1]; forAllActivities(a -> { @@ -1059,6 +1069,20 @@ class TaskFragment extends WindowContainer<WindowContainer> { return runningActivityCount[0]; } + /** + * Returns {@code true} if there's any non-finishing direct children activity, which is not + * embedded in TaskFragments + */ + boolean hasNonFinishingDirectActivity() { + for (int i = getChildCount() - 1; i >= 0; --i) { + final ActivityRecord activity = getChildAt(i).asActivityRecord(); + if (activity != null && !activity.finishing) { + return true; + } + } + return false; + } + boolean isTopActivityFocusable() { final ActivityRecord r = topRunningActivity(); return r != null ? r.isFocusable() diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 4a0f44b58ecf..8f884d2fe29e 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -2706,6 +2706,10 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< return mLastLayer; } + SurfaceControl getLastRelativeLayer() { + return mLastRelativeToLayer; + } + protected void setRelativeLayer(Transaction t, SurfaceControl relativeTo, int layer) { if (mSurfaceFreezer.hasLeash()) { // When the freezer has created animation leash parent for the window, set the layer diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index eb60aaba510f..a8b9417edb9b 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -37,6 +37,7 @@ import static android.window.TaskFragmentOperation.OP_TYPE_SET_RELATIVE_BOUNDS; import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT; import static android.window.TaskFragmentOperation.OP_TYPE_UNKNOWN; import static android.window.WindowContainerTransaction.Change.CHANGE_FOCUSABLE; +import static android.window.WindowContainerTransaction.Change.CHANGE_FORCE_TRANSLUCENT; import static android.window.WindowContainerTransaction.Change.CHANGE_HIDDEN; import static android.window.WindowContainerTransaction.Change.CHANGE_RELATIVE_BOUNDS; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER; @@ -768,8 +769,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } } - if ((c.getChangeMask() - & WindowContainerTransaction.Change.CHANGE_FORCE_TRANSLUCENT) != 0) { + if ((c.getChangeMask() & CHANGE_FORCE_TRANSLUCENT) != 0) { tr.setForceTranslucent(c.getForceTranslucent()); effects = TRANSACT_EFFECTS_LIFECYCLE; } @@ -877,6 +877,11 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub effects |= TRANSACT_EFFECTS_LIFECYCLE; } } + if ((c.getChangeMask() & CHANGE_FORCE_TRANSLUCENT) != 0) { + taskFragment.setForceTranslucent(c.getForceTranslucent()); + effects = TRANSACT_EFFECTS_LIFECYCLE; + } + effects |= applyChanges(taskFragment, c); if (taskFragment.shouldStartChangeTransition(mTmpBounds0, mTmpBounds1)) { @@ -2022,9 +2027,11 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub if (mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) { // System organizer is allowed to update the hidden and focusable state. - // We unset the CHANGE_HIDDEN and CHANGE_FOCUSABLE bits because they are checked here. + // We unset the CHANGE_HIDDEN, CHANGE_FOCUSABLE, and CHANGE_FORCE_TRANSLUCENT bits + // because they are checked here. changeMaskToBeChecked &= ~CHANGE_HIDDEN; changeMaskToBeChecked &= ~CHANGE_FOCUSABLE; + changeMaskToBeChecked &= ~CHANGE_FORCE_TRANSLUCENT; } // setRelativeBounds is allowed. diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java index 4e6dd064165d..ece3dfeabafa 100644 --- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java @@ -674,6 +674,17 @@ public class MediaProjectionManagerServiceTest { } @Test + public void notifyPermissionRequestCancelled_forwardsToLogger() { + int hostUid = 123; + mService = + new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector); + + mService.notifyPermissionRequestCancelled(hostUid); + + verify(mMediaProjectionMetricsLogger).logProjectionPermissionRequestCancelled(hostUid); + } + + @Test public void notifyAppSelectorDisplayed_forwardsToLogger() { int hostUid = 456; mService = diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionMetricsLoggerTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionMetricsLoggerTest.java index 410604f02e1f..ad1cd6eca5ac 100644 --- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionMetricsLoggerTest.java +++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionMetricsLoggerTest.java @@ -18,6 +18,7 @@ package com.android.server.media.projection; import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN; import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED; +import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CANCELLED; import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS; import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED; import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_PERMISSION_REQUEST_DISPLAYED; @@ -452,6 +453,63 @@ public class MediaProjectionMetricsLoggerTest { MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED); } + @Test + public void logProjectionPermissionRequestCancelled_logsStateChangedAtomId() { + mLogger.logProjectionPermissionRequestCancelled(TEST_HOST_UID); + + verifyStateChangedAtomIdLogged(); + } + + @Test + public void logProjectionPermissionRequestCancelled_logsExistingSessionId() { + int existingSessionId = 456; + when(mSessionIdGenerator.getCurrentSessionId()).thenReturn(existingSessionId); + + mLogger.logProjectionPermissionRequestCancelled(TEST_HOST_UID); + + verifySessionIdLogged(existingSessionId); + } + + @Test + public void logProjectionPermissionRequestCancelled_logsStateCancelled() { + mLogger.logProjectionPermissionRequestCancelled(TEST_HOST_UID); + + verifyStateLogged(MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CANCELLED); + } + + @Test + public void logProjectionPermissionRequestCancelled_logsPreviousState() { + mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE); + verifyPreviousStateLogged( + MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN); + + mLogger.logProjectionPermissionRequestCancelled(TEST_HOST_UID); + verifyPreviousStateLogged( + MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED); + } + + @Test + public void logProjectionPermissionRequestCancelled_logsHostUid() { + mLogger.logProjectionPermissionRequestCancelled(TEST_HOST_UID); + + verifyHostUidLogged(TEST_HOST_UID); + } + + @Test + public void logProjectionPermissionRequestCancelled_logsUnknownTargetUid() { + mLogger.logProjectionPermissionRequestCancelled(TEST_HOST_UID); + + verifyTargetUidLogged(-2); + } + + @Test + public void logProjectionPermissionRequestCancelled_logsUnknownCreationSource() { + mLogger.logProjectionPermissionRequestCancelled(TEST_HOST_UID); + + verifyCreationSourceLogged( + MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN); + } + private void verifyStateChangedAtomIdLogged() { verify(mFrameworkStatsLogWrapper) .write( diff --git a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java index fc27edcb7bda..a4d50f0f9559 100644 --- a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java +++ b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java @@ -94,27 +94,31 @@ public class BugreportManagerServiceImplTest { public void testBugreportFileManagerFileExists() { Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage); mBugreportFileManager.addBugreportFileForCaller( - callingInfo, mBugreportFile); + callingInfo, mBugreportFile, /* keepOnRetrieval= */ false); assertThrows(IllegalArgumentException.class, () -> mBugreportFileManager.ensureCallerPreviouslyGeneratedFile( - callingInfo, "unknown-file.zip")); + mContext, callingInfo, Process.myUserHandle().getIdentifier(), + "unknown-file.zip")); // No exception should be thrown. - mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(callingInfo, mBugreportFile); + mBugreportFileManager.ensureCallerPreviouslyGeneratedFile( + mContext, callingInfo, mContext.getUserId(), mBugreportFile); } @Test public void testBugreportFileManagerMultipleFiles() { Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage); mBugreportFileManager.addBugreportFileForCaller( - callingInfo, mBugreportFile); + callingInfo, mBugreportFile, /* keepOnRetrieval= */ false); mBugreportFileManager.addBugreportFileForCaller( - callingInfo, mBugreportFile2); + callingInfo, mBugreportFile2, /* keepOnRetrieval= */ false); // No exception should be thrown. - mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(callingInfo, mBugreportFile); - mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(callingInfo, mBugreportFile2); + mBugreportFileManager.ensureCallerPreviouslyGeneratedFile( + mContext, callingInfo, mContext.getUserId(), mBugreportFile); + mBugreportFileManager.ensureCallerPreviouslyGeneratedFile( + mContext, callingInfo, mContext.getUserId(), mBugreportFile2); } @Test @@ -122,7 +126,8 @@ public class BugreportManagerServiceImplTest { Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage); assertThrows(IllegalArgumentException.class, () -> mBugreportFileManager.ensureCallerPreviouslyGeneratedFile( - callingInfo, "test-file.zip")); + mContext, callingInfo, Process.myUserHandle().getIdentifier(), + "test-file.zip")); } @Test @@ -130,7 +135,8 @@ public class BugreportManagerServiceImplTest { CountDownLatch latch = new CountDownLatch(1); Listener listener = new Listener(latch); mService.retrieveBugreport(Binder.getCallingUid(), mContext.getPackageName(), - new FileDescriptor(), mBugreportFile, listener); + mContext.getUserId(), new FileDescriptor(), mBugreportFile, + /* keepOnRetrieval= */ false, listener); assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue(); assertThat(listener.getErrorCode()).isEqualTo( BugreportCallback.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index 6235b3b67145..c57b05130e77 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -1742,6 +1742,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { private void testApplyTransaction_reorder_failsIfNotSystemOrganizer_common( @TaskFragmentOperation.OperationType int opType) { final Task task = createTask(mDisplayContent); + doNothing().when(task).sendTaskFragmentParentInfoChangedIfNeeded(); // Create a non-embedded Activity at the bottom. final ActivityRecord bottomActivity = new ActivityBuilder(mAtm) .setTask(task) @@ -1934,7 +1935,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { /** Setups the mock Task as the parent of the given TaskFragment. */ private static void setupMockParent(TaskFragment taskFragment, Task mockParent) { doReturn(mockParent).when(taskFragment).getTask(); - doReturn(new TaskFragmentParentInfo(new Configuration(), DEFAULT_DISPLAY, true)) + doReturn(new TaskFragmentParentInfo(new Configuration(), DEFAULT_DISPLAY, true, true)) .when(mockParent).getTaskFragmentParentInfo(); // Task needs to be visible diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java index 0c580697bc4a..435a8357dabb 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -1568,6 +1568,7 @@ public class TaskTests extends WindowTestsBase { final TaskFragment fragment = createTaskFragmentWithEmbeddedActivity(task, organizer); final ActivityRecord embeddedActivity = fragment.getTopMostActivity(); + doNothing().when(task).sendTaskFragmentParentInfoChangedIfNeeded(); task.moveActivityToFront(activity); assertEquals("Activity must be moved to front", activity, task.getTopMostActivity()); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index cd3fef6b6fdb..699580a6536d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -101,6 +101,7 @@ import org.mockito.ArgumentCaptor; import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.function.BiConsumer; /** * Test class for {@link ITaskOrganizer} and {@link android.window.ITaskOrganizerController}. @@ -583,7 +584,7 @@ public class WindowOrganizerTests extends WindowTestsBase { } @Test - public void testTaskFragmentHiddenAndFocusableChanges() { + public void testTaskFragmentHiddenFocusableTranslucentChanges() { removeGlobalMinSizeRestriction(); final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true) .setWindowingMode(WINDOWING_MODE_FULLSCREEN).build(); @@ -605,10 +606,12 @@ public class WindowOrganizerTests extends WindowTestsBase { assertTrue(taskFragment.shouldBeVisible(null)); assertTrue(taskFragment.isFocusable()); assertTrue(taskFragment.isTopActivityFocusable()); + assertFalse(taskFragment.isForceTranslucent()); // Apply transaction to the TaskFragment hidden and not focusable. t.setHidden(taskFragment.mRemoteToken.toWindowContainerToken(), true); t.setFocusable(taskFragment.mRemoteToken.toWindowContainerToken(), false); + t.setForceTranslucent(taskFragment.mRemoteToken.toWindowContainerToken(), true); mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked( t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE, false /* shouldApplyIndependently */); @@ -617,10 +620,12 @@ public class WindowOrganizerTests extends WindowTestsBase { assertFalse(taskFragment.shouldBeVisible(null)); assertFalse(taskFragment.isFocusable()); assertFalse(taskFragment.isTopActivityFocusable()); + assertTrue(taskFragment.isForceTranslucent()); // Apply transaction to the TaskFragment not hidden and focusable. t.setHidden(taskFragment.mRemoteToken.toWindowContainerToken(), false); t.setFocusable(taskFragment.mRemoteToken.toWindowContainerToken(), true); + t.setForceTranslucent(taskFragment.mRemoteToken.toWindowContainerToken(), false); mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked( t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE, false /* shouldApplyIndependently */); @@ -629,10 +634,32 @@ public class WindowOrganizerTests extends WindowTestsBase { assertTrue(taskFragment.shouldBeVisible(null)); assertTrue(taskFragment.isFocusable()); assertTrue(taskFragment.isTopActivityFocusable()); + assertFalse(taskFragment.isForceTranslucent()); } @Test - public void testTaskFragmentHiddenAndFocusableChanges_throwsWhenNotSystemOrganizer() { + public void testTaskFragmentChangeHidden_throwsWhenNotSystemOrganizer() { + // Non-system organizers are not allow to update the hidden state. + testTaskFragmentChangesWithoutSystemOrganizerThrowException( + (t, windowContainerToken) -> t.setHidden(windowContainerToken, true)); + } + + @Test + public void testTaskFragmentChangeFocusable_throwsWhenNotSystemOrganizer() { + // Non-system organizers are not allow to update the focusable state. + testTaskFragmentChangesWithoutSystemOrganizerThrowException( + (t, windowContainerToken) -> t.setFocusable(windowContainerToken, false)); + } + + @Test + public void testTaskFragmentChangeTranslucent_throwsWhenNotSystemOrganizer() { + // Non-system organizers are not allow to update the translucent state. + testTaskFragmentChangesWithoutSystemOrganizerThrowException( + (t, windowContainerToken) -> t.setForceTranslucent(windowContainerToken, true)); + } + + private void testTaskFragmentChangesWithoutSystemOrganizerThrowException( + BiConsumer<WindowContainerTransaction, WindowContainerToken> addOp) { removeGlobalMinSizeRestriction(); final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true) .setWindowingMode(WINDOWING_MODE_FULLSCREEN).build(); @@ -641,21 +668,15 @@ public class WindowOrganizerTests extends WindowTestsBase { final TaskFragmentOrganizer organizer = createTaskFragmentOrganizer(t, false /* isSystemOrganizer */); - final IBinder token = new Binder(); final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) .setParentTask(rootTask) - .setFragmentToken(token) + .setFragmentToken(new Binder()) .setOrganizer(organizer) .createActivityCount(1) .build(); - assertTrue(rootTask.shouldBeVisible(null)); - assertTrue(taskFragment.shouldBeVisible(null)); - - t.setHidden(taskFragment.mRemoteToken.toWindowContainerToken(), true); - t.setFocusable(taskFragment.mRemoteToken.toWindowContainerToken(), false); + addOp.accept(t, taskFragment.mRemoteToken.toWindowContainerToken()); - // Non-system organizers are not allow to update the hidden and focusable states. assertThrows(SecurityException.class, () -> mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked( t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE, diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java index 3ec6f425a37a..973ab846fa4a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java @@ -448,7 +448,6 @@ public class ZOrderingTests extends WindowTestsBase { mDisplayContent.updateImeParent(); // Ime should on top of the popup IME layering target window. - mDisplayContent.assignChildLayers(mTransaction); assertWindowHigher(mImeWindow, popupImeTargetWin); } diff --git a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java index a1a39ff173b4..359ef83cfe7c 100644 --- a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java +++ b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java @@ -125,6 +125,17 @@ public class VcnGatewayConnectionConfigTest { TUNNEL_CONNECTION_PARAMS); } + private static VcnGatewayConnectionConfig.Builder newBuilderMinimal() { + final VcnGatewayConnectionConfig.Builder builder = + new VcnGatewayConnectionConfig.Builder( + "newBuilderMinimal", TUNNEL_CONNECTION_PARAMS); + for (int caps : EXPOSED_CAPS) { + builder.addExposedCapability(caps); + } + + return builder; + } + private static VcnGatewayConnectionConfig buildTestConfigWithExposedCapsAndOptions( VcnGatewayConnectionConfig.Builder builder, Set<Integer> gatewayOptions, @@ -273,6 +284,7 @@ public class VcnGatewayConnectionConfigTest { assertArrayEquals(RETRY_INTERVALS_MS, config.getRetryIntervalsMillis()); assertEquals(MAX_MTU, config.getMaxMtu()); + assertTrue(config.isSafeModeEnabled()); assertFalse( config.hasGatewayOption( @@ -290,6 +302,13 @@ public class VcnGatewayConnectionConfigTest { } @Test + public void testBuilderAndGettersSafeModeDisabled() { + final VcnGatewayConnectionConfig config = newBuilderMinimal().enableSafeMode(false).build(); + + assertFalse(config.isSafeModeEnabled()); + } + + @Test public void testPersistableBundle() { final VcnGatewayConnectionConfig config = buildTestConfig(); @@ -305,6 +324,13 @@ public class VcnGatewayConnectionConfigTest { } @Test + public void testPersistableBundleSafeModeDisabled() { + final VcnGatewayConnectionConfig config = newBuilderMinimal().enableSafeMode(false).build(); + + assertEquals(config, new VcnGatewayConnectionConfig(config.toPersistableBundle())); + } + + @Test public void testParsePersistableBundleWithoutVcnUnderlyingNetworkTemplates() { PersistableBundle configBundle = buildTestConfig().toPersistableBundle(); configBundle.putPersistableBundle(UNDERLYING_NETWORK_TEMPLATES_KEY, null); @@ -411,4 +437,18 @@ public class VcnGatewayConnectionConfigTest { assertEquals(config, configEqual); assertNotEquals(config, configNotEqual); } + + @Test + public void testSafeModeEnableDisableEquality() throws Exception { + final VcnGatewayConnectionConfig config = newBuilderMinimal().build(); + final VcnGatewayConnectionConfig configEqual = newBuilderMinimal().build(); + + assertEquals(config.isSafeModeEnabled(), configEqual.isSafeModeEnabled()); + + final VcnGatewayConnectionConfig configNotEqual = + newBuilderMinimal().enableSafeMode(false).build(); + + assertEquals(config, configEqual); + assertNotEquals(config, configNotEqual); + } } |