diff --git a/Android.bp b/Android.bp index 986a07108b91712251e46973f71a5c66e85abd5f..b5f7e99c88230abbbae63dbb03cda596af600a2a 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 cce83292b2c9d5affdf5eadf538b5d981f76c342..18001e832afdbfcac0098debbe5a7b59bca689e2 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/test-current.txt b/core/api/test-current.txt index e130206d49fa0a13d015c8bbff368222760b2ea3..83b68808b62c21ea6c77489645444cf24613621c 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/KeyguardManager.java b/core/java/android/app/KeyguardManager.java index 2d554031ab48ccf3ae847c190629e010cb09bf42..545ba8e81f624ffb35de4bedfca55238ded204e5 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/trust/ITrustManager.aidl b/core/java/android/app/trust/ITrustManager.aidl index a5e5135f2cb00dff44a61f15c418a39e4b48f3ad..70c25ea3ab1c4667586d08449f254ee450e8b839 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 aefa55f30826fa9932d8766080af4013531c25c5..323592c43760674a5e41025c2737325732d7377f 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/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig index 3ec239c7125e8293e09c3f41500693076747d9a4..43cf97fd209092efd314f038fffb9ff1c827d7a8 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 a40fb154c256814376edcba611245a9140080b42..66e3c28c19518ac261cf872e1e72ac45026413b3 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(); } @@ -482,6 +490,17 @@ public final class VcnGatewayConnectionConfig { return mMinUdpPort4500NatTimeoutSeconds; } + /** + * 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. * @@ -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<>(); @@ -788,6 +811,28 @@ public final class VcnGatewayConnectionConfig { return this; } + /** + * 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. * @@ -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 58def6ef796122fd064e3aeb5385ab2734778a84..960e84d671e7139f1dd159e0a3aca11f3e8cbeb2 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 e8ad303c42084eccf1c35eab674ab1fd0a91a6cf..8510084c309db2f541336326ecdab018b9593209 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 839c56f85b7e0a578f2d868c4599881dcf69db88..330b992ef3082f1f1e06433f21aa9f52a0dbcb1c 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 72bc2113f93f37d954ff73ffe7b4ea15963af92a..cc79ae307f600da9a4c9e0a093a28730a375712f 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, @@ -2790,17 +2792,34 @@ 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)); } /** @@ -2839,6 +2858,23 @@ public class UserManager { return user != null && user.isAdmin(); } + /** + * 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. * @@ -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 be4693bc0377ad26b7d3c452ea290abb7f0ea302..4da02f902e607d1ed2cff71b284e98e3f4e850ed 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 841354a9219211222dc1b76b32c9a593e6b3ea85..e6eeca4b4801b801e909a3c805684fc76e07cdfa 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 */ @@ -67,6 +71,14 @@ public class TaskFragmentParentInfo implements Parcelable { return mVisible; } + /** + * 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}. @@ -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/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index 965277c4635e7a34895766a6462f0d8df78764f9..1c5f4f0f1369cb0b596471750c3a29dcc0ced421 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 993e4e7b4b3d0bb39f0afe8e60947fd3d03baded..5fe086da8c6a9846295d90094bca3e33b3d29149 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/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java index 72969f7d8b96a7bac130071f8ca961364b633d6a..37a499adf6824e296bd80ad08e546824bad73c43 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/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java index 06b62f85bede099798659fb6aaad9c7ae29e6297..dd116b5b5c35959621053468a9bf525a0a46ce0d 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 49606f0bf4857c4ed1463b2834a21beefc2c5e7b..7743ad55debbc55e9acbe73c61fc6e2509040cab 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 9e533808ccc0356afccc0aa4ca70859d2bfd6759..eeb3ccf0d4cbb99f59d225778bf6a76961128fcd 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 405f0b2f51dc89f6a314dbe17eb10074c3485ef2..e74d5fb4d0bed5e968824c821a14bb8b85adca11 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 96839c57d745e571c8a8dbf92fbf47b5153bd6ab..02031a67e7e3ae508b3a71e0c146619034340224 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 21889960a8b2719f243b05652373ae7d275414a4..e3f51697c284633ce02bb2ace559f9f8fbbed2bc 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 ca2c3b4fbff1e77f94537f629ea8ffcce33cca07..293b66084d283398cc1e6f38d223bea378508582 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/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig index c9cfa670cf02b8c25cb3f777cd4d7e4439a31a94..386534b8092576b7efa0a7fb754891c4083fa610 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 10c880d8ab06bd5a9ca6facd49848e28c16676d0..24efbd14bc02d801b5c385f303875100596cd4f3 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 477e61d2a7b1c34fc4d8734168fcd77f0fa96696..f0fa6c5c4dd29333219e8ef4e70d5e40a60d685e 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 d35dcb54c36d96e1896ed09424a702dcdbd337b5..81cbd5acaca60aa1ca3853e2f77a54e02be0a75e 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/SystemUI/res/layout-land-television/volume_dialog_row.xml b/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml index bddcb6abeebed3711e32f8ac8890f5fe36cab829..cf301c96a9f8b625959ecbfba79ae947fb446c90 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 3b70dc060e84e5c500e7676d8c3803af40f66955..5ce2601d407d8dda4aa84caadd38e6f60e6ccd7a 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/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml index 6a192d4b7e059d25d17ead6d3bf7023389d2d015..39a1f1f9b85dccd81362227cba9bf8aa13658494 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 c9256ae5123be5681488b629884f74517f3d1e45..f35de0568327ee8b242a0d5377c327ba26d2d16a 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/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java index 584357bb739cf9252e4924eff077b26515841b24..0bd4859eefd59102f7a0c7ac2ad77cfe4313efe8 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 06ec17ff80d1ed9debc2125dba4f4398fcdb75a2..ff1df3616b3d50365a520251f147658384690465 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -479,7 +479,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 +618,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 +722,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 +768,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 aecfdaa860f36e3b586a24e357cfd5c43b5d60b4..e768f162c270b8173f6f289d33a913dc3b888473 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 19621199aad5e2ef5ed5445ed5c035ecc7e18d7f..98a389669ba90439bd5218bf53af59b1b974ed6d 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 04d55665f572ef935dc48c0cae68e42831d5ea87..50e9e7517a0f2a8bbe21988c99a4c2dd80b14608 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 67ef119a74284dd4a0aadd78a64edb19b8e981d1..575e1986a23f6cc33223b2899c725ced9f27b366 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 8b437c3225498dc58d283c9f6590f3bb87f06fd1..eea369f15a16fd646b6da41fafd314919e5e54bc 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 f7cc589db0681a84d6bc3108486f298feb694d9f..eacfa578ac44d8d9635d1c5f76f2723805916edb 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 b9bafd4926fa773514be32d22c33062a32033a6e..cff22b0dc019c6301f159e909b0111833f8531b0 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/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java index ea1205ae6079f85fa8e0a91358bdb9efb9bb9827..05f125f6f531cff2c30939ff383174765e0d1cf2 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 f2e94e94757fb239f977ed94d7d44ee26cd792be..e7481ccd0efd69f2a0c08764f7cd329e46f37135 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 3b3aa5334979e139e8e25155767c6f0ea632a5a6..f74234bc2e21998d9bcab7cfcfa95dfd8c24b236 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/notification/interruption/VisualInterruptionSuppressor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt new file mode 100644 index 0000000000000000000000000000000000000000..4ef80e38bc63c0d77cec24b13a5f7d12e569a317 --- /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/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 0ff308e19dd6474acc51ece148325b200ec07368..280c66a3315121506131546da37cff0ba5b73aa3 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); @@ -1790,28 +1786,14 @@ 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/systemui/accessibility/MockMagnificationAnimationCallback.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MockMagnificationAnimationCallback.java deleted file mode 100644 index 89389b0dd42491f4c563c9edeb2390ff5eff7e32..0000000000000000000000000000000000000000 --- 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 f15164e3fdcca7ad66edf027a9e1f09d77470da6..284c273fa83193375b92ecfe57621502bf74d09a 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 86ae51768219828cd7cc9df1ffe3c702f098b7f4..06421dbbf2bb5f5b8e6c7422e08bd9ab1fe161ff 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/mediaprojection/MediaProjectionMetricsLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt index fd1e2c7b19a51b71e1fa1adda70ae6fc17563235..da448aa20289268a339296af106334e827d602fa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt @@ -73,6 +73,15 @@ class MediaProjectionMetricsLoggerTest : SysuiTestCase() { verify(service).notifyPermissionRequestDisplayed(hostUid) } + @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 5255f71b9c0961ebfffadd34546ed61a9a59302c..44798ea99bee2c1de15b1c8153a35da10a6c7c7e 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 c439cfe6270fd87e08fd407b058af3cc664f2298..49049bc51460b4a4c9deaa65d83cfb5ea9fe11ce 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 bf12d7deb07631c058a848374ddbe071767467ea..fd381392c3e93d6f3f2a70df575f2afb737b7758 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/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt index cbb08946a1b031a7491704a97c7bf925064cc8cd..947bcfb5011b799afed09a303c85faf9746027eb 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 0000000000000000000000000000000000000000..6f4bbd5e21fce38da447e1652b93e44b450d2262 --- /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/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java index c4c7472ba39c669f4bb8ea899c887440cb7eed58..7456e00e948d1787f6b059d46be93139b421842f 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 0000000000000000000000000000000000000000..cdd0ff7c38f797dfdf4206959e0c3fb1e286cd3f --- /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/statusbar/FakeStatusBarStateController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/FakeStatusBarStateController.kt new file mode 100644 index 0000000000000000000000000000000000000000..19fdb6ddad02eac4f80d9eedc299091845419f9b --- /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 eaa109d672f812049bc580f379cb624b9c197d54..209cac6688a27ff9d257c9f867d33b180401516c 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/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java index 5af80da894ccc6ed956373d37f18a3c81f6b6e1b..29137167f52624bc1cfae94d2416e16969652600 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -145,6 +145,13 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo /** Flag for intercepting generic motion events. */ static final int FLAG_FEATURE_INTERCEPT_GENERIC_MOTION_EVENTS = 0x00000800; + /** + * Flag for enabling the two-finger triple-tap magnification feature. + * + * @see #setUserAndEnabledFeatures(int, int) + */ + static final int FLAG_FEATURE_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP = 0x00001000; + static final int FEATURES_AFFECTING_MOTION_EVENTS = FLAG_FEATURE_INJECT_MOTION_EVENTS | FLAG_FEATURE_AUTOCLICK diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index d575102bd5e15223e6d1b5a4d27dfb0d46437a83..a7b5325273819b0ebb56fc5eab0a10565b1f6bf5 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -2786,6 +2786,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub flags |= AccessibilityInputFilter .FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP; } + if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) { + if (userState.isMagnificationSingleFingerTripleTapEnabledLocked()) { + flags |= AccessibilityInputFilter + .FLAG_FEATURE_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP; + } + } if (userState.isShortcutMagnificationEnabledLocked()) { flags |= AccessibilityInputFilter.FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER; } @@ -3150,6 +3156,21 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return false; } + private boolean readMagnificationTwoFingerTripleTapSettingsLocked( + AccessibilityUserState userState) { + final boolean magnificationTwoFingerTripleTapEnabled = Settings.Secure.getIntForUser( + mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED, + 0, userState.mUserId) == 1; + if ((magnificationTwoFingerTripleTapEnabled + != userState.isMagnificationTwoFingerTripleTapEnabledLocked())) { + userState.setMagnificationTwoFingerTripleTapEnabledLocked( + magnificationTwoFingerTripleTapEnabled); + return true; + } + return false; + } + private boolean readAutoclickEnabledSettingLocked(AccessibilityUserState userState) { final boolean autoclickEnabled = Settings.Secure.getIntForUser( mContext.getContentResolver(), @@ -3385,6 +3406,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub // displays in one display. It's not a real display and there's no input events for it. final ArrayList<Display> displays = getValidDisplayList(); if (userState.isMagnificationSingleFingerTripleTapEnabledLocked() + || userState.isMagnificationTwoFingerTripleTapEnabledLocked() || userState.isShortcutMagnificationEnabledLocked()) { for (int i = 0; i < displays.size(); i++) { final Display display = displays.get(i); @@ -4920,6 +4942,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private final Uri mMagnificationmSingleFingerTripleTapEnabledUri = Settings.Secure .getUriFor(Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED); + private final Uri mMagnificationTwoFingerTripleTapEnabledUri = Settings.Secure.getUriFor( + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED); + private final Uri mAutoclickEnabledUri = Settings.Secure.getUriFor( Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED); @@ -4977,6 +5002,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub false, this, UserHandle.USER_ALL); contentResolver.registerContentObserver(mMagnificationmSingleFingerTripleTapEnabledUri, false, this, UserHandle.USER_ALL); + if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) { + contentResolver.registerContentObserver(mMagnificationTwoFingerTripleTapEnabledUri, + false, this, UserHandle.USER_ALL); + } contentResolver.registerContentObserver(mAutoclickEnabledUri, false, this, UserHandle.USER_ALL); contentResolver.registerContentObserver(mEnabledAccessibilityServicesUri, @@ -5027,6 +5056,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (readMagnificationEnabledSettingsLocked(userState)) { onUserStateChangedLocked(userState); } + } else if (Flags.enableMagnificationMultipleFingerMultipleTapGesture() + && mMagnificationTwoFingerTripleTapEnabledUri.equals(uri)) { + if (readMagnificationTwoFingerTripleTapSettingsLocked(userState)) { + onUserStateChangedLocked(userState); + } } else if (mAutoclickEnabledUri.equals(uri)) { if (readAutoclickEnabledSettingLocked(userState)) { onUserStateChangedLocked(userState); diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java index b4efec1a4b38cbb6f7e66d94941a1a7f3a4bd86c..68ee78076f3d59687aa1c4240d4e7f0090db882e 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java @@ -110,6 +110,7 @@ class AccessibilityUserState { private boolean mIsAudioDescriptionByDefaultRequested; private boolean mIsAutoclickEnabled; private boolean mIsMagnificationSingleFingerTripleTapEnabled; + private boolean mMagnificationTwoFingerTripleTapEnabled; private boolean mIsFilterKeyEventsEnabled; private boolean mIsPerformGesturesEnabled; private boolean mAccessibilityFocusOnlyInActiveWindow; @@ -212,6 +213,7 @@ class AccessibilityUserState { mRequestTwoFingerPassthrough = false; mSendMotionEventsEnabled = false; mIsMagnificationSingleFingerTripleTapEnabled = false; + mMagnificationTwoFingerTripleTapEnabled = false; mIsAutoclickEnabled = false; mUserNonInteractiveUiTimeout = 0; mUserInteractiveUiTimeout = 0; @@ -633,6 +635,14 @@ class AccessibilityUserState { mIsMagnificationSingleFingerTripleTapEnabled = enabled; } + public boolean isMagnificationTwoFingerTripleTapEnabledLocked() { + return mMagnificationTwoFingerTripleTapEnabled; + } + + public void setMagnificationTwoFingerTripleTapEnabledLocked(boolean enabled) { + mMagnificationTwoFingerTripleTapEnabled = enabled; + } + public boolean isFilterKeyEventsEnabledLocked() { return mIsFilterKeyEventsEnabled; } diff --git a/services/core/Android.bp b/services/core/Android.bp index 961e9d3dcfff99baa0d51ad4f20e75a986cb32d9..3985a0e7f46cfe822e25222d663ee62c60eff5f7 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/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java index c29d9bd191492060f808c301132c8c05de70e88f..f085647c96769d10a09c218b013474c2494e1248 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 76dde5463534096b7283de88991bc67793ca8ce9..e3c0cf7365ceefdc5f8dcf8d1a430cd57e38c2f1 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 67a1ccd17835f7113c5ddec0903ac3d112f55e88..78077a831622a720c51839fdc6015175ace91b28 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 58927d14ee235a33b8bad5ecbff2ea44d915757e..893ed6119f9f0867b0902435c98a1ede2665bb6c 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java @@ -469,6 +469,11 @@ public final class MediaProjectionManagerService extends SystemService mMediaProjectionMetricsLogger.logPermissionRequestDisplayed(hostUid); } + @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 55a30bf225a33396191e6e32a064727b435ad429..d7fefeb0b1fe76f5da329cda7566b1dd7d0487a6 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java @@ -117,6 +117,23 @@ public class MediaProjectionMetricsLogger { MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN); } + /** + * 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. * @@ -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 1134714bce5512812356e2f064c3b90d2128f140..e57ea0f6580470f362122c6fab0f1408d79e1106 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 7c32cde6c1d85df3098e6fb41bfdcac87ad0c663..2951ef630eb390369c580ccc7ecb3e08e4e02b64 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 c260be9a51245c51b1d5b282b7f5815d92d4f721..8b82a1c6e7c954ae193641a9bebd1ef4f735535a 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 6a2ddc8f94b0017284026b318a8a2ac3e6954c65..ea082cf779877e97fa7495d2ea7c85c139376bc7 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/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 978d8e4a0e8a77d19e55b2ad979fd6bc6e030186..3430bb4dd7c282d34f800e27d3f2c211f8310a4b 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -2137,6 +2137,19 @@ public class UserManagerService extends IUserManager.Stub { return displayId; } + @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(); diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index 635e11be3a1682058279c9128b5993059a8652ba..f82f08bfc851e3e41e2e5e8fee15b8aece27851f 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 e7893da6084789d5793bab2763e8e31be685e8b6..f31490060fc876ebcd0d393c0e8cd5274bbf4451 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 d2f6d1698f1a6243679610ff38f35f657045fc11..6cad16c7db40707296d7a5a770d78796dde41b99 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(); @@ -4522,11 +4516,6 @@ class Task extends TaskFragment { return (mForceHiddenFlags & FLAG_FORCE_HIDDEN_FOR_PINNED_TASK) != 0; } - @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 82d34246f8571f33bca35d6f0a117acbcb9f1e34..00f2b89633509b3f9a72ba7e80c87d5c81e54746 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 4a0f44b58ecff4dbfe0317d17280ac9480c97ef1..8f884d2fe29e459b7ed7ccc81410d71c69ff5e5f 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 eb60aaba510ff0d2473d56a6f6cfab3c99d89bea..a8b9417edb9b713b2e1622605d66dfcca2fc6816 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/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java index 71007f53f0e1fe790057973125aac63c249d71ce..52a5d8f6d9527232a1dea46abbc8004e1d0e7e7c 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java @@ -156,6 +156,7 @@ public class AccessibilityUserStateTest { mUserState.setTargetAssignedToAccessibilityButton(COMPONENT_NAME.flattenToString()); mUserState.setTouchExplorationEnabledLocked(true); mUserState.setMagnificationSingleFingerTripleTapEnabledLocked(true); + mUserState.setMagnificationTwoFingerTripleTapEnabledLocked(true); mUserState.setAutoclickEnabledLocked(true); mUserState.setUserNonInteractiveUiTimeoutLocked(30); mUserState.setUserInteractiveUiTimeoutLocked(30); @@ -178,6 +179,7 @@ public class AccessibilityUserStateTest { assertNull(mUserState.getTargetAssignedToAccessibilityButton()); assertFalse(mUserState.isTouchExplorationEnabledLocked()); assertFalse(mUserState.isMagnificationSingleFingerTripleTapEnabledLocked()); + assertFalse(mUserState.isMagnificationTwoFingerTripleTapEnabledLocked()); assertFalse(mUserState.isAutoclickEnabledLocked()); assertEquals(0, mUserState.getUserNonInteractiveUiTimeoutLocked()); assertEquals(0, mUserState.getUserInteractiveUiTimeoutLocked()); 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 4e6dd064165d19a6c314bfa2bde79bdeadfc8618..ece3dfeabafa354804f969161f7ad27e645f7f78 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 @@ -673,6 +673,17 @@ public class MediaProjectionManagerServiceTest { verify(mMediaProjectionMetricsLogger).logPermissionRequestDisplayed(hostUid); } + @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; 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 410604f02e1fc3be9c48f1f4b382e3b486dcea81..ad1cd6eca5ac8b0c6cf5b388026fb2644c133860 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 fc27edcb7bda6c523c568329043f265d33fcbfa1..a4d50f0f9559687312ab33111c497f283902614f 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 6235b3b67145070843e596953b08d6953824214a..c57b05130e777ad7dc747ed62ee44fccbd884196 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 0c580697bc4acd61651d0e2e9b26d307d36c0614..435a8357dabb481a833661614bf58cb1d236308c 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 cd3fef6b6fdb6d3783f4a469233ba414329ab142..699580a6536d7c2a92aefc97321c5a9509423659 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 3ec6f425a37a6b5d317d4489beb00223464096df..973ab846fa4a8bdf3403535ba41d9255992a14a1 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 a1a39ff173b410e09ec3ab9ba70e83af0d62780c..359ef83cfe7ca013f9030bd8d21a8c9975532342 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( @@ -289,6 +301,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(); @@ -304,6 +323,13 @@ public class VcnGatewayConnectionConfigTest { assertEquals(config, new VcnGatewayConnectionConfig(config.toPersistableBundle())); } + @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(); @@ -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); + } }