diff options
175 files changed, 4507 insertions, 2484 deletions
diff --git a/StubLibraries.bp b/StubLibraries.bp index 49433f16f572..86364af20812 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -82,12 +82,13 @@ stubs_defaults { "android.hardware.vibrator-V1.3-java", "framework-protos", "stable.core.platform.api.stubs", - // There are a few classes from modules used as type arguments that - // need to be resolved by metalava. For now, we can use a previously - // finalized stub library to resolve them. If a new class gets added, - // this may be need to be revisited to use a manually maintained stub - // library with empty classes in order to resolve those references. - "sdk_system_30_android", + // There are a few classes from modules used by the core that + // need to be resolved by metalava. We use a prebuilt stub of the + // full sdk to ensure we can resolve them. If a new class gets added, + // the prebuilts/sdk/current needs to be updated. + "sdk_system_current_android", + // NOTE: The below can be removed once the prebuilt stub contains IKE. + "sdk_system_current_android.net.ipsec.ike", ], high_mem: true, // Lots of sources => high memory use, see b/170701554 installable: false, @@ -404,7 +405,11 @@ java_library_static { "android_defaults_stubs_current", "android_stubs_dists_default", ], - libs: ["sdk_system_29_android"], + libs: [ + "sdk_system_current_android", + // NOTE: The below can be removed once the prebuilt stub contains IKE. + "sdk_system_current_android.net.ipsec.ike", + ], static_libs: ["art.module.public.api.stubs"], dist: { dir: "apistubs/android/module-lib", diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java index e8a281717754..d249f2ae813c 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java @@ -16,7 +16,6 @@ package com.android.server.job.controllers; -import static android.net.NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; @@ -332,7 +331,7 @@ public final class ConnectivityController extends RestrictingController implemen if (downloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) { final long bandwidth = capabilities.getLinkDownstreamBandwidthKbps(); // If we don't know the bandwidth, all we can do is hope the job finishes in time. - if (bandwidth != LINK_BANDWIDTH_UNSPECIFIED) { + if (bandwidth > 0) { // Divide by 8 to convert bits to bytes. final long estimatedMillis = ((downloadBytes * DateUtils.SECOND_IN_MILLIS) / (DataUnit.KIBIBYTES.toBytes(bandwidth) / 8)); @@ -350,7 +349,7 @@ public final class ConnectivityController extends RestrictingController implemen if (uploadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) { final long bandwidth = capabilities.getLinkUpstreamBandwidthKbps(); // If we don't know the bandwidth, all we can do is hope the job finishes in time. - if (bandwidth != LINK_BANDWIDTH_UNSPECIFIED) { + if (bandwidth > 0) { // Divide by 8 to convert bits to bytes. final long estimatedMillis = ((uploadBytes * DateUtils.SECOND_IN_MILLIS) / (DataUnit.KIBIBYTES.toBytes(bandwidth) / 8)); @@ -380,18 +379,16 @@ public final class ConnectivityController extends RestrictingController implemen private static boolean isStrictSatisfied(JobStatus jobStatus, Network network, NetworkCapabilities capabilities, Constants constants) { - final NetworkCapabilities required; // A restricted job that's out of quota MUST use an unmetered network. if (jobStatus.getEffectiveStandbyBucket() == RESTRICTED_INDEX && !jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)) { - required = new NetworkCapabilities( + final NetworkCapabilities required = new NetworkCapabilities.Builder( jobStatus.getJob().getRequiredNetwork().networkCapabilities) - .addCapability(NET_CAPABILITY_NOT_METERED); + .addCapability(NET_CAPABILITY_NOT_METERED).build(); + return required.satisfiedByNetworkCapabilities(capabilities); } else { - required = jobStatus.getJob().getRequiredNetwork().networkCapabilities; + return jobStatus.getJob().getRequiredNetwork().canBeSatisfiedBy(capabilities); } - - return required.satisfiedByNetworkCapabilities(capabilities); } private static boolean isRelaxedSatisfied(JobStatus jobStatus, Network network, @@ -402,9 +399,9 @@ public final class ConnectivityController extends RestrictingController implemen } // See if we match after relaxing any unmetered request - final NetworkCapabilities relaxed = new NetworkCapabilities( + final NetworkCapabilities relaxed = new NetworkCapabilities.Builder( jobStatus.getJob().getRequiredNetwork().networkCapabilities) - .removeCapability(NET_CAPABILITY_NOT_METERED); + .removeCapability(NET_CAPABILITY_NOT_METERED).build(); if (relaxed.satisfiedByNetworkCapabilities(capabilities)) { // TODO: treat this as "maybe" response; need to check quotas return jobStatus.getFractionRunTime() > constants.CONN_PREFETCH_RELAX_FRAC; diff --git a/core/api/current.txt b/core/api/current.txt index a9458897dc4f..5679a3dbcd6a 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -12298,7 +12298,7 @@ package android.content.pm { method @Deprecated public abstract void removePackageFromPreferred(@NonNull String); method public abstract void removePermission(@NonNull String); method @RequiresPermission(value="android.permission.WHITELIST_RESTRICTED_PERMISSIONS", conditional=true) public boolean removeWhitelistedRestrictedPermission(@NonNull String, @NonNull String, int); - method public void requestChecksums(@NonNull String, boolean, int, @NonNull java.util.List<java.security.cert.Certificate>, @NonNull android.content.IntentSender) throws java.security.cert.CertificateEncodingException, android.content.pm.PackageManager.NameNotFoundException; + method public void requestChecksums(@NonNull String, boolean, int, @NonNull java.util.List<java.security.cert.Certificate>, @NonNull android.content.pm.PackageManager.OnChecksumsReadyListener) throws java.security.cert.CertificateEncodingException, android.content.pm.PackageManager.NameNotFoundException; method @Nullable public abstract android.content.pm.ResolveInfo resolveActivity(@NonNull android.content.Intent, int); method @Nullable public abstract android.content.pm.ProviderInfo resolveContentProvider(@NonNull String, int); method @Nullable public abstract android.content.pm.ResolveInfo resolveService(@NonNull android.content.Intent, int); @@ -12318,7 +12318,6 @@ package android.content.pm { field public static final int COMPONENT_ENABLED_STATE_DISABLED_USER = 3; // 0x3 field public static final int COMPONENT_ENABLED_STATE_ENABLED = 1; // 0x1 field public static final int DONT_KILL_APP = 1; // 0x1 - field public static final String EXTRA_CHECKSUMS = "android.content.pm.extra.CHECKSUMS"; field public static final String EXTRA_VERIFICATION_ID = "android.content.pm.extra.VERIFICATION_ID"; field public static final String EXTRA_VERIFICATION_RESULT = "android.content.pm.extra.VERIFICATION_RESULT"; field public static final String FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS = "android.software.activities_on_secondary_displays"; @@ -12504,6 +12503,10 @@ package android.content.pm { ctor public PackageManager.NameNotFoundException(String); } + @java.lang.FunctionalInterface public static interface PackageManager.OnChecksumsReadyListener { + method public void onChecksumsReady(@NonNull java.util.List<android.content.pm.ApkChecksum>); + } + public static final class PackageManager.Property implements android.os.Parcelable { method public int describeContents(); method public boolean getBoolean(); @@ -15025,6 +15028,7 @@ package android.graphics { field public static final int RGB_565 = 4; // 0x4 field public static final int UNKNOWN = 0; // 0x0 field public static final int Y8 = 538982489; // 0x20203859 + field public static final int YCBCR_P010 = 54; // 0x36 field public static final int YUV_420_888 = 35; // 0x23 field public static final int YUV_422_888 = 39; // 0x27 field public static final int YUV_444_888 = 40; // 0x28 @@ -31444,6 +31448,7 @@ 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 isDemoUser(); + method public static boolean isHeadlessSystemUserMode(); method public boolean isManagedProfile(); method public boolean isQuietModeEnabled(android.os.UserHandle); method public boolean isSystemUser(); @@ -40390,6 +40395,7 @@ package android.telephony { field public static final String KEY_ENABLE_PRESENCE_GROUP_SUBSCRIBE_BOOL = "ims.enable_presence_group_subscribe_bool"; field public static final String KEY_ENABLE_PRESENCE_PUBLISH_BOOL = "ims.enable_presence_publish_bool"; field public static final String KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL = "ims.ims_single_registration_required_bool"; + field public static final String KEY_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC_INT = "ims.non_rcs_capabilities_cache_expiration_sec_int"; field public static final String KEY_PREFIX = "ims."; field public static final String KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL = "ims.rcs_bulk_capability_exchange_bool"; field public static final String KEY_WIFI_OFF_DEFERRING_TIME_MILLIS_INT = "ims.wifi_off_deferring_time_millis_int"; diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 1977babe0bfb..053f6cb4b05b 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -10331,7 +10331,7 @@ package android.service.storage { ctor public ExternalStorageService(); method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent); method public abstract void onEndSession(@NonNull String) throws java.io.IOException; - method public void onFreeCacheRequested(@NonNull java.util.UUID, long); + method public void onFreeCache(@NonNull java.util.UUID, long) throws java.io.IOException; method public abstract void onStartSession(@NonNull String, int, @NonNull android.os.ParcelFileDescriptor, @NonNull java.io.File, @NonNull java.io.File) throws java.io.IOException; method public abstract void onVolumeStateChanged(@NonNull android.os.storage.StorageVolume) throws java.io.IOException; field public static final int FLAG_SESSION_ATTRIBUTE_INDEXABLE = 2; // 0x2 @@ -11704,6 +11704,7 @@ package android.telephony { method public boolean canManageSubscription(@NonNull android.telephony.SubscriptionInfo, @NonNull String); method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int[] getActiveSubscriptionIdList(); method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.SubscriptionInfo getActiveSubscriptionInfoForIcc(@NonNull String); + method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public byte[] getAllSimSpecificSettingsForBackup(); method public java.util.List<android.telephony.SubscriptionInfo> getAvailableSubscriptionInfoList(); method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int[] getCompleteActiveSubscriptionIdList(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEnabledSubscriptionId(int); @@ -11711,6 +11712,7 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isSubscriptionEnabled(int); method public void requestEmbeddedSubscriptionInfoListRefresh(); method public void requestEmbeddedSubscriptionInfoListRefresh(int); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void restoreAllSimSpecificSettingsFromBackup(@NonNull byte[]); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDefaultDataSubId(int); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDefaultSmsSubId(int); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDefaultVoiceSubscriptionId(int); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index e88b6b92581e..79917d020198 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -28,6 +28,7 @@ package android { field public static final String READ_PRIVILEGED_PHONE_STATE = "android.permission.READ_PRIVILEGED_PHONE_STATE"; field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS"; field public static final String RESET_APP_ERRORS = "android.permission.RESET_APP_ERRORS"; + field public static final String START_TASKS_FROM_RECENTS = "android.permission.START_TASKS_FROM_RECENTS"; field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS"; field public static final String TEST_BIOMETRIC = "android.permission.TEST_BIOMETRIC"; field public static final String TEST_MANAGE_ROLLBACKS = "android.permission.TEST_MANAGE_ROLLBACKS"; @@ -119,6 +120,7 @@ package android.app { public class ActivityOptions { method @NonNull public static android.app.ActivityOptions makeCustomAnimation(@NonNull android.content.Context, int, int, @Nullable android.os.Handler, @Nullable android.app.ActivityOptions.OnAnimationStartedListener, @Nullable android.app.ActivityOptions.OnAnimationFinishedListener); + method @NonNull @RequiresPermission(android.Manifest.permission.START_TASKS_FROM_RECENTS) public static android.app.ActivityOptions makeCustomTaskAnimation(@NonNull android.content.Context, int, int, @Nullable android.os.Handler, @Nullable android.app.ActivityOptions.OnAnimationStartedListener, @Nullable android.app.ActivityOptions.OnAnimationFinishedListener); method public static void setExitTransitionTimeout(long); method public void setLaunchActivityType(int); method public void setLaunchTaskId(int); @@ -390,11 +392,10 @@ package android.app.admin { method public boolean isFactoryResetProtectionPolicySupported(); method @NonNull public static String operationToString(int); method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void provisionFullyManagedDevice(@NonNull android.app.admin.FullyManagedDeviceProvisioningParams) throws android.app.admin.ProvisioningException; + method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void resetDefaultCrossProfileIntentFilters(int); method @RequiresPermission("android.permission.MANAGE_DEVICE_ADMINS") public void setNextOperationSafety(int, int); method @NonNull public static String unsafeOperationReasonToString(int); field public static final String ACTION_DATA_SHARING_RESTRICTION_APPLIED = "android.app.action.DATA_SHARING_RESTRICTION_APPLIED"; - field public static final String ACTION_MANAGED_PROFILE_CREATED = "android.app.action.MANAGED_PROFILE_CREATED"; - field public static final String ACTION_PROVISIONED_MANAGED_DEVICE = "android.app.action.PROVISIONED_MANAGED_DEVICE"; field public static final int CODE_ACCOUNTS_NOT_EMPTY = 6; // 0x6 field public static final int CODE_CANNOT_ADD_MANAGED_PROFILE = 11; // 0xb field public static final int CODE_DEVICE_ADMIN_NOT_SUPPORTED = 13; // 0xd diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 2b5e18d3feec..28da1c3a3eb7 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -17,6 +17,7 @@ package android.app; import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS; +import static android.Manifest.permission.START_TASKS_FROM_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.Display.INVALID_DISPLAY; @@ -310,6 +311,9 @@ public class ActivityOptions { private static final String KEY_REMOTE_TRANSITION = "android:activity.remoteTransition"; + private static final String KEY_OVERRIDE_TASK_TRANSITION = + "android:activity.overrideTaskTransition"; + /** * @see #setLaunchCookie * @hide @@ -393,6 +397,7 @@ public class ActivityOptions { private RemoteAnimationAdapter mRemoteAnimationAdapter; private IBinder mLaunchCookie; private IRemoteTransition mRemoteTransition; + private boolean mOverrideTaskTransition; /** * Create an ActivityOptions specifying a custom animation to run when @@ -476,6 +481,40 @@ public class ActivityOptions { } /** + * Create an ActivityOptions specifying a custom animation to run when the activity in the + * different task is displayed. + * + * @param context Who is defining this. This is the application that the + * animation resources will be loaded from. + * @param enterResId A resource ID of the animation resource to use for + * the incoming activity. Use 0 for no animation. + * @param exitResId A resource ID of the animation resource to use for + * the outgoing activity. Use 0 for no animation. + * @param handler If <var>listener</var> is non-null this must be a valid + * Handler on which to dispatch the callback; otherwise it should be null. + * @param startedListener Optional OnAnimationStartedListener to find out when the + * requested animation has started running. If for some reason the animation + * is not executed, the callback will happen immediately. + * @param finishedListener Optional OnAnimationFinishedListener when the animation + * has finished running. + * + * @return Returns a new ActivityOptions object that you can use to + * supply these options as the options Bundle when starting an activity. + * @hide + */ + @RequiresPermission(START_TASKS_FROM_RECENTS) + @TestApi + public static @NonNull ActivityOptions makeCustomTaskAnimation(@NonNull Context context, + int enterResId, int exitResId, @Nullable Handler handler, + @Nullable OnAnimationStartedListener startedListener, + @Nullable OnAnimationFinishedListener finishedListener) { + ActivityOptions opts = makeCustomAnimation(context, enterResId, exitResId, handler, + startedListener, finishedListener); + opts.mOverrideTaskTransition = true; + return opts; + } + + /** * Creates an ActivityOptions specifying a custom animation to run in place on an existing * activity. * @@ -1107,6 +1146,7 @@ public class ActivityOptions { mLaunchCookie = opts.getBinder(KEY_LAUNCH_COOKIE); mRemoteTransition = IRemoteTransition.Stub.asInterface(opts.getBinder( KEY_REMOTE_TRANSITION)); + mOverrideTaskTransition = opts.getBoolean(KEY_OVERRIDE_TASK_TRANSITION); } /** @@ -1561,6 +1601,12 @@ public class ActivityOptions { return mLaunchCookie; } + + /** @hide */ + public boolean getOverrideTaskTransition() { + return mOverrideTaskTransition; + } + /** * Update the current values in this ActivityOptions from those supplied * in <var>otherOptions</var>. Any values @@ -1789,6 +1835,9 @@ public class ActivityOptions { if (mRemoteTransition != null) { b.putBinder(KEY_REMOTE_TRANSITION, mRemoteTransition.asBinder()); } + if (mOverrideTaskTransition) { + b.putBoolean(KEY_OVERRIDE_TASK_TRANSITION, mOverrideTaskTransition); + } return b; } diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index b51d4ac8c988..8ac91396a6b0 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -39,11 +39,13 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; import android.content.pm.ActivityInfo; +import android.content.pm.ApkChecksum; import android.content.pm.ApplicationInfo; import android.content.pm.ChangedPackages; import android.content.pm.Checksum; import android.content.pm.ComponentInfo; import android.content.pm.FeatureInfo; +import android.content.pm.IOnChecksumsReadyListener; import android.content.pm.IPackageDataObserver; import android.content.pm.IPackageDeleteObserver; import android.content.pm.IPackageManager; @@ -880,10 +882,10 @@ public class ApplicationPackageManager extends PackageManager { @Override public void requestChecksums(@NonNull String packageName, boolean includeSplits, @Checksum.Type int required, @NonNull List<Certificate> trustedInstallers, - @NonNull IntentSender statusReceiver) + @NonNull OnChecksumsReadyListener onChecksumsReadyListener) throws CertificateEncodingException, NameNotFoundException { Objects.requireNonNull(packageName); - Objects.requireNonNull(statusReceiver); + Objects.requireNonNull(onChecksumsReadyListener); Objects.requireNonNull(trustedInstallers); try { if (trustedInstallers == TRUST_ALL) { @@ -895,8 +897,17 @@ public class ApplicationPackageManager extends PackageManager { "trustedInstallers has to be one of TRUST_ALL/TRUST_NONE or a non-empty " + "list of certificates."); } + IOnChecksumsReadyListener onChecksumsReadyListenerDelegate = + new IOnChecksumsReadyListener.Stub() { + @Override + public void onChecksumsReady(List<ApkChecksum> checksums) + throws RemoteException { + onChecksumsReadyListener.onChecksumsReady(checksums); + } + }; mPM.requestChecksums(packageName, includeSplits, DEFAULT_CHECKSUMS, required, - encodeCertificates(trustedInstallers), statusReceiver, getUserId()); + encodeCertificates(trustedInstallers), onChecksumsReadyListenerDelegate, + getUserId()); } catch (ParcelableException e) { e.maybeRethrow(PackageManager.NameNotFoundException.class); throw new RuntimeException(e); diff --git a/core/java/android/app/WindowContext.java b/core/java/android/app/WindowContext.java index 14ed414da9d0..cbe2995f2467 100644 --- a/core/java/android/app/WindowContext.java +++ b/core/java/android/app/WindowContext.java @@ -15,8 +15,7 @@ */ package android.app; -import static android.view.WindowManagerGlobal.ADD_OKAY; -import static android.view.WindowManagerGlobal.ADD_TOO_MANY_TOKENS; +import static android.view.WindowManagerImpl.createWindowContextWindowManager; import android.annotation.NonNull; import android.annotation.Nullable; @@ -28,8 +27,8 @@ import android.os.IBinder; import android.os.RemoteException; import android.view.Display; import android.view.IWindowManager; +import android.view.WindowManager; import android.view.WindowManagerGlobal; -import android.view.WindowManagerImpl; import com.android.internal.annotations.VisibleForTesting; @@ -46,10 +45,10 @@ import java.lang.ref.Reference; */ @UiContext public class WindowContext extends ContextWrapper { - private final WindowManagerImpl mWindowManager; + private final WindowManager mWindowManager; private final IWindowManager mWms; private final WindowTokenClient mToken; - private boolean mOwnsToken; + private boolean mListenerRegistered; /** * Default constructor. Will generate a {@link WindowTokenClient} and attach this context to @@ -86,25 +85,14 @@ public class WindowContext extends ContextWrapper { mToken.attachContext(this); - mWindowManager = new WindowManagerImpl(this); - mWindowManager.setDefaultToken(mToken); + mWindowManager = createWindowContextWindowManager(this); - int result; try { - // Register the token with WindowManager. This will also call back with the current - // config back to the client. - result = mWms.addWindowTokenWithOptions( - mToken, type, getDisplayId(), options, getPackageName()); + mListenerRegistered = mWms.registerWindowContextListener(mToken, type, getDisplayId(), + options); } catch (RemoteException e) { - mOwnsToken = false; throw e.rethrowFromSystemServer(); } - if (result == ADD_TOO_MANY_TOKENS) { - throw new UnsupportedOperationException("createWindowContext failed! Too many unused " - + "window contexts. Please see Context#createWindowContext documentation for " - + "detail."); - } - mOwnsToken = result == ADD_OKAY; Reference.reachabilityFence(this); } @@ -131,10 +119,10 @@ public class WindowContext extends ContextWrapper { /** Used for test to invoke because we can't invoke finalize directly. */ @VisibleForTesting public void release() { - if (mOwnsToken) { + if (mListenerRegistered) { + mListenerRegistered = false; try { - mWms.removeWindowToken(mToken, getDisplayId()); - mOwnsToken = false; + mWms.unregisterWindowContextListener(mToken); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 642bce4eea08..06fe9d764f25 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -1730,8 +1730,12 @@ public class DevicePolicyManager { * Broadcast action to notify ManagedProvisioning that * {@link UserManager#DISALLOW_SHARE_INTO_MANAGED_PROFILE} restriction has changed. * @hide + * @deprecated No longer needed as ManagedProvisioning no longer handles + * {@link UserManager#DISALLOW_SHARE_INTO_MANAGED_PROFILE} restriction changing. */ + // TODO(b/177221010): Remove when Managed Provisioning no longer depend on it. @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @Deprecated public static final String ACTION_DATA_SHARING_RESTRICTION_CHANGED = "android.app.action.DATA_SHARING_RESTRICTION_CHANGED"; @@ -5469,26 +5473,6 @@ public class DevicePolicyManager { "android.app.action.ACTION_SHOW_NEW_USER_DISCLAIMER"; /** - * Broadcast action: notify managed provisioning that the device has been provisioned. - * - * @hide - */ - @TestApi - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_PROVISIONED_MANAGED_DEVICE = - "android.app.action.PROVISIONED_MANAGED_DEVICE"; - - /** - * Broadcast action: notify managed provisioning that a new managed profile is created. - * - * @hide - */ - @TestApi - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_MANAGED_PROFILE_CREATED = - "android.app.action.MANAGED_PROFILE_CREATED"; - - /** * Widgets are enabled in keyguard */ public static final int KEYGUARD_DISABLE_FEATURES_NONE = 0; @@ -13268,4 +13252,23 @@ public class DevicePolicyManager { } } } + + /** + * Resets the default cross profile intent filters that were set during + * {@link #createAndProvisionManagedProfile} between {@code userId} and all it's managed + * profiles if any. + * + * @hide + */ + @TestApi + @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) + public void resetDefaultCrossProfileIntentFilters(@UserIdInt int userId) { + if (mService != null) { + try { + mService.resetDefaultCrossProfileIntentFilters(userId); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + } } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index f9ee15393e93..cf0b31ea8cb2 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -498,4 +498,6 @@ interface IDevicePolicyManager { UserHandle createAndProvisionManagedProfile(in ManagedProfileProvisioningParams provisioningParams, in String callerPackage); void provisionFullyManagedDevice(in FullyManagedDeviceProvisioningParams provisioningParams, in String callerPackage); + + void resetDefaultCrossProfileIntentFilters(int userId); } diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java index 587e883edaf2..742d05c1ffa4 100644 --- a/core/java/android/app/backup/FullBackup.java +++ b/core/java/android/app/backup/FullBackup.java @@ -41,6 +41,7 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.Map; +import java.util.Optional; import java.util.Set; /** @@ -460,24 +461,40 @@ public class FullBackup { Set<PathWithRequiredFlags> excludes, Map<String, Set<PathWithRequiredFlags>> includes) throws IOException, XmlPullParserException { + verifyTopLevelTag(parser, "full-backup-content"); + + parseRules(parser, excludes, includes, Optional.empty()); + + logParsingResults(excludes, includes); + } + + private void verifyTopLevelTag(XmlPullParser parser, String tag) + throws XmlPullParserException, IOException { int event = parser.getEventType(); // START_DOCUMENT while (event != XmlPullParser.START_TAG) { event = parser.next(); } - if (!"full-backup-content".equals(parser.getName())) { + if (!tag.equals(parser.getName())) { throw new XmlPullParserException("Xml file didn't start with correct tag" + - " (<full-backup-content>). Found \"" + parser.getName() + "\""); + " (" + tag + " ). Found \"" + parser.getName() + "\""); } if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) { Log.v(TAG_XML_PARSER, "\n"); Log.v(TAG_XML_PARSER, "===================================================="); - Log.v(TAG_XML_PARSER, "Found valid fullBackupContent; parsing xml resource."); + Log.v(TAG_XML_PARSER, "Found valid " + tag + "; parsing xml resource."); Log.v(TAG_XML_PARSER, "===================================================="); Log.v(TAG_XML_PARSER, ""); } + } + private void parseRules(XmlPullParser parser, + Set<PathWithRequiredFlags> excludes, + Map<String, Set<PathWithRequiredFlags>> includes, + Optional<Integer> maybeRequiredFlags) + throws IOException, XmlPullParserException { + int event; while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) { switch (event) { case XmlPullParser.START_TAG: @@ -498,13 +515,7 @@ public class FullBackup { break; } - int requiredFlags = 0; // no transport flags are required by default - if (TAG_INCLUDE.equals(parser.getName())) { - // requiredFlags are only supported for <include /> tag, for <exclude /> - // we should always leave them as the default = 0 - requiredFlags = getRequiredFlagsFromString( - parser.getAttributeValue(null, "requireFlags")); - } + int requiredFlags = getRequiredFlagsForRule(parser, maybeRequiredFlags); // retrieve the include/exclude set we'll be adding this rule to Set<PathWithRequiredFlags> activeSet = parseCurrentTagForDomain( @@ -542,7 +553,7 @@ public class FullBackup { // Special case for sharedpref files (not dirs) also add ".xml" suffix file. if ("sharedpref".equals(domainFromXml) && !canonicalFile.isDirectory() && - !canonicalFile.getCanonicalPath().endsWith(".xml")) { + !canonicalFile.getCanonicalPath().endsWith(".xml")) { final String canonicalXmlPath = canonicalFile.getCanonicalPath() + ".xml"; activeSet.add(new PathWithRequiredFlags(canonicalXmlPath, @@ -554,6 +565,10 @@ public class FullBackup { } } } + } + + private void logParsingResults(Set<PathWithRequiredFlags> excludes, + Map<String, Set<PathWithRequiredFlags>> includes) { if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) { Log.v(TAG_XML_PARSER, "\n"); Log.v(TAG_XML_PARSER, "Xml resource parsing complete."); @@ -613,6 +628,24 @@ public class FullBackup { return flags; } + private int getRequiredFlagsForRule(XmlPullParser parser, + Optional<Integer> maybeRequiredFlags) { + if (maybeRequiredFlags.isPresent()) { + // This is the new config format where required flags are specified for the whole + // section, not per rule. + return maybeRequiredFlags.get(); + } + + if (TAG_INCLUDE.equals(parser.getName())) { + // In the legacy config, requiredFlags are only supported for <include /> tag, + // for <exclude /> we should always leave them as the default = 0. + return getRequiredFlagsFromString( + parser.getAttributeValue(null, "requireFlags")); + } + + return 0; + } + private Set<PathWithRequiredFlags> parseCurrentTagForDomain(XmlPullParser parser, Set<PathWithRequiredFlags> excludes, Map<String, Set<PathWithRequiredFlags>> includes, String domain) diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 987de3fca6b1..5d28216756ae 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -75,6 +75,7 @@ import android.view.Display; import android.view.DisplayAdjustments; import android.view.View; import android.view.ViewDebug; +import android.view.ViewGroup.LayoutParams; import android.view.WindowManager; import android.view.WindowManager.LayoutParams.WindowType; import android.view.autofill.AutofillManager.AutofillClient; @@ -6110,18 +6111,19 @@ public abstract class Context { * * // WindowManager.LayoutParams initialization * ... + * // The types used in addView and createWindowContext must match. * mParams.type = TYPE_APPLICATION_OVERLAY; * ... * - * mWindowContext.getSystemService(WindowManager.class).addView(overlayView, mParams); + * windowContext.getSystemService(WindowManager.class).addView(overlayView, mParams); * </pre> * * <p> - * This context's configuration and resources are adjusted to a display area where the windows - * with provided type will be added. <b>Note that all windows associated with the same context - * will have an affinity and can only be moved together between different displays or areas on a - * display.</b> If there is a need to add different window types, or non-associated windows, - * separate Contexts should be used. + * This context's configuration and resources are adjusted to an area of the display where + * the windows with provided type will be added. <b>Note that all windows associated with the + * same context will have an affinity and can only be moved together between different displays + * or areas on a display.</b> If there is a need to add different window types, or + * non-associated windows, separate Contexts should be used. * </p> * <p> * Creating a window context is an expensive operation. Misuse of this API may lead to a huge @@ -6129,7 +6131,43 @@ public abstract class Context { * An approach is to create one window context with specific window type and display and * use it everywhere it's needed. * </p> + * <p> + * After {@link Build.VERSION_CODES#S}, window context provides the capability to receive + * configuration changes for existing token by overriding the + * {@link android.view.WindowManager.LayoutParams#token token} of the + * {@link android.view.WindowManager.LayoutParams} passed in + * {@link WindowManager#addView(View, LayoutParams)}. This is useful when an application needs + * to attach its window to an existing activity for window token sharing use-case. + * </p> + * <p> + * Note that the window context in {@link Build.VERSION_CODES#R} didn't have this + * capability. This is a no-op for the window context in {@link Build.VERSION_CODES#R}. + * </p> + * Below is sample code to <b>attach an existing token to a window context:</b> + * <pre class="prettyprint"> + * final DisplayManager dm = anyContext.getSystemService(DisplayManager.class); + * final Display primaryDisplay = dm.getDisplay(DEFAULT_DISPLAY); + * final Context windowContext = anyContext.createWindowContext(primaryDisplay, + * TYPE_APPLICATION, null); + * + * // Get an existing token. + * final IBinder existingToken = activity.getWindow().getAttributes().token; + * + * // The types used in addView() and createWindowContext() must match. + * final WindowManager.LayoutParams params = new WindowManager.LayoutParams(TYPE_APPLICATION); + * params.token = existingToken; * + * // After WindowManager#addView(), the server side will extract the provided token from + * // LayoutParams#token (existingToken in the sample code), and switch to propagate + * // configuration changes from the node associated with the provided token. + * windowContext.getSystemService(WindowManager.class).addView(overlayView, mParams); + * </pre> + * <p> + * Note that using {@link android.app.Application} or {@link android.app.Service} context for + * UI-related queries may result in layout or continuity issues on devices with variable screen + * sizes (e.g. foldables) or in multi-window modes, since these non-UI contexts may not reflect + * the {@link Configuration} changes for the visual container. + * </p> * @param type Window type in {@link WindowManager.LayoutParams} * @param options A bundle used to pass window-related options * @return A {@link Context} that can be used to create @@ -6141,9 +6179,7 @@ public abstract class Context { * @see #LAYOUT_INFLATER_SERVICE * @see #WALLPAPER_SERVICE * @throws UnsupportedOperationException if this {@link Context} does not attach to a display, - * such as {@link android.app.Application Application} or {@link android.app.Service Service}, - * or the current number of window contexts without adding any view by - * {@link WindowManager#addView} <b>exceeds five</b>. + * such as {@link android.app.Application Application} or {@link android.app.Service Service}. */ @UiContext @NonNull diff --git a/core/java/android/content/pm/IOnChecksumsReadyListener.aidl b/core/java/android/content/pm/IOnChecksumsReadyListener.aidl new file mode 100644 index 000000000000..7963ce19956a --- /dev/null +++ b/core/java/android/content/pm/IOnChecksumsReadyListener.aidl @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm; + +import android.content.pm.ApkChecksum; + +/** + * Listener that gets notified when checksums are available. + * {@hide} + */ +oneway interface IOnChecksumsReadyListener { + void onChecksumsReady(in List<ApkChecksum> checksums); +} diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index b8829bbf1ca5..a46876ec53c4 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -27,6 +27,7 @@ import android.content.pm.InstantAppInfo; import android.content.pm.FeatureInfo; import android.content.pm.IDexModuleRegisterCallback; import android.content.pm.InstallSourceInfo; +import android.content.pm.IOnChecksumsReadyListener; import android.content.pm.IPackageInstaller; import android.content.pm.IPackageDeleteObserver; import android.content.pm.IPackageDeleteObserver2; @@ -754,7 +755,7 @@ interface IPackageManager { void notifyPackagesReplacedReceived(in String[] packages); - void requestChecksums(in String packageName, boolean includeSplits, int optional, int required, in List trustedInstallers, in IntentSender statusReceiver, int userId); + void requestChecksums(in String packageName, boolean includeSplits, int optional, int required, in List trustedInstallers, in IOnChecksumsReadyListener onChecksumsReadyListener, int userId); //------------------------------------------------------------------------ // diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index d09d83f0cd1d..b95b991b095c 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -3858,13 +3858,6 @@ public abstract class PackageManager { public static final String EXTRA_FAILURE_EXISTING_PERMISSION = "android.content.pm.extra.FAILURE_EXISTING_PERMISSION"; - /** - * Extra field name for the ID of a package pending verification. Passed to - * a package verifier and is used to call back to - * @see #requestChecksums - */ - public static final String EXTRA_CHECKSUMS = "android.content.pm.extra.CHECKSUMS"; - /** * Permission flag: The permission is set in its current state * by the user and apps can still request it at runtime. @@ -8709,9 +8702,20 @@ public abstract class PackageManager { */ public static final @NonNull List<Certificate> TRUST_NONE = Collections.singletonList(null); + /** Listener that gets notified when checksums are available. */ + @FunctionalInterface + public interface OnChecksumsReadyListener { + /** + * Called when the checksums are available. + * + * @param checksums array of checksums. + */ + void onChecksumsReady(@NonNull List<ApkChecksum> checksums); + } + /** * Requesting the checksums for APKs within a package. - * The checksums will be returned asynchronously via statusReceiver. + * The checksums will be returned asynchronously via onChecksumsReadyListener. * * By default returns all readily available checksums: * - enforced by platform, @@ -8730,15 +8734,14 @@ public abstract class PackageManager { * {@link #TRUST_ALL} will return checksums from any installer, * {@link #TRUST_NONE} disables optimized installer-enforced checksums, * otherwise the list has to be non-empty list of certificates. - * @param statusReceiver called once when the results are available as - * {@link #EXTRA_CHECKSUMS} of type {@link ApkChecksum}[]. + * @param onChecksumsReadyListener called once when the results are available. * @throws CertificateEncodingException if an encoding error occurs for trustedInstallers. * @throws IllegalArgumentException if the list of trusted installer certificates is empty. * @throws NameNotFoundException if a package with the given name cannot be found on the system. */ public void requestChecksums(@NonNull String packageName, boolean includeSplits, @Checksum.Type int required, @NonNull List<Certificate> trustedInstallers, - @NonNull IntentSender statusReceiver) + @NonNull OnChecksumsReadyListener onChecksumsReadyListener) throws CertificateEncodingException, NameNotFoundException { throw new UnsupportedOperationException("requestChecksums not implemented in subclass"); } diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl index 84a2acc165c4..b016ed67c4d9 100644 --- a/core/java/android/net/INetworkPolicyManager.aidl +++ b/core/java/android/net/INetworkPolicyManager.aidl @@ -82,4 +82,5 @@ interface INetworkPolicyManager { boolean isUidNetworkingBlocked(int uid, boolean meteredNetwork); boolean isUidRestrictedOnMeteredNetworks(int uid); + boolean checkUidNetworkingBlocked(int uid, int uidRules, boolean isNetworkMetered, boolean isBackgroundRestricted); } diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java index ed169e75bd37..3e6237d99011 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -464,6 +464,31 @@ public class NetworkPolicyManager { } /** + * Figure out if networking is blocked for a given set of conditions. + * + * This is used by ConnectivityService via passing stale copies of conditions, so it must not + * take any locks. + * + * @param uid The target uid. + * @param uidRules The uid rules which are obtained from NetworkPolicyManagerService. + * @param isNetworkMetered True if the network is metered. + * @param isBackgroundRestricted True if data saver is enabled. + * + * @return true if networking is blocked for the UID under the specified conditions. + * + * @hide + */ + public boolean checkUidNetworkingBlocked(int uid, int uidRules, + boolean isNetworkMetered, boolean isBackgroundRestricted) { + try { + return mService.checkUidNetworkingBlocked(uid, uidRules, isNetworkMetered, + isBackgroundRestricted); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Check that the given uid is restricted from doing networking on metered networks. * * @param uid The target uid. diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 77183ac21d09..ea1ce3728365 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -1694,10 +1694,13 @@ public class UserManager { } /** - * @hide - * @return Whether the device is running in a headless system user mode. It means the headless - * user (system user) runs system services and system UI, but is not associated with any real - * person. Secondary users can be created to be associated with real person. + * Checks whether the device is running in a headless system user mode. + * + * <p>Headless system user mode means the {@link #isSystemUser() system user} runs system + * services and some system UI, but it is not associated with any real person and additional + * users must be created to be associated with real persons. + * + * @return whether the device is running in a headless system user mode. */ public static boolean isHeadlessSystemUserMode() { return RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER; diff --git a/core/java/android/service/storage/ExternalStorageService.java b/core/java/android/service/storage/ExternalStorageService.java index 0123c368583c..a750b689ee02 100644 --- a/core/java/android/service/storage/ExternalStorageService.java +++ b/core/java/android/service/storage/ExternalStorageService.java @@ -158,7 +158,7 @@ public abstract class ExternalStorageService extends Service { * @param volumeUuid uuid of the {@link StorageVolume} from which cache needs to be freed * @param bytes number of bytes which need to be freed */ - public void onFreeCacheRequested(@NonNull UUID volumeUuid, @BytesLong long bytes) { + public void onFreeCache(@NonNull UUID volumeUuid, @BytesLong long bytes) throws IOException { throw new UnsupportedOperationException("onFreeCacheRequested not implemented"); } @@ -202,7 +202,7 @@ public abstract class ExternalStorageService extends Service { RemoteCallback callback) { mHandler.post(() -> { try { - onFreeCacheRequested(StorageManager.convert(volumeUuid), bytes); + onFreeCache(StorageManager.convert(volumeUuid), bytes); sendResult(sessionId, null /* throwable */, callback); } catch (Throwable t) { sendResult(sessionId, t, callback); diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 41680647ad57..0ba1dfee16f3 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -25,8 +25,8 @@ import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.TestApi; import android.app.KeyguardManager; -import android.app.WindowConfiguration; import android.compat.annotation.UnsupportedAppUsage; +import android.content.Context; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.content.res.Resources; @@ -59,8 +59,12 @@ import java.util.List; * an application window, excluding the system decorations. The application display area may * be smaller than the real display area because the system subtracts the space needed * for decor elements such as the status bar. Use {@link WindowMetrics#getBounds()} to query the - * application window bounds. Generally, use {@link WindowManager#getCurrentWindowMetrics()} to - * query the metrics and perform UI-related actions.</li> + * application window bounds.</li> + * <li>The real display area specifies the part of the display that contains content + * including the system decorations. Even so, the real display area may be smaller than the + * physical size of the display if the window manager is emulating a smaller display + * using (adb shell wm size). Use the following methods to query the + * real display area: {@link #getRealSize}, {@link #getRealMetrics}.</li> * </ul> * </p><p> * A logical display does not necessarily represent a particular physical display device @@ -673,9 +677,9 @@ public final class Display { @UnsupportedAppUsage public DisplayAdjustments getDisplayAdjustments() { if (mResources != null) { - final DisplayAdjustments currentAdjustments = mResources.getDisplayAdjustments(); - if (!mDisplayAdjustments.equals(currentAdjustments)) { - mDisplayAdjustments = new DisplayAdjustments(currentAdjustments); + final DisplayAdjustments currentAdjustements = mResources.getDisplayAdjustments(); + if (!mDisplayAdjustments.equals(currentAdjustements)) { + mDisplayAdjustments = new DisplayAdjustments(currentAdjustements); } } @@ -1213,34 +1217,30 @@ public final class Display { } /** - * Provides the largest {@link Point outSize} an app may expect in the current system state, - * without subtracting any window decor. + * Gets the real size of the display without subtracting any window decor or + * applying any compatibility scale factors. * <p> - * The size describes the largest potential area the window might occupy. The size is adjusted - * based on the current rotation of the display. + * The size is adjusted based on the current rotation of the display. * </p><p> * The real size may be smaller than the physical size of the screen when the * window manager is emulating a smaller display (using adb shell wm size). - * </p> + * </p><p> + * In general, {@link #getRealSize(Point)} and {@link WindowManager#getMaximumWindowMetrics()} + * report the same bounds except that certain areas of the display may not be available to + * windows created in the {@link WindowManager}'s {@link Context}. + * + * For example, imagine a device which has a multi-task mode that limits windows to half of the + * screen. In this case, {@link WindowManager#getMaximumWindowMetrics()} reports the + * bounds of the screen half where the window is located, while {@link #getRealSize(Point)} + * still reports the bounds of the whole display. * * @param outSize Set to the real size of the display. + * + * @see WindowManager#getMaximumWindowMetrics() */ public void getRealSize(Point outSize) { synchronized (this) { updateDisplayInfoLocked(); - if (shouldReportMaxBounds()) { - final Rect bounds = mResources.getConfiguration() - .windowConfiguration.getMaxBounds(); - outSize.x = bounds.width(); - outSize.y = bounds.height(); - if (DEBUG) { - Log.d(TAG, "getRealSize determined from max bounds: " + outSize - + " for uid " + Process.myUid()); - } - // Skip adjusting by fixed rotation, since if it is necessary, the configuration - // should already reflect the expected rotation. - return; - } outSize.x = mDisplayInfo.logicalWidth; outSize.y = mDisplayInfo.logicalHeight; if (mMayAdjustByFixedRotation) { @@ -1250,11 +1250,9 @@ public final class Display { } /** - * Provides the largest {@link DisplayMetrics outMetrics} an app may expect in the current - * system state, without subtracting any window decor. + * Gets display metrics based on the real size of this display. * <p> - * The size describes the largest potential area the window might occupy. The size is adjusted - * based on the current rotation of the display. + * The size is adjusted based on the current rotation of the display. * </p><p> * The real size may be smaller than the physical size of the screen when the * window manager is emulating a smaller display (using adb shell wm size). @@ -1265,18 +1263,6 @@ public final class Display { public void getRealMetrics(DisplayMetrics outMetrics) { synchronized (this) { updateDisplayInfoLocked(); - if (shouldReportMaxBounds()) { - mDisplayInfo.getMaxBoundsMetrics(outMetrics, - CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, - mResources.getConfiguration()); - if (DEBUG) { - Log.d(TAG, "getRealMetrics determined from max bounds: " + outMetrics - + " for uid " + Process.myUid()); - } - // Skip adjusting by fixed rotation, since if it is necessary, the configuration - // should already reflect the expected rotation. - return; - } mDisplayInfo.getLogicalMetrics(outMetrics, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); if (mMayAdjustByFixedRotation) { @@ -1286,20 +1272,6 @@ public final class Display { } /** - * Determines if {@link WindowConfiguration#getMaxBounds()} should be reported as the - * display dimensions. The max bounds field may be smaller than the logical dimensions - * when apps need to be sandboxed. - * @return {@code true} when max bounds should be applied. - */ - private boolean shouldReportMaxBounds() { - if (mResources == null) { - return false; - } - final Configuration config = mResources.getConfiguration(); - return config != null && !config.windowConfiguration.getMaxBounds().isEmpty(); - } - - /** * Gets the state of the display, such as whether it is on or off. * * @return The state of the display: one of {@link #STATE_OFF}, {@link #STATE_ON}, diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java index 8a445041a1f2..2a00b5a2e513 100644 --- a/core/java/android/view/DisplayInfo.java +++ b/core/java/android/view/DisplayInfo.java @@ -24,7 +24,6 @@ import static android.view.DisplayInfoProto.LOGICAL_WIDTH; import static android.view.DisplayInfoProto.NAME; import android.annotation.Nullable; -import android.app.WindowConfiguration; import android.compat.annotation.UnsupportedAppUsage; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; @@ -616,29 +615,11 @@ public final class DisplayInfo implements Parcelable { getMetricsWithSize(outMetrics, ci, configuration, appWidth, appHeight); } - /** - * Populates {@code outMetrics} with details of the logical display. Bounds are limited - * by the logical size of the display. - * - * @param outMetrics the {@link DisplayMetrics} to be populated - * @param compatInfo the {@link CompatibilityInfo} to be applied - * @param configuration the {@link Configuration} - */ public void getLogicalMetrics(DisplayMetrics outMetrics, CompatibilityInfo compatInfo, Configuration configuration) { getMetricsWithSize(outMetrics, compatInfo, configuration, logicalWidth, logicalHeight); } - /** - * Similar to {@link #getLogicalMetrics}, but the limiting bounds are determined from - * {@link WindowConfiguration#getMaxBounds()} - */ - public void getMaxBoundsMetrics(DisplayMetrics outMetrics, CompatibilityInfo compatInfo, - Configuration configuration) { - Rect bounds = configuration.windowConfiguration.getMaxBounds(); - getMetricsWithSize(outMetrics, compatInfo, configuration, bounds.width(), bounds.height()); - } - public int getNaturalWidth() { return rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180 ? logicalWidth : logicalHeight; diff --git a/core/java/android/view/IRemoteAnimationRunner.aidl b/core/java/android/view/IRemoteAnimationRunner.aidl index 423e23d2bc08..1f64fb8ca2ec 100644 --- a/core/java/android/view/IRemoteAnimationRunner.aidl +++ b/core/java/android/view/IRemoteAnimationRunner.aidl @@ -30,11 +30,15 @@ oneway interface IRemoteAnimationRunner { /** * Called when the process needs to start the remote animation. * + * @param transition The old transition type. Must be one of WindowManager.TRANSIT_OLD_* values. * @param apps The list of apps to animate. + * @param wallpapers The list of wallpapers to animate. + * @param nonApps The list of non-app windows such as Bubbles to animate. * @param finishedCallback The callback to invoke when the animation is finished. */ @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) - void onAnimationStart(in RemoteAnimationTarget[] apps, in RemoteAnimationTarget[] wallpapers, + void onAnimationStart(int transit, in RemoteAnimationTarget[] apps, + in RemoteAnimationTarget[] wallpapers, in RemoteAnimationTarget[] nonApps, in IRemoteAnimationFinishedCallback finishedCallback); /** diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 784341153d69..ae8afca9b5c5 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -117,7 +117,7 @@ interface IWindowManager // These can only be called when holding the MANAGE_APP_TOKENS permission. void setEventDispatching(boolean enabled); - /** @return {@code true} if this binder is a registered window token. */ + /** Returns {@code true} if this binder is a registered window token. */ boolean isWindowToken(in IBinder binder); /** * Adds window token for a given type. diff --git a/core/java/android/view/InsetsAnimationThreadControlRunner.java b/core/java/android/view/InsetsAnimationThreadControlRunner.java index 0939336132a8..6a34a1520fa3 100644 --- a/core/java/android/view/InsetsAnimationThreadControlRunner.java +++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java @@ -111,6 +111,9 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro mControl = new InsetsAnimationControlImpl(controls, frame, state, listener, types, mCallbacks, durationMs, interpolator, animationType, translator); InsetsAnimationThread.getHandler().post(() -> { + if (mControl.isCancelled()) { + return; + } Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "InsetsAsyncAnimation: " + WindowInsets.Type.toString(types), types); listener.onReady(mControl, types); diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index fe6b6e4d6fd1..219190f554ea 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -604,13 +604,13 @@ public class InsetsState implements Parcelable { return Type.CAPTION_BAR; case ITYPE_IME: return Type.IME; - case ITYPE_TOP_GESTURES: - case ITYPE_BOTTOM_GESTURES: case ITYPE_TOP_MANDATORY_GESTURES: case ITYPE_BOTTOM_MANDATORY_GESTURES: case ITYPE_LEFT_MANDATORY_GESTURES: case ITYPE_RIGHT_MANDATORY_GESTURES: return Type.MANDATORY_SYSTEM_GESTURES; + case ITYPE_TOP_GESTURES: + case ITYPE_BOTTOM_GESTURES: case ITYPE_LEFT_GESTURES: case ITYPE_RIGHT_GESTURES: return Type.SYSTEM_GESTURES; diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 18ef80ce9772..036a703f178c 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -315,7 +315,7 @@ public final class ViewRootImpl implements ViewParent, * In that case we receive a call back from {@link ActivityThread} and this flag is used to * preserve the initial value. * - * @see #performConfigurationChange(Configuration, Configuration, boolean, int) + * @see #performConfigurationChange(MergedConfiguration, boolean, int) */ private boolean mForceNextConfigUpdate; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 269ccbbbfaec..dc81bc9d449d 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -690,10 +690,6 @@ <!-- Made protected in S (was added in R) --> <protected-broadcast android:name="com.android.internal.intent.action.BUGREPORT_REQUESTED" /> - <!-- Added in S --> - <protected-broadcast android:name="android.app.action.MANAGED_PROFILE_CREATED" /> - <protected-broadcast android:name="android.app.action.PROVISIONED_MANAGED_DEVICE" /> - <!-- ====================================================================== --> <!-- RUNTIME PERMISSIONS --> <!-- ====================================================================== --> @@ -2539,7 +2535,7 @@ <permission android:name="android.permission.REAL_GET_TASKS" android:protectionLevel="signature|privileged" /> - <!-- Allows an application to start a task from a ActivityManager#RecentTaskInfo. + <!-- @TestApi Allows an application to start a task from a ActivityManager#RecentTaskInfo. @hide --> <permission android:name="android.permission.START_TASKS_FROM_RECENTS" android:protectionLevel="signature|privileged|recents" /> diff --git a/core/res/res/color-car/car_borderless_button_text_color.xml b/core/res/res/color-car/car_borderless_button_text_color.xml index 1cdd6cd901af..0a86e4012e99 100644 --- a/core/res/res/color-car/car_borderless_button_text_color.xml +++ b/core/res/res/color-car/car_borderless_button_text_color.xml @@ -16,5 +16,6 @@ limitations under the License. <!-- Default text colors for car buttons when enabled/disabled. --> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:color="@*android:color/car_grey_700" android:state_enabled="false"/> + <item android:color="@*android:color/car_grey_700" android:state_ux_restricted="true"/> <item android:color="?android:attr/colorButtonNormal"/> </selector> diff --git a/core/res/res/color-car/car_switch_track.xml b/core/res/res/color-car/car_switch_track.xml new file mode 100644 index 000000000000..8ca67dd8dda9 --- /dev/null +++ b/core/res/res/color-car/car_switch_track.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2014 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. +--> +<!-- copy of switch_track_material, but with a ux restricted state --> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false" + android:color="?attr/colorForeground" + android:alpha="?attr/disabledAlpha" /> + <item android:state_ux_restricted="true" + android:color="?attr/colorForeground" + android:alpha="?attr/disabledAlpha" /> + <item android:state_checked="true" + android:color="?attr/colorControlActivated" /> + <item android:color="?attr/colorForeground" /> +</selector> diff --git a/core/res/res/drawable-car/car_button_background.xml b/core/res/res/drawable-car/car_button_background.xml index e568aebfe81d..13b0ec13f355 100644 --- a/core/res/res/drawable-car/car_button_background.xml +++ b/core/res/res/drawable-car/car_button_background.xml @@ -25,6 +25,22 @@ limitations under the License. android:color="#0059B3"/> </shape> </item> + <item android:state_focused="true" android:state_pressed="true" android:state_ux_restricted="true"> + <shape android:shape="rectangle"> + <corners android:radius="@*android:dimen/car_button_radius"/> + <solid android:color="@*android:color/car_grey_300"/> + <stroke android:width="4dp" + android:color="#0059B3"/> + </shape> + </item> + <item android:state_focused="true" android:state_ux_restricted="true"> + <shape android:shape="rectangle"> + <corners android:radius="@*android:dimen/car_button_radius"/> + <solid android:color="@*android:color/car_grey_300"/> + <stroke android:width="8dp" + android:color="#0059B3"/> + </shape> + </item> <item android:state_focused="true" android:state_pressed="true"> <shape android:shape="rectangle"> <corners android:radius="@*android:dimen/car_button_radius"/> @@ -47,6 +63,12 @@ limitations under the License. <solid android:color="@*android:color/car_grey_300"/> </shape> </item> + <item android:state_ux_restricted="true"> + <shape android:shape="rectangle"> + <corners android:radius="@*android:dimen/car_button_radius"/> + <solid android:color="@*android:color/car_grey_300"/> + </shape> + </item> <item> <ripple android:color="?android:attr/colorControlHighlight"> <item> diff --git a/core/res/res/drawable-car/car_switch_track.xml b/core/res/res/drawable-car/car_switch_track.xml index cb0b9beeeab6..51e9f7eb4ebc 100644 --- a/core/res/res/drawable-car/car_switch_track.xml +++ b/core/res/res/drawable-car/car_switch_track.xml @@ -41,7 +41,7 @@ android:right="@dimen/car_switch_track_margin_size"> <shape android:shape="rectangle" - android:tint="@color/switch_track_material"> + android:tint="@color/car_switch_track"> <corners android:radius="7dp" /> <solid android:color="@color/white_disabled_material" /> <size android:height="14dp" /> diff --git a/core/res/res/values/attrs_car.xml b/core/res/res/values/attrs_car.xml new file mode 100644 index 000000000000..6bfea9728b69 --- /dev/null +++ b/core/res/res/values/attrs_car.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<!-- Formatting note: terminate all comments with a period, to avoid breaking + the documentation output. To suppress comment lines from the documentation + output, insert an eat-comment element after the comment lines. +--> + +<resources> + <attr name="state_ux_restricted" format="boolean"/> +</resources> diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index bb826deb4eff..f31233b29cd0 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -147,6 +147,9 @@ <!-- WindowMetricsTest permissions --> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> + <!-- WindowContextTest permissions --> + <uses-permission android:name="android.permission.MANAGE_APP_TOKENS" /> + <!-- Allow use of PendingIntent.getIntent() --> <uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT" /> diff --git a/core/tests/coretests/src/android/app/WindowContextTest.java b/core/tests/coretests/src/android/app/WindowContextTest.java index dcf5e025f82b..da7304efbd3d 100644 --- a/core/tests/coretests/src/android/app/WindowContextTest.java +++ b/core/tests/coretests/src/android/app/WindowContextTest.java @@ -18,29 +18,38 @@ package android.app; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; +import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.content.Context; +import android.content.Intent; import android.hardware.display.DisplayManager; +import android.os.Binder; import android.os.IBinder; import android.platform.test.annotations.Presubmit; import android.view.Display; import android.view.IWindowManager; import android.view.View; import android.view.WindowManager; +import android.view.WindowManager.LayoutParams.WindowType; import android.view.WindowManagerGlobal; import android.view.WindowManagerImpl; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.rule.ActivityTestRule; import androidx.test.runner.AndroidJUnit4; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + /** * Tests for {@link WindowContext} * @@ -54,41 +63,156 @@ import org.junit.runner.RunWith; @SmallTest @Presubmit public class WindowContextTest { + @Rule + public ActivityTestRule<EmptyActivity> mActivityRule = + new ActivityTestRule<>(EmptyActivity.class, false /* initialTouchMode */, + false /* launchActivity */); + private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation(); private final WindowContext mWindowContext = createWindowContext(); + private final IWindowManager mWms = WindowManagerGlobal.getWindowManagerService(); @Test - public void testWindowContextRelease_doRemoveWindowToken() throws Throwable { + public void testCreateWindowContextWindowManagerAttachClientToken() { + final WindowManager windowContextWm = WindowManagerImpl + .createWindowContextWindowManager(mWindowContext); + final WindowManager.LayoutParams params = + new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY); + mInstrumentation.runOnMainSync(() -> { + final View view = new View(mWindowContext); + windowContextWm.addView(view, params); + }); + + assertEquals(mWindowContext.getWindowContextToken(), params.mWindowContextToken); + } + + /** + * Test the {@link WindowContext} life cycle behavior to add a new window token: + * <ul> + * <li>The window token is created before adding the first view.</li> + * <li>The window token is registered after adding the first view.</li> + * <li>The window token is removed after {@link WindowContext}'s release.</li> + * </ul> + */ + @Test + public void testCreateWindowContextNewTokenFromClient() throws Throwable { final IBinder token = mWindowContext.getWindowContextToken(); - final IWindowManager wms = WindowManagerGlobal.getWindowManagerService(); - assertTrue("Token must be registered to WMS", wms.isWindowToken(token)); + // Test that the window token is not created yet. + assertFalse("Token must not be registered until adding the first window", + mWms.isWindowToken(token)); + + final WindowManager.LayoutParams params = + new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY); + final View testView = new View(mWindowContext); + + final CountDownLatch latch = new CountDownLatch(1); + testView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { + latch.countDown(); + } + + @Override + public void onViewDetachedFromWindow(View v) {} + }); + + mInstrumentation.runOnMainSync(() -> { + mWindowContext.getSystemService(WindowManager.class).addView(testView, params); + + assertEquals(token, params.mWindowContextToken); + }); + + + assertTrue(latch.await(4, TimeUnit.SECONDS)); + + + // Verify that the window token of the window context is created after first addView(). + assertTrue("Token must exist after adding the first view.", + mWms.isWindowToken(token)); mWindowContext.release(); - assertFalse("Token must be unregistered to WMS", wms.isWindowToken(token)); + // After the window context's release, the window token is also removed. + assertFalse("Token must be removed after release.", mWms.isWindowToken(token)); } + /** + * Verifies the behavior when window context attaches an {@link Activity} by override + * {@link WindowManager.LayoutParams#token}. + * + * The window context token should be overridden to + * {@link android.view.WindowManager.LayoutParams} and the {@link Activity}'s token must + * not be removed regardless of the release of window context. + */ @Test - public void testCreateWindowContextWindowManagerAttachClientToken() { - final WindowManager windowContextWm = WindowManagerImpl - .createWindowContextWindowManager(mWindowContext); + public void testCreateWindowContext_AttachActivity_TokenNotRemovedAfterRelease() + throws Throwable { + mActivityRule.launchActivity(new Intent()); + final Activity activity = mActivityRule.getActivity(); + final WindowManager.LayoutParams params = activity.getWindow().getAttributes(); + + final WindowContext windowContext = createWindowContext(params.type); + final IBinder token = windowContext.getWindowContextToken(); + + final View testView = new View(windowContext); + + mInstrumentation.runOnMainSync(() -> { + windowContext.getSystemService(WindowManager.class).addView(testView, params); + + assertEquals(token, params.mWindowContextToken); + }); + windowContext.release(); + + // Even if the window context is released, the activity should still exist. + assertTrue("Token must exist even if the window context is released.", + mWms.isWindowToken(activity.getActivityToken())); + } + + /** + * Verifies the behavior when window context attaches an existing token by override + * {@link WindowManager.LayoutParams#token}. + * + * The window context token should be overridden to + * {@link android.view.WindowManager.LayoutParams} and the {@link Activity}'s token must not be + * removed regardless of release of window context. + */ + @Test + public void testCreateWindowContext_AttachWindowToken_TokenNotRemovedAfterRelease() + throws Throwable { + final WindowContext windowContext = createWindowContext(TYPE_INPUT_METHOD); + final IBinder token = windowContext.getWindowContextToken(); + + final IBinder existingToken = new Binder(); + mWms.addWindowToken(existingToken, TYPE_INPUT_METHOD, windowContext.getDisplayId()); + final WindowManager.LayoutParams params = - new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY); + new WindowManager.LayoutParams(TYPE_INPUT_METHOD); + params.token = existingToken; + final View testView = new View(windowContext); + mInstrumentation.runOnMainSync(() -> { - final View view = new View(mWindowContext); - windowContextWm.addView(view, params); + windowContext.getSystemService(WindowManager.class).addView(testView, params); + + assertEquals(token, params.mWindowContextToken); }); + windowContext.release(); - assertEquals(mWindowContext.getWindowContextToken(), params.mWindowContextToken); + // Even if the window context is released, the existing token should still exist. + assertTrue("Token must exist even if the window context is released.", + mWms.isWindowToken(existingToken)); + + mWms.removeWindowToken(existingToken, DEFAULT_DISPLAY); } private WindowContext createWindowContext() { + return createWindowContext(TYPE_APPLICATION_OVERLAY); + } + + private WindowContext createWindowContext(@WindowType int type) { final Context instContext = mInstrumentation.getTargetContext(); final Display display = instContext.getSystemService(DisplayManager.class) .getDisplay(DEFAULT_DISPLAY); - final Context context = instContext.createDisplayContext(display); - return new WindowContext(context, TYPE_APPLICATION_OVERLAY, - null /* options */); + return (WindowContext) instContext.createWindowContext(display, type, null /* options */); } } diff --git a/core/tests/mockingcoretests/src/android/view/DisplayTests.java b/core/tests/mockingcoretests/src/android/view/DisplayTests.java deleted file mode 100644 index 5a3ea35b1194..000000000000 --- a/core/tests/mockingcoretests/src/android/view/DisplayTests.java +++ /dev/null @@ -1,527 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view; - -import static android.view.Display.DEFAULT_DISPLAY; -import static android.view.Surface.ROTATION_0; -import static android.view.Surface.ROTATION_90; - -import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; - -import static com.google.common.truth.Truth.assertThat; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Point; -import android.graphics.Rect; -import android.hardware.display.DisplayManagerGlobal; -import android.util.DisplayMetrics; -import android.view.DisplayAdjustments.FixedRotationAdjustments; - -import androidx.test.core.app.ApplicationProvider; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import com.android.dx.mockito.inline.extended.StaticMockitoSession; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mockito; -import org.mockito.quality.Strictness; - -import java.util.function.Consumer; - -/** - * Tests for {@link Display}. - * - * <p>Build/Install/Run: - * - * atest FrameworksMockingCoreTests:android.view.DisplayTests - */ -@RunWith(AndroidJUnit4.class) -public class DisplayTests { - - private static final int APP_WIDTH = 272; - private static final int APP_HEIGHT = 700; - // Tablet size device, ROTATION_0 corresponds to portrait. - private static final int LOGICAL_WIDTH = 700; - private static final int LOGICAL_HEIGHT = 1800; - - // Bounds of the app when the device is in portrait mode. - private static Rect sAppBoundsPortrait = buildAppBounds(LOGICAL_WIDTH, LOGICAL_HEIGHT); - private static Rect sAppBoundsLandscape = buildAppBounds(LOGICAL_HEIGHT, LOGICAL_WIDTH); - - private StaticMockitoSession mMockitoSession; - - private DisplayManagerGlobal mDisplayManagerGlobal; - private Context mApplicationContext; - private DisplayInfo mDisplayInfo = new DisplayInfo(); - - @Before - public void setupTests() { - mMockitoSession = mockitoSession() - .mockStatic(DisplayManagerGlobal.class) - .strictness(Strictness.LENIENT) - .startMocking(); - - // Ensure no adjustments are set before each test. - mApplicationContext = ApplicationProvider.getApplicationContext(); - DisplayAdjustments displayAdjustments = - mApplicationContext.getResources().getDisplayAdjustments(); - displayAdjustments.setFixedRotationAdjustments(null); - mApplicationContext.getResources().overrideDisplayAdjustments(null); - mApplicationContext.getResources().getConfiguration().windowConfiguration.setAppBounds( - null); - mApplicationContext.getResources().getConfiguration().windowConfiguration.setMaxBounds( - null); - mDisplayInfo.rotation = ROTATION_0; - - mDisplayManagerGlobal = mock(DisplayManagerGlobal.class); - doReturn(mDisplayInfo).when(mDisplayManagerGlobal).getDisplayInfo(anyInt()); - } - - @After - public void teardownTests() { - if (mMockitoSession != null) { - mMockitoSession.finishMocking(); - } - Mockito.framework().clearInlineMocks(); - } - - @Test - public void testConstructor_defaultDisplayAdjustments_matchesDisplayInfo() { - setDisplayInfoPortrait(mDisplayInfo); - final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, - DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); - assertThat(display.getDisplayAdjustments()).isEqualTo( - DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); - DisplayInfo actualDisplayInfo = new DisplayInfo(); - display.getDisplayInfo(actualDisplayInfo); - verifyDisplayInfo(actualDisplayInfo, mDisplayInfo); - } - - @Test - public void testConstructor_defaultResources_matchesDisplayInfo() { - setDisplayInfoPortrait(mDisplayInfo); - final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, - mApplicationContext.getResources()); - assertThat(display.getDisplayAdjustments()).isEqualTo( - mApplicationContext.getResources().getDisplayAdjustments()); - DisplayInfo actualDisplayInfo = new DisplayInfo(); - display.getDisplayInfo(actualDisplayInfo); - verifyDisplayInfo(actualDisplayInfo, mDisplayInfo); - } - - @Test - public void testGetRotation_defaultDisplayAdjustments_rotationNotAdjusted() { - setDisplayInfoPortrait(mDisplayInfo); - final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, - DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); - assertThat(display.getRotation()).isEqualTo(ROTATION_0); - } - - @Test - public void testGetRotation_displayAdjustmentsWithoutOverride_rotationNotAdjusted() { - // GIVEN display is not rotated. - setDisplayInfoPortrait(mDisplayInfo); - // GIVEN fixed rotation adjustments are rotated, but no override is set. - DisplayAdjustments displayAdjustments = DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS; - final FixedRotationAdjustments fixedRotationAdjustments = - new FixedRotationAdjustments(ROTATION_90, APP_WIDTH, APP_HEIGHT, - DisplayCutout.NO_CUTOUT); - displayAdjustments.setFixedRotationAdjustments(fixedRotationAdjustments); - // GIVEN display is constructed with display adjustments. - final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, - displayAdjustments); - // THEN rotation is not adjusted since no override was set. - assertThat(display.getRotation()).isEqualTo(ROTATION_0); - } - - @Test - public void testGetRotation_resourcesWithoutOverride_rotationNotAdjusted() { - // GIVEN display is not rotated. - setDisplayInfoPortrait(mDisplayInfo); - // GIVEN fixed rotation adjustments are rotated, but no override is set. - setFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_90); - // GIVEN display is constructed with default resources. - final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, - mApplicationContext.getResources()); - // THEN rotation is not adjusted since no override is set. - assertThat(display.getRotation()).isEqualTo(ROTATION_0); - } - - @Test - public void testGetRotation_resourcesWithOverrideDisplayAdjustments_rotationAdjusted() { - // GIVEN display is not rotated. - setDisplayInfoPortrait(mDisplayInfo); - // GIVEN fixed rotation adjustments are rotated, and an override is set. - setOverrideFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_90); - // GIVEN display is constructed with default resources. - final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, - mApplicationContext.getResources()); - // THEN rotation is adjusted since an override is set. - assertThat(display.getRotation()).isEqualTo(ROTATION_90); - } - - @Test - public void testGetRealSize_defaultResourcesPortrait_matchesLogicalSize() { - // GIVEN display is not rotated. - setDisplayInfoPortrait(mDisplayInfo); - final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, - mApplicationContext.getResources()); - // THEN real size matches display orientation. - verifyRealSizeIsPortrait(display); - } - - @Test - public void testGetRealSize_defaultResourcesLandscape_matchesRotatedLogicalSize() { - // GIVEN display is rotated. - setDisplayInfoLandscape(mDisplayInfo); - final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, - mApplicationContext.getResources()); - // THEN real size matches display orientation. - verifyRealSizeIsLandscape(display); - } - - @Test - public void testGetRealSize_defaultDisplayAdjustmentsPortrait_matchesLogicalSize() { - // GIVEN display is not rotated. - setDisplayInfoPortrait(mDisplayInfo); - final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, - DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); - // THEN real size matches display orientation. - verifyRealSizeIsPortrait(display); - } - - @Test - public void testGetRealSize_defaultDisplayAdjustmentsLandscape_matchesLogicalSize() { - // GIVEN display is rotated. - setDisplayInfoLandscape(mDisplayInfo); - final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, - DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); - // THEN real size matches display orientation. - verifyRealSizeIsLandscape(display); - } - - @Test - public void testGetRealSize_resourcesPortraitWithFixedRotation_notRotatedLogicalSize() { - // GIVEN display is rotated. - setDisplayInfoLandscape(mDisplayInfo); - // GIVEN fixed rotation adjustments are rotated. - setFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_0); - // GIVEN display is constructed with default resources. - final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, - mApplicationContext.getResources()); - // THEN real size matches display orientation. - verifyRealSizeIsLandscape(display); - } - - @Test - public void testGetRealSize_resourcesWithLandscapeFixedRotation_notRotatedLogicalSize() { - // GIVEN display is not rotated. - setDisplayInfoPortrait(mDisplayInfo); - // GIVEN fixed rotation adjustments are rotated. - setFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_90); - // GIVEN display is constructed with default resources. - final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, - mApplicationContext.getResources()); - // THEN real size matches display orientation. - verifyRealSizeIsPortrait(display); - } - - @Test - public void testGetRealSize_resourcesWithPortraitOverrideRotation_rotatedLogicalSize() { - // GIVEN display is rotated. - setDisplayInfoLandscape(mDisplayInfo); - // GIVEN fixed rotation adjustments are rotated, and an override is set. - setOverrideFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_0); - // GIVEN display is constructed with default resources. - final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, - mApplicationContext.getResources()); - // THEN real size matches app orientation. - verifyRealSizeIsPortrait(display); - } - - @Test - public void testGetRealSize_resourcesWithLandscapeOverrideRotation_rotatedLogicalSize() { - // GIVEN display is not rotated. - setDisplayInfoPortrait(mDisplayInfo); - // GIVEN fixed rotation adjustments are rotated, and an override is set. - setOverrideFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_90); - // GIVEN display is constructed with default resources. - final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, - mApplicationContext.getResources()); - // THEN real size matches app orientation. - verifyRealSizeIsLandscape(display); - } - - @Test - public void testGetRealSize_resourcesPortraitSandboxed_matchesSandboxBounds() { - // GIVEN display is not rotated. - setDisplayInfoPortrait(mDisplayInfo); - // GIVEN app is letterboxed. - setMaxBoundsSandboxedToMatchAppBounds(mApplicationContext.getResources(), - sAppBoundsPortrait); - final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, - mApplicationContext.getResources()); - // THEN real size matches app bounds. - verifyRealSizeMatchesApp(display, sAppBoundsPortrait); - } - - @Test - public void testGetRealSize_resourcesLandscapeSandboxed_matchesSandboxBounds() { - // GIVEN display is rotated. - setDisplayInfoLandscape(mDisplayInfo); - // GIVEN app is letterboxed. - setMaxBoundsSandboxedToMatchAppBounds(mApplicationContext.getResources(), - sAppBoundsLandscape); - final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, - mApplicationContext.getResources()); - // THEN real size matches app bounds. - verifyRealSizeMatchesApp(display, sAppBoundsLandscape); - } - - @Test - public void testGetRealMetrics_defaultResourcesPortrait_matchesLogicalSize() { - // GIVEN display is not rotated. - setDisplayInfoPortrait(mDisplayInfo); - final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, - mApplicationContext.getResources()); - // THEN real metrics matches display orientation. - verifyRealMetricsIsPortrait(display); - } - - @Test - public void testGetRealMetrics_defaultResourcesLandscape_matchesRotatedLogicalSize() { - // GIVEN display is rotated. - setDisplayInfoLandscape(mDisplayInfo); - final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, - mApplicationContext.getResources()); - // THEN real metrics matches display orientation. - verifyRealMetricsIsLandscape(display); - } - - @Test - public void testGetRealMetrics_defaultDisplayAdjustmentsPortrait_matchesLogicalSize() { - // GIVEN display is not rotated. - setDisplayInfoPortrait(mDisplayInfo); - final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, - DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); - // THEN real metrics matches display orientation. - verifyRealMetricsIsPortrait(display); - } - - @Test - public void testGetRealMetrics_defaultDisplayAdjustmentsLandscape_matchesLogicalSize() { - // GIVEN display is rotated. - setDisplayInfoLandscape(mDisplayInfo); - final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, - DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); - // THEN real metrics matches display orientation. - verifyRealMetricsIsLandscape(display); - } - - @Test - public void testGetRealMetrics_resourcesPortraitWithFixedRotation_notRotatedLogicalSize() { - // GIVEN display is rotated. - setDisplayInfoLandscape(mDisplayInfo); - // GIVEN fixed rotation adjustments are rotated. - setFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_0); - // GIVEN display is constructed with default resources. - final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, - mApplicationContext.getResources()); - // THEN real metrics matches display orientation. - verifyRealMetricsIsLandscape(display); - } - - @Test - public void testGetRealMetrics_resourcesWithLandscapeFixedRotation_notRotatedLogicalSize() { - // GIVEN display is not rotated. - setDisplayInfoPortrait(mDisplayInfo); - // GIVEN fixed rotation adjustments are rotated. - setFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_90); - // GIVEN display is constructed with default resources. - final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, - mApplicationContext.getResources()); - // THEN real metrics matches display orientation. - verifyRealMetricsIsPortrait(display); - } - - @Test - public void testGetRealMetrics_resourcesWithPortraitOverrideRotation_rotatedLogicalSize() { - // GIVEN display is rotated. - setDisplayInfoLandscape(mDisplayInfo); - // GIVEN fixed rotation adjustments are rotated with an override. - setOverrideFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_0); - // GIVEN display is constructed with default resources. - final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, - mApplicationContext.getResources()); - // THEN real metrics matches app orientation. - verifyRealMetricsIsPortrait(display); - } - - @Test - public void testGetRealMetrics_resourcesWithLandscapeOverrideRotation_rotatedLogicalSize() { - // GIVEN display is not rotated. - setDisplayInfoPortrait(mDisplayInfo); - // GIVEN fixed rotation adjustments are rotated. - setOverrideFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_90); - // GIVEN display is constructed with default resources. - final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, - mApplicationContext.getResources()); - // THEN real metrics matches app orientation. - verifyRealMetricsIsLandscape(display); - } - - @Test - public void testGetRealMetrics_resourcesPortraitSandboxed_matchesSandboxBounds() { - // GIVEN display is not rotated. - setDisplayInfoPortrait(mDisplayInfo); - // GIVEN app is letterboxed. - setMaxBoundsSandboxedToMatchAppBounds(mApplicationContext.getResources(), - sAppBoundsPortrait); - final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, - mApplicationContext.getResources()); - // THEN real metrics matches app bounds. - verifyRealMetricsMatchesApp(display, sAppBoundsPortrait); - } - - @Test - public void testGetRealMetrics_resourcesLandscapeSandboxed_matchesSandboxBounds() { - // GIVEN display is rotated. - setDisplayInfoLandscape(mDisplayInfo); - // GIVEN app is letterboxed. - setMaxBoundsSandboxedToMatchAppBounds(mApplicationContext.getResources(), - sAppBoundsLandscape); - final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, - mApplicationContext.getResources()); - // THEN real metrics matches app bounds. - verifyRealMetricsMatchesApp(display, sAppBoundsLandscape); - } - - // Given rotated display dimensions, calculate the letterboxed app bounds. - private static Rect buildAppBounds(int displayWidth, int displayHeight) { - final int midWidth = displayWidth / 2; - final int left = midWidth - (APP_WIDTH / 2); - final int right = midWidth + (APP_WIDTH / 2); - final int midHeight = displayHeight / 2; - // Coordinate system starts at top left. - final int top = midHeight - (APP_HEIGHT / 2); - final int bottom = midHeight + (APP_HEIGHT / 2); - return new Rect(left, top, right, bottom); - } - - private static void setDisplayInfoLandscape(DisplayInfo displayInfo) { - displayInfo.rotation = ROTATION_90; - // Flip width & height assignment since the device is rotated. - displayInfo.logicalWidth = LOGICAL_HEIGHT; - displayInfo.logicalHeight = LOGICAL_WIDTH; - } - - private static void setDisplayInfoPortrait(DisplayInfo displayInfo) { - displayInfo.rotation = ROTATION_0; - displayInfo.logicalWidth = LOGICAL_WIDTH; - displayInfo.logicalHeight = LOGICAL_HEIGHT; - } - - /** - * Set max bounds to be sandboxed to the app bounds, indicating the app is in - * size compat mode or letterbox. - */ - private static void setMaxBoundsSandboxedToMatchAppBounds(Resources resources, Rect appBounds) { - resources.getConfiguration().windowConfiguration.setMaxBounds(appBounds); - } - - /** - * Do not compare entire display info, since it is updated to match display the test is run on. - */ - private static void verifyDisplayInfo(DisplayInfo actual, DisplayInfo expected) { - assertThat(actual.displayId).isEqualTo(expected.displayId); - assertThat(actual.rotation).isEqualTo(expected.rotation); - assertThat(actual.logicalWidth).isEqualTo(LOGICAL_WIDTH); - assertThat(actual.logicalHeight).isEqualTo(LOGICAL_HEIGHT); - } - - private static void verifyRealSizeIsLandscape(Display display) { - Point size = new Point(); - display.getRealSize(size); - // Flip the width and height check since the device is rotated. - assertThat(size).isEqualTo(new Point(LOGICAL_HEIGHT, LOGICAL_WIDTH)); - } - - private static void verifyRealMetricsIsLandscape(Display display) { - DisplayMetrics metrics = new DisplayMetrics(); - display.getRealMetrics(metrics); - // Flip the width and height check since the device is rotated. - assertThat(metrics.widthPixels).isEqualTo(LOGICAL_HEIGHT); - assertThat(metrics.heightPixels).isEqualTo(LOGICAL_WIDTH); - } - - private static void verifyRealSizeIsPortrait(Display display) { - Point size = new Point(); - display.getRealSize(size); - assertThat(size).isEqualTo(new Point(LOGICAL_WIDTH, LOGICAL_HEIGHT)); - } - - private static void verifyRealMetricsIsPortrait(Display display) { - DisplayMetrics metrics = new DisplayMetrics(); - display.getRealMetrics(metrics); - assertThat(metrics.widthPixels).isEqualTo(LOGICAL_WIDTH); - assertThat(metrics.heightPixels).isEqualTo(LOGICAL_HEIGHT); - } - - private static void verifyRealSizeMatchesApp(Display display, Rect appBounds) { - Point size = new Point(); - display.getRealSize(size); - assertThat(size).isEqualTo(new Point(appBounds.width(), appBounds.height())); - } - - private static void verifyRealMetricsMatchesApp(Display display, Rect appBounds) { - DisplayMetrics metrics = new DisplayMetrics(); - display.getRealMetrics(metrics); - assertThat(metrics.widthPixels).isEqualTo(appBounds.width()); - assertThat(metrics.heightPixels).isEqualTo(appBounds.height()); - } - - private static FixedRotationAdjustments setOverrideFixedRotationAdjustments( - Resources resources, @Surface.Rotation int rotation) { - FixedRotationAdjustments fixedRotationAdjustments = - setFixedRotationAdjustments(resources, rotation); - resources.overrideDisplayAdjustments( - buildOverrideRotationAdjustments(fixedRotationAdjustments)); - return fixedRotationAdjustments; - } - - private static FixedRotationAdjustments setFixedRotationAdjustments(Resources resources, - @Surface.Rotation int rotation) { - final FixedRotationAdjustments fixedRotationAdjustments = - new FixedRotationAdjustments(rotation, APP_WIDTH, APP_HEIGHT, - DisplayCutout.NO_CUTOUT); - resources.getDisplayAdjustments().setFixedRotationAdjustments(fixedRotationAdjustments); - return fixedRotationAdjustments; - } - - private static Consumer<DisplayAdjustments> buildOverrideRotationAdjustments( - FixedRotationAdjustments fixedRotationAdjustments) { - return consumedDisplayAdjustments - -> consumedDisplayAdjustments.setFixedRotationAdjustments(fixedRotationAdjustments); - } -} diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index ac8a296123c6..222c9bdf2cb4 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -1903,6 +1903,12 @@ "group": "WM_DEBUG_FOCUS_LIGHT", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "123161180": { + "message": "SEVER CHILDREN", + "level": "INFO", + "group": "WM_SHOW_TRANSACTIONS", + "at": "com\/android\/server\/wm\/WindowSurfaceController.java" + }, "140319294": { "message": "IME target changed within ActivityRecord", "level": "DEBUG", @@ -2137,12 +2143,6 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, - "332390227": { - "message": "Sandbox max bounds for uid %s to bounds %s due to letterboxing? %s mismatch with parent bounds? %s size compat mode %s", - "level": "DEBUG", - "group": "WM_DEBUG_CONFIGURATION", - "at": "com\/android\/server\/wm\/ActivityRecord.java" - }, "342460966": { "message": "DRAG %s: pos=(%d,%d)", "level": "INFO", @@ -2623,12 +2623,6 @@ "group": "WM_DEBUG_WINDOW_ORGANIZER", "at": "com\/android\/server\/wm\/WindowOrganizerController.java" }, - "910200295": { - "message": "Sandbox max bounds due to mismatched orientation with parent, to %s vs DisplayArea %s", - "level": "DEBUG", - "group": "WM_DEBUG_CONFIGURATION", - "at": "com\/android\/server\/wm\/Task.java" - }, "913494177": { "message": "removeAllWindowsIfPossible: removing win=%s", "level": "WARN", diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java index a7d3f7980d37..5b79d76dd6b9 100644 --- a/graphics/java/android/graphics/ImageFormat.java +++ b/graphics/java/android/graphics/ImageFormat.java @@ -175,6 +175,39 @@ public class ImageFormat { public static final int Y16 = 0x20363159; /** + * <p>Android YUV P010 format.</p> + * + * P010 is a 4:2:0 YCbCr semiplanar format comprised of a WxH Y plane + * followed immediately by a Wx(H/2) CbCr plane. Each sample is + * represented by a 16-bit little-endian value, with the lower 6 bits set + * to zero. + * + * <p>This format assumes + * <ul> + * <li>an even height</li> + * <li>a vertical stride equal to the height</li> + * </ul> + * </p> + * + * <pre> stride_in_bytes = stride * 2 </pre> + * <pre> y_size = stride_in_bytes * height </pre> + * <pre> cbcr_size = stride_in_bytes * (height / 2) </pre> + * <pre> cb_offset = y_size </pre> + * <pre> cr_offset = cb_offset + 2 </pre> + * + * <p>For example, the {@link android.media.Image} object can provide data + * in this format from a {@link android.hardware.camera2.CameraDevice} + * through a {@link android.media.ImageReader} object if this format is + * supported by {@link android.hardware.camera2.CameraDevice}.</p> + * + * @see android.media.Image + * @see android.media.ImageReader + * @see android.hardware.camera2.CameraDevice + * + */ + public static final int YCBCR_P010 = 0x36; + + /** * YCbCr format, used for video. * * <p>For the {@link android.hardware.camera2} API, the {@link #YUV_420_888} format is @@ -807,6 +840,8 @@ public class ImageFormat { case RAW_DEPTH: case RAW_SENSOR: return 16; + case YCBCR_P010: + return 20; case RAW_DEPTH10: case RAW10: return 10; @@ -839,6 +874,7 @@ public class ImageFormat { case YUV_420_888: case YUV_422_888: case YUV_444_888: + case YCBCR_P010: case FLEX_RGB_888: case FLEX_RGBA_8888: case RAW_SENSOR: diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index 005a72661091..35e6b8595c2a 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -42,6 +42,7 @@ import android.provider.FontsContract; import android.system.ErrnoException; import android.system.OsConstants; import android.text.FontConfig; +import android.util.ArrayMap; import android.util.Base64; import android.util.LongSparseArray; import android.util.LruCache; @@ -67,7 +68,6 @@ import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -147,7 +147,7 @@ public class Typeface { */ @GuardedBy("SYSTEM_FONT_MAP_LOCK") @UnsupportedAppUsage(trackingBug = 123769347) - static final Map<String, Typeface> sSystemFontMap = new HashMap<>(); + static final Map<String, Typeface> sSystemFontMap = new ArrayMap<>(); // DirectByteBuffer object to hold sSystemFontMap's backing memory mapping. static ByteBuffer sSystemFontMapBuffer = null; @@ -1231,7 +1231,7 @@ public class Typeface { /** @hide */ @VisibleForTesting public static Map<String, Typeface> deserializeFontMap(ByteBuffer buffer) throws IOException { - Map<String, Typeface> fontMap = new HashMap<>(); + Map<String, Typeface> fontMap = new ArrayMap<>(); int typefacesBytesCount = buffer.getInt(); long[] nativePtrs = nativeReadTypefaces(buffer.slice()); if (nativePtrs == null) { diff --git a/graphics/java/android/graphics/fonts/Font.java b/graphics/java/android/graphics/fonts/Font.java index fea756c8290f..9214ff1eb088 100644 --- a/graphics/java/android/graphics/fonts/Font.java +++ b/graphics/java/android/graphics/fonts/Font.java @@ -486,7 +486,8 @@ public final class Font { long ptr; int fontIdentifier; if (mFont == null) { - ptr = nBuild(builderPtr, readonlyBuffer, filePath, mWeight, italic, mTtcIndex); + ptr = nBuild(builderPtr, readonlyBuffer, filePath, mLocaleList, mWeight, italic, + mTtcIndex); long fontBufferPtr = nGetFontBufferAddress(ptr); synchronized (SOURCE_ID_LOCK) { long id = FONT_SOURCE_ID_MAP.get(fontBufferPtr, -1); @@ -513,8 +514,8 @@ public final class Font { @CriticalNative private static native void nAddAxis(long builderPtr, int tag, float value); private static native long nBuild( - long builderPtr, @NonNull ByteBuffer buffer, @NonNull String filePath, int weight, - boolean italic, int ttcIndex); + long builderPtr, @NonNull ByteBuffer buffer, @NonNull String filePath, + @NonNull String localeList, int weight, boolean italic, int ttcIndex); @CriticalNative private static native long nGetReleaseNativeFont(); diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java index c29c194861f1..77f86fe726f3 100644 --- a/graphics/java/android/graphics/fonts/FontFamily.java +++ b/graphics/java/android/graphics/fonts/FontFamily.java @@ -20,15 +20,16 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.text.FontConfig; +import android.util.SparseIntArray; import com.android.internal.util.Preconditions; import dalvik.annotation.optimization.CriticalNative; +import dalvik.annotation.optimization.FastNative; import libcore.util.NativeAllocationRegistry; import java.util.ArrayList; -import java.util.HashSet; /** * A font family class can be used for creating Typeface. @@ -68,7 +69,9 @@ public final class FontFamily { nGetReleaseNativeFamily()); private final ArrayList<Font> mFonts = new ArrayList<>(); - private final HashSet<Integer> mStyleHashSet = new HashSet<>(); + // Most FontFamily only has regular, bold, italic, bold-italic. Thus 4 should be good for + // initial capacity. + private final SparseIntArray mStyles = new SparseIntArray(4); /** * Constructs a builder. @@ -77,7 +80,7 @@ public final class FontFamily { */ public Builder(@NonNull Font font) { Preconditions.checkNotNull(font, "font can not be null"); - mStyleHashSet.add(makeStyleIdentifier(font)); + mStyles.append(makeStyleIdentifier(font), 0); mFonts.add(font); } @@ -97,9 +100,11 @@ public final class FontFamily { */ public @NonNull Builder addFont(@NonNull Font font) { Preconditions.checkNotNull(font, "font can not be null"); - if (!mStyleHashSet.add(makeStyleIdentifier(font))) { + int key = makeStyleIdentifier(font); + if (mStyles.indexOfKey(key) >= 0) { throw new IllegalArgumentException(font + " has already been added"); } + mStyles.append(key, 0); mFonts.add(font); return this; } @@ -120,7 +125,7 @@ public final class FontFamily { nAddFont(builderPtr, mFonts.get(i).getNativePtr()); } final long ptr = nBuild(builderPtr, langTags, variant, isCustomFallback); - final FontFamily family = new FontFamily(mFonts, langTags, variant, ptr); + final FontFamily family = new FontFamily(mFonts, ptr); sFamilyRegistory.registerNativeAllocation(family, ptr); return family; } @@ -139,15 +144,11 @@ public final class FontFamily { } private final ArrayList<Font> mFonts; - private final String mLangTags; - private final int mVariant; private final long mNativePtr; // Use Builder instead. - private FontFamily(@NonNull ArrayList<Font> fonts, String langTags, int variant, long ptr) { + private FontFamily(@NonNull ArrayList<Font> fonts, long ptr) { mFonts = fonts; - mLangTags = langTags; - mVariant = variant; mNativePtr = ptr; } @@ -157,7 +158,7 @@ public final class FontFamily { * @return a BCP-47 compliant language tag. */ public @Nullable String getLangTags() { - return mLangTags; + return nGetLangTags(mNativePtr); } /** @@ -165,7 +166,7 @@ public final class FontFamily { * @return a family variant */ public int getVariant() { - return mVariant; + return nGetVariant(mNativePtr); } /** @@ -191,4 +192,10 @@ public final class FontFamily { public long getNativePtr() { return mNativePtr; } + + @FastNative + private static native String nGetLangTags(long family); + + @CriticalNative + private static native int nGetVariant(long family); } diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java index c166e12fc6bf..904085feb8cb 100644 --- a/graphics/java/android/graphics/fonts/SystemFonts.java +++ b/graphics/java/android/graphics/fonts/SystemFonts.java @@ -22,6 +22,7 @@ import android.graphics.FontListParser; import android.graphics.Typeface; import android.text.FontConfig; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Log; import com.android.internal.annotations.GuardedBy; @@ -36,8 +37,6 @@ import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -76,7 +75,7 @@ public final class SystemFonts { if (Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) { sAvailableFonts = collectAllFonts(); } else { - Set<Font> set = new HashSet<>(); + Set<Font> set = new ArraySet<>(); for (FontFamily[] items : sFamilyMap.values()) { for (FontFamily family : items) { for (int i = 0; i < family.getSize(); ++i) { @@ -96,7 +95,7 @@ public final class SystemFonts { FontConfig fontConfig = getSystemPreinstalledFontConfig(); Map<String, FontFamily[]> map = buildSystemFallback(fontConfig); - Set<Font> res = new HashSet<>(); + Set<Font> res = new ArraySet<>(); for (FontFamily[] families : map.values()) { for (FontFamily family : families) { for (int i = 0; i < family.getSize(); ++i) { @@ -218,7 +217,7 @@ public final class SystemFonts { } private static void appendNamedFamily(@NonNull FontConfig.FontFamily xmlFamily, - @NonNull HashMap<String, ByteBuffer> bufferCache, + @NonNull ArrayMap<String, ByteBuffer> bufferCache, @NonNull ArrayMap<String, ArrayList<FontFamily>> fallbackListMap) { final String familyName = xmlFamily.getName(); final FontFamily family = createFontFamily( @@ -284,8 +283,8 @@ public final class SystemFonts { */ @VisibleForTesting public static Map<String, FontFamily[]> buildSystemFallback(FontConfig fontConfig) { - final Map<String, FontFamily[]> fallbackMap = new HashMap<>(); - final HashMap<String, ByteBuffer> bufferCache = new HashMap<>(); + final Map<String, FontFamily[]> fallbackMap = new ArrayMap<>(); + final ArrayMap<String, ByteBuffer> bufferCache = new ArrayMap<>(); final List<FontConfig.FontFamily> xmlFamilies = fontConfig.getFontFamilies(); final ArrayMap<String, ArrayList<FontFamily>> fallbackListMap = new ArrayMap<>(); @@ -326,7 +325,7 @@ public final class SystemFonts { public static Map<String, Typeface> buildSystemTypefaces( FontConfig fontConfig, Map<String, FontFamily[]> fallbackMap) { - final HashMap<String, Typeface> result = new HashMap<>(); + final ArrayMap<String, Typeface> result = new ArrayMap<>(); Typeface.initSystemDefaultTypefaces(fallbackMap, fontConfig.getAliases(), result); return result; } diff --git a/libs/WindowManager/Jetpack/Android.bp b/libs/WindowManager/Jetpack/Android.bp index 7fbbb61e469d..4612ba28d1db 100644 --- a/libs/WindowManager/Jetpack/Android.bp +++ b/libs/WindowManager/Jetpack/Android.bp @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +// Sidecar android_library_import { name: "window-sidecar", aars: ["window-sidecar-release.aar"], @@ -20,7 +21,7 @@ android_library_import { java_library { name: "androidx.window.sidecar", - srcs: ["src/**/*.java"], + srcs: ["src/androidx/window/sidecar/**/*.java", "src/androidx/window/util/**/*.java"], static_libs: ["window-sidecar"], installable: true, sdk_version: "core_platform", @@ -36,3 +37,31 @@ prebuilt_etc { src: "androidx.window.sidecar.xml", filename_from_src: true, } + +// Extensions +// NOTE: This module is still under active development and must not +// be used in production. Use 'androidx.window.sidecar' instead. +android_library_import { + name: "window-extensions", + aars: ["window-extensions-release.aar"], + sdk_version: "current", +} + +java_library { + name: "androidx.window.extensions", + srcs: ["src/androidx/window/extensions/**/*.java", "src/androidx/window/util/**/*.java"], + static_libs: ["window-extensions"], + installable: true, + sdk_version: "core_platform", + system_ext_specific: true, + libs: ["framework", "androidx.annotation_annotation",], + required: ["androidx.window.extensions.xml",], +} + +prebuilt_etc { + name: "androidx.window.extensions.xml", + system_ext_specific: true, + sub_dir: "permissions", + src: "androidx.window.extensions.xml", + filename_from_src: true, +} diff --git a/libs/WindowManager/Jetpack/androidx.window.extensions.xml b/libs/WindowManager/Jetpack/androidx.window.extensions.xml new file mode 100644 index 000000000000..34264aa3ade3 --- /dev/null +++ b/libs/WindowManager/Jetpack/androidx.window.extensions.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<permissions> + <library + name="androidx.window.extensions" + file="/system_ext/framework/androidx.window.extensions.jar"/> +</permissions> diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/ExtensionProvider.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/ExtensionProvider.java new file mode 100644 index 000000000000..b7a60392c512 --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/ExtensionProvider.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.extensions; + +import android.content.Context; + +/** + * Provider class that will instantiate the library implementation. It must be included in the + * vendor library, and the vendor implementation must match the signature of this class. + */ +public class ExtensionProvider { + /** + * Provides a simple implementation of {@link ExtensionInterface} that can be replaced by + * an OEM by overriding this method. + */ + public static ExtensionInterface getExtensionImpl(Context context) { + return new SampleExtensionImpl(context); + } + + /** + * The support library will use this method to check API version compatibility. + * @return API version string in MAJOR.MINOR.PATCH-description format. + */ + public static String getApiVersion() { + return "1.0.0-settings_sample"; + } +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java new file mode 100644 index 000000000000..0bf6965beb5f --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.extensions; + +import static android.view.Display.DEFAULT_DISPLAY; + +import static androidx.window.util.ExtensionHelper.rotateRectToDisplayRotation; +import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect; + +import android.app.Activity; +import android.content.Context; +import android.graphics.Rect; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.window.util.BaseDisplayFeature; +import androidx.window.util.SettingsConfigProvider; + +import java.util.ArrayList; +import java.util.List; + +/** + * Reference implementation of androidx.window.extensions OEM interface for use with + * WindowManager Jetpack. + * + * NOTE: This version is a work in progress and under active development. It MUST NOT be used in + * production builds since the interface can still change before reaching stable version. + * Please refer to {@link androidx.window.sidecar.SampleSidecarImpl} instead. + */ +class SampleExtensionImpl extends StubExtension implements + SettingsConfigProvider.StateChangeCallback { + private static final String TAG = "SampleExtension"; + + private final SettingsConfigProvider mConfigProvider; + + SampleExtensionImpl(Context context) { + mConfigProvider = new SettingsConfigProvider(context, this); + } + + @Override + public void onDevicePostureChanged() { + updateDeviceState(new ExtensionDeviceState(mConfigProvider.getDeviceState())); + } + + @Override + public void onDisplayFeaturesChanged() { + for (Activity activity : getActivitiesListeningForLayoutChanges()) { + ExtensionWindowLayoutInfo newLayout = getWindowLayoutInfo(activity); + updateWindowLayout(activity, newLayout); + } + } + + @NonNull + private ExtensionWindowLayoutInfo getWindowLayoutInfo(@NonNull Activity activity) { + List<ExtensionDisplayFeature> displayFeatures = getDisplayFeatures(activity); + return new ExtensionWindowLayoutInfo(displayFeatures); + } + + private List<ExtensionDisplayFeature> getDisplayFeatures(@NonNull Activity activity) { + List<ExtensionDisplayFeature> features = new ArrayList<>(); + int displayId = activity.getDisplayId(); + if (displayId != DEFAULT_DISPLAY) { + Log.w(TAG, "This sample doesn't support display features on secondary displays"); + return features; + } + + if (activity.isInMultiWindowMode()) { + // It is recommended not to report any display features in multi-window mode, since it + // won't be possible to synchronize the display feature positions with window movement. + return features; + } + + List<BaseDisplayFeature> storedFeatures = mConfigProvider.getDisplayFeatures(); + for (BaseDisplayFeature baseFeature : storedFeatures) { + Rect featureRect = baseFeature.getRect(); + rotateRectToDisplayRotation(displayId, featureRect); + transformToWindowSpaceRect(activity, featureRect); + features.add(new ExtensionFoldingFeature(featureRect, baseFeature.getType(), + baseFeature.getState())); + } + return features; + } + + @Override + protected void onListenersChanged() { + if (hasListeners()) { + mConfigProvider.registerObserversIfNeeded(); + } else { + mConfigProvider.unregisterObserversIfNeeded(); + } + + onDevicePostureChanged(); + onDisplayFeaturesChanged(); + } +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/StubExtension.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/StubExtension.java new file mode 100644 index 000000000000..b0895efc75a9 --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/StubExtension.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.extensions; + +import android.app.Activity; + +import androidx.annotation.NonNull; + +import java.util.HashSet; +import java.util.Set; + +/** + * Basic implementation of the {@link ExtensionInterface}. An OEM can choose to use it as the base + * class for their implementation. + */ +abstract class StubExtension implements ExtensionInterface { + + private ExtensionCallback mExtensionCallback; + private final Set<Activity> mWindowLayoutChangeListenerActivities = new HashSet<>(); + private boolean mDeviceStateChangeListenerRegistered; + + StubExtension() { + } + + @Override + public void setExtensionCallback(@NonNull ExtensionCallback extensionCallback) { + this.mExtensionCallback = extensionCallback; + } + + @Override + public void onWindowLayoutChangeListenerAdded(@NonNull Activity activity) { + this.mWindowLayoutChangeListenerActivities.add(activity); + this.onListenersChanged(); + } + + @Override + public void onWindowLayoutChangeListenerRemoved(@NonNull Activity activity) { + this.mWindowLayoutChangeListenerActivities.remove(activity); + this.onListenersChanged(); + } + + @Override + public void onDeviceStateListenersChanged(boolean isEmpty) { + this.mDeviceStateChangeListenerRegistered = !isEmpty; + this.onListenersChanged(); + } + + void updateDeviceState(ExtensionDeviceState newState) { + if (this.mExtensionCallback != null) { + mExtensionCallback.onDeviceStateChanged(newState); + } + } + + void updateWindowLayout(@NonNull Activity activity, + @NonNull ExtensionWindowLayoutInfo newLayout) { + if (this.mExtensionCallback != null) { + mExtensionCallback.onWindowLayoutChanged(activity, newLayout); + } + } + + @NonNull + Set<Activity> getActivitiesListeningForLayoutChanges() { + return mWindowLayoutChangeListenerActivities; + } + + protected boolean hasListeners() { + return !mWindowLayoutChangeListenerActivities.isEmpty() + || mDeviceStateChangeListenerRegistered; + } + + protected abstract void onListenersChanged(); +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java new file mode 100644 index 000000000000..1094a0e2b4da --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.sidecar; + +import static android.view.Display.DEFAULT_DISPLAY; + +import static androidx.window.util.ExtensionHelper.rotateRectToDisplayRotation; +import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect; + +import android.app.Activity; +import android.app.ActivityThread; +import android.content.Context; +import android.graphics.Rect; +import android.os.IBinder; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.window.util.BaseDisplayFeature; +import androidx.window.util.SettingsConfigProvider; + +import java.util.ArrayList; +import java.util.List; + +/** + * Reference implementation of androidx.window.sidecar OEM interface for use with + * WindowManager Jetpack. + */ +class SampleSidecarImpl extends StubSidecar implements + SettingsConfigProvider.StateChangeCallback { + private static final String TAG = "SampleSidecar"; + + private final SettingsConfigProvider mConfigProvider; + + SampleSidecarImpl(Context context) { + mConfigProvider = new SettingsConfigProvider(context, this); + } + + @Override + public void onDevicePostureChanged() { + updateDeviceState(getDeviceState()); + } + + @Override + public void onDisplayFeaturesChanged() { + for (IBinder windowToken : getWindowsListeningForLayoutChanges()) { + SidecarWindowLayoutInfo newLayout = getWindowLayoutInfo(windowToken); + updateWindowLayout(windowToken, newLayout); + } + } + + @NonNull + @Override + public SidecarDeviceState getDeviceState() { + SidecarDeviceState deviceState = new SidecarDeviceState(); + deviceState.posture = mConfigProvider.getDeviceState(); + return deviceState; + } + + @NonNull + @Override + public SidecarWindowLayoutInfo getWindowLayoutInfo(@NonNull IBinder windowToken) { + Activity activity = ActivityThread.currentActivityThread().getActivity(windowToken); + SidecarWindowLayoutInfo windowLayoutInfo = new SidecarWindowLayoutInfo(); + if (activity == null) { + return windowLayoutInfo; + } + windowLayoutInfo.displayFeatures = getDisplayFeatures(activity); + return windowLayoutInfo; + } + + private List<SidecarDisplayFeature> getDisplayFeatures(@NonNull Activity activity) { + List<SidecarDisplayFeature> features = new ArrayList<SidecarDisplayFeature>(); + int displayId = activity.getDisplayId(); + if (displayId != DEFAULT_DISPLAY) { + Log.w(TAG, "This sample doesn't support display features on secondary displays"); + return features; + } + + if (activity.isInMultiWindowMode()) { + // It is recommended not to report any display features in multi-window mode, since it + // won't be possible to synchronize the display feature positions with window movement. + return features; + } + + List<BaseDisplayFeature> storedFeatures = mConfigProvider.getDisplayFeatures(); + for (BaseDisplayFeature baseFeature : storedFeatures) { + SidecarDisplayFeature feature = new SidecarDisplayFeature(); + Rect featureRect = baseFeature.getRect(); + rotateRectToDisplayRotation(displayId, featureRect); + transformToWindowSpaceRect(activity, featureRect); + feature.setRect(featureRect); + feature.setType(baseFeature.getType()); + features.add(feature); + } + return features; + } + + @Override + protected void onListenersChanged() { + if (hasListeners()) { + mConfigProvider.registerObserversIfNeeded(); + } else { + mConfigProvider.unregisterObserversIfNeeded(); + } + } +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SettingsSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SettingsSidecarImpl.java deleted file mode 100644 index 5397302f6882..000000000000 --- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SettingsSidecarImpl.java +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.window.sidecar; - -import static android.view.Display.DEFAULT_DISPLAY; - -import static androidx.window.sidecar.SidecarHelper.getWindowDisplay; -import static androidx.window.sidecar.SidecarHelper.isInMultiWindow; -import static androidx.window.sidecar.SidecarHelper.rotateRectToDisplayRotation; -import static androidx.window.sidecar.SidecarHelper.transformToWindowSpaceRect; - -import android.content.ContentResolver; -import android.content.Context; -import android.database.ContentObserver; -import android.graphics.Rect; -import android.net.Uri; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.provider.Settings; -import android.text.TextUtils; -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.android.internal.R; - -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -class SettingsSidecarImpl extends StubSidecar { - private static final String TAG = "SettingsSidecar"; - - private static final String DEVICE_POSTURE = "device_posture"; - private static final String DISPLAY_FEATURES = "display_features"; - - private static final Pattern FEATURE_PATTERN = - Pattern.compile("([a-z]+)-\\[(\\d+),(\\d+),(\\d+),(\\d+)]"); - - private static final String FEATURE_TYPE_FOLD = "fold"; - private static final String FEATURE_TYPE_HINGE = "hinge"; - - private Context mContext; - private SettingsObserver mSettingsObserver; - - final class SettingsObserver extends ContentObserver { - private final Uri mDevicePostureUri = - Settings.Global.getUriFor(DEVICE_POSTURE); - private final Uri mDisplayFeaturesUri = - Settings.Global.getUriFor(DISPLAY_FEATURES); - private final ContentResolver mResolver = mContext.getContentResolver(); - private boolean mRegisteredObservers; - - - private SettingsObserver() { - super(new Handler(Looper.getMainLooper())); - } - - private void registerObserversIfNeeded() { - if (mRegisteredObservers) { - return; - } - mRegisteredObservers = true; - mResolver.registerContentObserver(mDevicePostureUri, false /* notifyForDescendents */, - this /* ContentObserver */); - mResolver.registerContentObserver(mDisplayFeaturesUri, false /* notifyForDescendents */, - this /* ContentObserver */); - } - - private void unregisterObserversIfNeeded() { - if (!mRegisteredObservers) { - return; - } - mRegisteredObservers = false; - mResolver.unregisterContentObserver(this); - } - - @Override - public void onChange(boolean selfChange, Uri uri) { - if (uri == null) { - return; - } - - if (mDevicePostureUri.equals(uri)) { - updateDevicePosture(); - return; - } - if (mDisplayFeaturesUri.equals(uri)) { - updateDisplayFeatures(); - return; - } - } - } - - SettingsSidecarImpl(Context context) { - mContext = context; - mSettingsObserver = new SettingsObserver(); - } - - private void updateDevicePosture() { - updateDeviceState(getDeviceState()); - } - - /** Update display features with values read from settings. */ - private void updateDisplayFeatures() { - for (IBinder windowToken : getWindowsListeningForLayoutChanges()) { - SidecarWindowLayoutInfo newLayout = getWindowLayoutInfo(windowToken); - updateWindowLayout(windowToken, newLayout); - } - } - - @NonNull - @Override - public SidecarDeviceState getDeviceState() { - ContentResolver resolver = mContext.getContentResolver(); - int posture = Settings.Global.getInt(resolver, DEVICE_POSTURE, - SidecarDeviceState.POSTURE_UNKNOWN); - SidecarDeviceState deviceState = new SidecarDeviceState(); - deviceState.posture = posture; - return deviceState; - } - - @NonNull - @Override - public SidecarWindowLayoutInfo getWindowLayoutInfo(@NonNull IBinder windowToken) { - List<SidecarDisplayFeature> displayFeatures = readDisplayFeatures(windowToken); - SidecarWindowLayoutInfo windowLayoutInfo = new SidecarWindowLayoutInfo(); - windowLayoutInfo.displayFeatures = displayFeatures; - return windowLayoutInfo; - } - - private List<SidecarDisplayFeature> readDisplayFeatures(IBinder windowToken) { - List<SidecarDisplayFeature> features = new ArrayList<SidecarDisplayFeature>(); - int displayId = getWindowDisplay(windowToken); - if (displayId != DEFAULT_DISPLAY) { - Log.w(TAG, "This sample doesn't support display features on secondary displays"); - return features; - } - - if (isInMultiWindow(windowToken)) { - // It is recommended not to report any display features in multi-window mode, since it - // won't be possible to synchronize the display feature positions with window movement. - return features; - } - - ContentResolver resolver = mContext.getContentResolver(); - String displayFeaturesString = Settings.Global.getString(resolver, DISPLAY_FEATURES); - if (TextUtils.isEmpty(displayFeaturesString)) { - displayFeaturesString = mContext.getResources().getString( - R.string.config_display_features); - } - if (TextUtils.isEmpty(displayFeaturesString)) { - return features; - } - - String[] featureStrings = displayFeaturesString.split(";"); - for (String featureString : featureStrings) { - Matcher featureMatcher = FEATURE_PATTERN.matcher(featureString); - if (!featureMatcher.matches()) { - Log.e(TAG, "Malformed feature description format: " + featureString); - continue; - } - try { - String featureType = featureMatcher.group(1); - int type; - switch (featureType) { - case FEATURE_TYPE_FOLD: - type = SidecarDisplayFeature.TYPE_FOLD; - break; - case FEATURE_TYPE_HINGE: - type = SidecarDisplayFeature.TYPE_HINGE; - break; - default: { - Log.e(TAG, "Malformed feature type: " + featureType); - continue; - } - } - - int left = Integer.parseInt(featureMatcher.group(2)); - int top = Integer.parseInt(featureMatcher.group(3)); - int right = Integer.parseInt(featureMatcher.group(4)); - int bottom = Integer.parseInt(featureMatcher.group(5)); - Rect featureRect = new Rect(left, top, right, bottom); - rotateRectToDisplayRotation(featureRect, displayId); - transformToWindowSpaceRect(featureRect, windowToken); - if (isNotZero(featureRect)) { - SidecarDisplayFeature feature = new SidecarDisplayFeature(); - feature.setRect(featureRect); - feature.setType(type); - features.add(feature); - } else { - Log.w(TAG, "Failed to adjust feature to window"); - } - } catch (NumberFormatException e) { - Log.e(TAG, "Malformed feature description: " + featureString); - } - } - return features; - } - - private static boolean isNotZero(Rect rect) { - return rect.height() > 0 || rect.width() > 0; - } - - @Override - protected void onListenersChanged() { - if (mSettingsObserver == null) { - return; - } - - if (hasListeners()) { - mSettingsObserver.registerObserversIfNeeded(); - } else { - mSettingsObserver.unregisterObserversIfNeeded(); - } - } -} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java index 0b4915ed5dac..e6f8388b031f 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java @@ -28,7 +28,7 @@ public class SidecarProvider { * an OEM by overriding this method. */ public static SidecarInterface getSidecarImpl(Context context) { - return new SettingsSidecarImpl(context); + return new SampleSidecarImpl(context); } /** diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDisplayFeature.java b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDisplayFeature.java new file mode 100644 index 000000000000..b74a2a4bd47d --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDisplayFeature.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.util; + +import android.graphics.Rect; + +import androidx.annotation.NonNull; + +/** Wrapper for both Extension and Sidecar versions of DisplayFeature. */ +public class BaseDisplayFeature { + private final int mType; + private final int mState; + @NonNull + public final Rect mRect; + + public BaseDisplayFeature(int type, int state, @NonNull Rect rect) { + this.mType = type; + this.mState = state; + if (rect.width() == 0 && rect.height() == 0) { + throw new IllegalArgumentException( + "Display feature rectangle cannot have zero width and height simultaneously."); + } + this.mRect = rect; + } + + public int getType() { + return mType; + } + + public int getState() { + return mState; + } + + @NonNull + public Rect getRect() { + return mRect; + } +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java index e5b6cff17b26..2a593f15a9de 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarHelper.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,30 +14,36 @@ * limitations under the License. */ -package androidx.window.sidecar; +package androidx.window.util; -import static android.view.Display.INVALID_DISPLAY; import static android.view.Surface.ROTATION_0; import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; import android.app.Activity; -import android.app.ActivityThread; import android.graphics.Rect; import android.hardware.display.DisplayManagerGlobal; -import android.os.IBinder; import android.view.DisplayInfo; import android.view.Surface; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; -class SidecarHelper { +/** + * Util class for both Sidecar and Extensions. + */ +public final class ExtensionHelper { + + private ExtensionHelper() { + // Util class, no instances should be created. + } + /** - * Rotate the input rectangle specified in default display orientation to the current display + * Rotates the input rectangle specified in default display orientation to the current display * rotation. */ - static void rotateRectToDisplayRotation(Rect inOutRect, int displayId) { + public static void rotateRectToDisplayRotation(int displayId, Rect inOutRect) { DisplayManagerGlobal dmGlobal = DisplayManagerGlobal.getInstance(); DisplayInfo displayInfo = dmGlobal.getDisplayInfo(displayId); int rotation = displayInfo.rotation; @@ -52,7 +58,7 @@ class SidecarHelper { } /** - * Rotate the input rectangle within parent bounds for a given delta. + * Rotates the input rectangle within parent bounds for a given delta. */ private static void rotateBounds(Rect inOutRect, int parentWidth, int parentHeight, @Surface.Rotation int delta) { @@ -79,9 +85,9 @@ class SidecarHelper { } } - /** Transform rectangle from absolute coordinate space to the window coordinate space. */ - static void transformToWindowSpaceRect(Rect inOutRect, IBinder windowToken) { - Rect windowRect = getWindowBounds(windowToken); + /** Transforms rectangle from absolute coordinate space to the window coordinate space. */ + public static void transformToWindowSpaceRect(Activity activity, Rect inOutRect) { + Rect windowRect = getWindowBounds(activity); if (windowRect == null) { inOutRect.setEmpty(); return; @@ -95,32 +101,17 @@ class SidecarHelper { } /** - * Get the current window bounds in absolute coordinates. - * NOTE: Only works with Activity windows. + * Gets the current window bounds in absolute coordinates. */ @Nullable - private static Rect getWindowBounds(IBinder windowToken) { - Activity activity = ActivityThread.currentActivityThread().getActivity(windowToken); - return activity != null - ? activity.getWindowManager().getCurrentWindowMetrics().getBounds() - : null; - } - - /** - * Check if this window is an Activity window that is in multi-window mode. - */ - static boolean isInMultiWindow(IBinder windowToken) { - Activity activity = ActivityThread.currentActivityThread().getActivity(windowToken); - return activity != null && activity.isInMultiWindowMode(); + private static Rect getWindowBounds(@NonNull Activity activity) { + return activity.getWindowManager().getCurrentWindowMetrics().getBounds(); } /** - * Get the id of the parent display for the window. - * NOTE: Only works with Activity windows. + * Checks if both dimensions of the given rect are zero at the same time. */ - static int getWindowDisplay(IBinder windowToken) { - Activity activity = ActivityThread.currentActivityThread().getActivity(windowToken); - return activity != null - ? activity.getWindowManager().getDefaultDisplay().getDisplayId() : INVALID_DISPLAY; + public static boolean isZero(@NonNull Rect rect) { + return rect.height() == 0 && rect.width() == 0; } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/SettingsConfigProvider.java b/libs/WindowManager/Jetpack/src/androidx/window/util/SettingsConfigProvider.java new file mode 100644 index 000000000000..6dd190ce2519 --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/util/SettingsConfigProvider.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.util; + +import static androidx.window.util.ExtensionHelper.isZero; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.graphics.Rect; +import android.net.Uri; +import android.os.Handler; +import android.os.Looper; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Log; + +import androidx.annotation.NonNull; + +import com.android.internal.R; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Device and display feature state provider that uses Settings as the source. + */ +public final class SettingsConfigProvider extends ContentObserver { + private static final String TAG = "SettingsConfigProvider"; + private static final String DEVICE_POSTURE = "device_posture"; + private static final String DISPLAY_FEATURES = "display_features"; + + private static final Pattern FEATURE_PATTERN = + Pattern.compile("([a-z]+)-\\[(\\d+),(\\d+),(\\d+),(\\d+)]"); + + private static final String FEATURE_TYPE_FOLD = "fold"; + private static final String FEATURE_TYPE_HINGE = "hinge"; + + private final Uri mDevicePostureUri = + Settings.Global.getUriFor(DEVICE_POSTURE); + private final Uri mDisplayFeaturesUri = + Settings.Global.getUriFor(DISPLAY_FEATURES); + private final Context mContext; + private final ContentResolver mResolver; + private final StateChangeCallback mCallback; + private boolean mRegisteredObservers; + + public SettingsConfigProvider(@NonNull Context context, @NonNull StateChangeCallback callback) { + super(new Handler(Looper.getMainLooper())); + mContext = context; + mResolver = context.getContentResolver(); + mCallback = callback; + } + + /** + * Registers the content observers for Settings keys that store device state and display feature + * configurations. + */ + public void registerObserversIfNeeded() { + if (mRegisteredObservers) { + return; + } + mRegisteredObservers = true; + mResolver.registerContentObserver(mDevicePostureUri, false /* notifyForDescendants */, + this /* ContentObserver */); + mResolver.registerContentObserver(mDisplayFeaturesUri, false /* notifyForDescendants */, + this /* ContentObserver */); + } + + /** + * Unregisters the content observers that are tracking the state changes. + * @see #registerObserversIfNeeded() + */ + public void unregisterObserversIfNeeded() { + if (!mRegisteredObservers) { + return; + } + mRegisteredObservers = false; + mResolver.unregisterContentObserver(this); + } + + /** + * Gets the device posture int stored in Settings. + */ + public int getDeviceState() { + return Settings.Global.getInt(mResolver, DEVICE_POSTURE, + 0 /* POSTURE_UNKNOWN */); + } + + /** + * Gets the list of all display feature configs stored in Settings. Uses a custom + * {@link BaseDisplayFeature} class to report the config to be translated for actual + * containers in Sidecar or Extensions. + */ + public List<BaseDisplayFeature> getDisplayFeatures() { + List<BaseDisplayFeature> features = new ArrayList<>(); + String displayFeaturesString = Settings.Global.getString(mResolver, DISPLAY_FEATURES); + if (TextUtils.isEmpty(displayFeaturesString)) { + displayFeaturesString = mContext.getResources().getString( + R.string.config_display_features); + } + if (TextUtils.isEmpty(displayFeaturesString)) { + return features; + } + String[] featureStrings = displayFeaturesString.split(";"); + + int deviceState = getDeviceState(); + + for (String featureString : featureStrings) { + Matcher featureMatcher = FEATURE_PATTERN.matcher(featureString); + if (!featureMatcher.matches()) { + Log.e(TAG, "Malformed feature description format: " + featureString); + continue; + } + try { + String featureType = featureMatcher.group(1); + int type; + switch (featureType) { + case FEATURE_TYPE_FOLD: + type = 1 /* TYPE_FOLD */; + break; + case FEATURE_TYPE_HINGE: + type = 2 /* TYPE_HINGE */; + break; + default: { + Log.e(TAG, "Malformed feature type: " + featureType); + continue; + } + } + + int left = Integer.parseInt(featureMatcher.group(2)); + int top = Integer.parseInt(featureMatcher.group(3)); + int right = Integer.parseInt(featureMatcher.group(4)); + int bottom = Integer.parseInt(featureMatcher.group(5)); + Rect featureRect = new Rect(left, top, right, bottom); + if (!isZero(featureRect)) { + BaseDisplayFeature feature = new BaseDisplayFeature(type, deviceState, + featureRect); + features.add(feature); + } else { + Log.w(TAG, "Read empty feature"); + } + } catch (NumberFormatException e) { + Log.e(TAG, "Malformed feature description: " + featureString); + } + } + return features; + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + if (uri == null) { + return; + } + + if (mDevicePostureUri.equals(uri)) { + mCallback.onDevicePostureChanged(); + mCallback.onDisplayFeaturesChanged(); + return; + } + if (mDisplayFeaturesUri.equals(uri)) { + mCallback.onDisplayFeaturesChanged(); + } + } + + /** + * Callback that notifies about device or display feature state changes. + */ + public interface StateChangeCallback { + /** + * Notifies about the device state update. + */ + void onDevicePostureChanged(); + + /** + * Notifies about the display feature config update. + */ + void onDisplayFeaturesChanged(); + } +} diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar Binary files differnew file mode 100644 index 000000000000..7b306b00afd9 --- /dev/null +++ b/libs/WindowManager/Jetpack/window-extensions-release.aar diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt index 7ea4689be7e2..255e4d2c0d44 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.animation -import android.os.Looper import android.util.ArrayMap import android.util.Log import android.view.View @@ -847,7 +846,7 @@ class PhysicsAnimator<T> private constructor (target: T) { * pass to [spring]. */ data class SpringConfig internal constructor( - internal var stiffness: Float, + var stiffness: Float, internal var dampingRatio: Float, internal var startVelocity: Float = 0f, internal var finalPosition: Float = UNSET @@ -879,8 +878,8 @@ class PhysicsAnimator<T> private constructor (target: T) { */ data class FlingConfig internal constructor( internal var friction: Float, - internal var min: Float, - internal var max: Float, + var min: Float, + var max: Float, internal var startVelocity: Float ) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 39510e55139a..4e19e0665d5f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -1478,6 +1478,9 @@ public class BubbleStackView extends FrameLayout * Update bubble order and pointer position. */ public void updateBubbleOrder(List<Bubble> bubbles) { + if (isExpansionAnimating()) { + return; + } final Runnable reorder = () -> { for (int i = 0; i < bubbles.size(); i++) { Bubble bubble = bubbles.get(i); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java index 8fb358ad74d1..b91ba34c4464 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java @@ -64,6 +64,7 @@ public class PipResizeGestureHandler { private static final String TAG = "PipResizeGestureHandler"; private static final int PINCH_RESIZE_SNAP_DURATION = 250; private static final int PINCH_RESIZE_MAX_ANGLE_ROTATION = 45; + private static final float PINCH_RESIZE_AUTO_MAX_RATIO = 0.9f; private final Context mContext; private final PipBoundsAlgorithm mPipBoundsAlgorithm; @@ -539,6 +540,11 @@ public class PipResizeGestureHandler { // position correctly. Drag-resize does not need to move, so just finalize resize. if (mUsingPinchToZoom) { final Rect startBounds = new Rect(mLastResizeBounds); + // If user resize is pretty close to max size, just auto resize to max. + if (mLastResizeBounds.width() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.x + || mLastResizeBounds.height() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.y) { + mLastResizeBounds.set(0, 0, mMaxSize.x, mMaxSize.y); + } mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, mPipBoundsAlgorithm.getSnapFraction(mPipBoundsState.getBounds())); mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds, diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp index dca27328f192..c0ab20d9249f 100644 --- a/libs/WindowManager/Shell/tests/unittest/Android.bp +++ b/libs/WindowManager/Shell/tests/unittest/Android.bp @@ -15,7 +15,10 @@ android_test { name: "WMShellUnitTests", - srcs: ["**/*.java"], + srcs: [ + "**/*.java", + "**/*.kt", + ], static_libs: [ "WindowManager-Shell", diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/animation/PhysicsAnimatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/animation/PhysicsAnimatorTest.kt index 4bd9bed26a82..17ed396987af 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/animation/PhysicsAnimatorTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/animation/PhysicsAnimatorTest.kt @@ -26,7 +26,7 @@ import androidx.dynamicanimation.animation.DynamicAnimation import androidx.dynamicanimation.animation.FloatPropertyCompat import androidx.dynamicanimation.animation.SpringForce import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase +import com.android.wm.shell.ShellTestCase import com.android.wm.shell.animation.PhysicsAnimator.EndListener import com.android.wm.shell.animation.PhysicsAnimator.UpdateListener import com.android.wm.shell.animation.PhysicsAnimatorTestUtils.clearAnimationUpdateFrames @@ -54,8 +54,7 @@ import org.mockito.MockitoAnnotations @TestableLooper.RunWithLooper @RunWith(AndroidTestingRunner::class) @SmallTest -@Ignore("Blocking presubmits - investigating in b/158697054") -class PhysicsAnimatorTest : SysuiTestCase() { +class PhysicsAnimatorTest : ShellTestCase() { private lateinit var viewGroup: ViewGroup private lateinit var testView: View private lateinit var testView2: View diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt index 4fab9a5496ec..dd1a6a5a281e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt @@ -20,12 +20,12 @@ import android.content.pm.LauncherApps import android.os.UserHandle import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest -import com.android.systemui.util.mockito.eq import com.android.wm.shell.ShellTestCase import junit.framework.Assert.assertEquals import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.eq import org.mockito.Mockito.mock import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/magnetictarget/MagnetizedObjectTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/magnetictarget/MagnetizedObjectTest.kt index fe536411d5ed..9f1ee6c92700 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/magnetictarget/MagnetizedObjectTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/magnetictarget/MagnetizedObjectTest.kt @@ -21,8 +21,8 @@ import android.view.MotionEvent import android.view.View import androidx.dynamicanimation.animation.FloatPropertyCompat import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.util.animation.PhysicsAnimatorTestUtils +import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.animation.PhysicsAnimatorTestUtils import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue @@ -43,7 +43,7 @@ import org.mockito.Mockito.verifyNoMoreInteractions @TestableLooper.RunWithLooper @RunWith(AndroidTestingRunner::class) @SmallTest -class MagnetizedObjectTest : SysuiTestCase() { +class MagnetizedObjectTest : ShellTestCase() { /** Incrementing value for fake MotionEvent timestamps. */ private var time = 0L diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp index b944310d8822..b769d40238a4 100644 --- a/libs/hwui/jni/fonts/Font.cpp +++ b/libs/hwui/jni/fonts/Font.cpp @@ -79,7 +79,8 @@ static void Font_Builder_addAxis(CRITICAL_JNI_PARAMS_COMMA jlong builderPtr, jin // Regular JNI static jlong Font_Builder_build(JNIEnv* env, jobject clazz, jlong builderPtr, jobject buffer, - jstring filePath, jint weight, jboolean italic, jint ttcIndex) { + jstring filePath, jstring langTags, jint weight, jboolean italic, + jint ttcIndex) { NPE_CHECK_RETURN_ZERO(env, buffer); std::unique_ptr<NativeFontBuilder> builder(toBuilder(builderPtr)); const void* fontPtr = env->GetDirectBufferAddress(buffer); @@ -94,6 +95,7 @@ static jlong Font_Builder_build(JNIEnv* env, jobject clazz, jlong builderPtr, jo return 0; } ScopedUtfChars fontPath(env, filePath); + ScopedUtfChars langTagStr(env, langTags); jobject fontRef = MakeGlobalRefOrDie(env, buffer); sk_sp<SkData> data(SkData::MakeWithProc(fontPtr, fontSize, release_global_ref, reinterpret_cast<void*>(fontRef))); @@ -105,8 +107,13 @@ static jlong Font_Builder_build(JNIEnv* env, jobject clazz, jlong builderPtr, jo "Failed to create internal object. maybe invalid font data."); return 0; } - std::shared_ptr<minikin::Font> font = minikin::Font::Builder(minikinFont).setWeight(weight) - .setSlant(static_cast<minikin::FontStyle::Slant>(italic)).build(); + uint32_t localeListId = minikin::registerLocaleList(langTagStr.c_str()); + std::shared_ptr<minikin::Font> font = + minikin::Font::Builder(minikinFont) + .setWeight(weight) + .setSlant(static_cast<minikin::FontStyle::Slant>(italic)) + .setLocaleListId(localeListId) + .build(); return reinterpret_cast<jlong>(new FontWrapper(std::move(font))); } @@ -302,11 +309,12 @@ static jint FontFileUtil_isPostScriptType1Font(JNIEnv* env, jobject, jobject buf /////////////////////////////////////////////////////////////////////////////// static const JNINativeMethod gFontBuilderMethods[] = { - { "nInitBuilder", "()J", (void*) Font_Builder_initBuilder }, - { "nAddAxis", "(JIF)V", (void*) Font_Builder_addAxis }, - { "nBuild", "(JLjava/nio/ByteBuffer;Ljava/lang/String;IZI)J", (void*) Font_Builder_build }, - { "nClone", "(JJIZI)J", (void*) Font_Builder_clone }, - { "nGetReleaseNativeFont", "()J", (void*) Font_Builder_getReleaseNativeFont }, + {"nInitBuilder", "()J", (void*)Font_Builder_initBuilder}, + {"nAddAxis", "(JIF)V", (void*)Font_Builder_addAxis}, + {"nBuild", "(JLjava/nio/ByteBuffer;Ljava/lang/String;Ljava/lang/String;IZI)J", + (void*)Font_Builder_build}, + {"nClone", "(JJIZI)J", (void*)Font_Builder_clone}, + {"nGetReleaseNativeFont", "()J", (void*)Font_Builder_getReleaseNativeFont}, }; static const JNINativeMethod gFontMethods[] = { diff --git a/libs/hwui/jni/fonts/FontFamily.cpp b/libs/hwui/jni/fonts/FontFamily.cpp index 37e52766f2ef..a07723f39b0c 100644 --- a/libs/hwui/jni/fonts/FontFamily.cpp +++ b/libs/hwui/jni/fonts/FontFamily.cpp @@ -83,6 +83,23 @@ static jlong FontFamily_Builder_GetReleaseFunc(CRITICAL_JNI_PARAMS) { return reinterpret_cast<jlong>(releaseFontFamily); } +// FastNative +static jstring FontFamily_getLangTags(JNIEnv* env, jobject, jlong familyPtr) { + FontFamilyWrapper* family = reinterpret_cast<FontFamilyWrapper*>(familyPtr); + uint32_t localeListId = family->family->localeListId(); + if (localeListId == 0) { + return nullptr; + } + std::string langTags = minikin::getLocaleString(localeListId); + return env->NewStringUTF(langTags.c_str()); +} + +// CriticalNative +static jint FontFamily_getVariant(jlong familyPtr) { + FontFamilyWrapper* family = reinterpret_cast<FontFamilyWrapper*>(familyPtr); + return static_cast<jint>(family->family->variant()); +} + /////////////////////////////////////////////////////////////////////////////// static const JNINativeMethod gFontFamilyBuilderMethods[] = { @@ -93,9 +110,16 @@ static const JNINativeMethod gFontFamilyBuilderMethods[] = { { "nGetReleaseNativeFamily", "()J", (void*) FontFamily_Builder_GetReleaseFunc }, }; +static const JNINativeMethod gFontFamilyMethods[] = { + {"nGetLangTags", "(J)Ljava/lang/String;", (void*)FontFamily_getLangTags}, + {"nGetVariant", "(J)I", (void*)FontFamily_getVariant}, +}; + int register_android_graphics_fonts_FontFamily(JNIEnv* env) { return RegisterMethodsOrDie(env, "android/graphics/fonts/FontFamily$Builder", - gFontFamilyBuilderMethods, NELEM(gFontFamilyBuilderMethods)); + gFontFamilyBuilderMethods, NELEM(gFontFamilyBuilderMethods)) + + RegisterMethodsOrDie(env, "android/graphics/fonts/FontFamily", gFontFamilyMethods, + NELEM(gFontFamilyMethods)); } } diff --git a/media/java/android/media/ImageUtils.java b/media/java/android/media/ImageUtils.java index d248f61f03ca..7837d7e39599 100644 --- a/media/java/android/media/ImageUtils.java +++ b/media/java/android/media/ImageUtils.java @@ -45,6 +45,7 @@ class ImageUtils { case ImageFormat.YV12: case ImageFormat.YUV_420_888: case ImageFormat.NV21: + case ImageFormat.YCBCR_P010: return 3; case ImageFormat.NV16: return 2; @@ -225,6 +226,7 @@ class ImageUtils { case ImageFormat.RAW_SENSOR: case ImageFormat.RAW_PRIVATE: // round estimate, real size is unknown case ImageFormat.DEPTH16: + case ImageFormat.YCBCR_P010: estimatedBytePerPixel = 2.0; break; case PixelFormat.RGB_888: @@ -244,6 +246,7 @@ class ImageUtils { private static Size getEffectivePlaneSizeForImage(Image image, int planeIdx) { switch (image.getFormat()) { + case ImageFormat.YCBCR_P010: case ImageFormat.YV12: case ImageFormat.YUV_420_888: case ImageFormat.NV21: diff --git a/media/jni/android_media_Utils.cpp b/media/jni/android_media_Utils.cpp index b6c47fca52f9..ecd9cc1cd098 100644 --- a/media/jni/android_media_Utils.cpp +++ b/media/jni/android_media_Utils.cpp @@ -69,6 +69,7 @@ bool isPossiblyYUV(PixelFormat format) { case HAL_PIXEL_FORMAT_RAW_OPAQUE: case HAL_PIXEL_FORMAT_BLOB: case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED: + case HAL_PIXEL_FORMAT_YCBCR_P010: return false; case HAL_PIXEL_FORMAT_YV12: @@ -261,6 +262,32 @@ status_t getLockedImageInfo(LockedImage* buffer, int idx, pStride = 1; rStride = (idx == 0) ? buffer->stride : ALIGN(buffer->stride / 2, 16); break; + case HAL_PIXEL_FORMAT_YCBCR_P010: + if (buffer->height % 2 != 0) { + ALOGE("YCBCR_P010: height (%d) should be a multiple of 2", buffer->height); + return BAD_VALUE; + } + + if (buffer->width <= 0) { + ALOGE("YCBCR_P010: width (%d) should be a > 0", buffer->width); + return BAD_VALUE; + } + + if (buffer->height <= 0) { + ALOGE("YCBCR_P010: height (%d) should be a > 0", buffer->height); + return BAD_VALUE; + } + + ySize = (buffer->stride * 2) * buffer->height; + cSize = ySize / 2; + pStride = (idx == 0) ? 2 : 4; + cb = buffer->data + ySize; + cr = cb + 2; + + pData = (idx == 0) ? buffer->data : (idx == 1) ? cb : cr; + dataSize = (idx == 0) ? ySize : cSize; + rStride = buffer->stride * 2; + break; case HAL_PIXEL_FORMAT_Y8: // Single plane, 8bpp. LOG_ALWAYS_FATAL_IF(idx != 0, "Wrong index: %d", idx); diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp index 9ec84d9d2265..eee9f1e08131 100644 --- a/media/jni/android_media_tv_Tuner.cpp +++ b/media/jni/android_media_tv_Tuner.cpp @@ -3786,7 +3786,7 @@ static jint android_media_tv_Tuner_read_filter_fmq( jniThrowRuntimeException(env, "Failed to GetByteArrayElements"); return -1; } - int realReadSize = filterClient->read(reinterpret_cast<uint8_t*>(dst) + offset, size); + int realReadSize = filterClient->read(reinterpret_cast<int8_t*>(dst) + offset, size); env->ReleaseByteArrayElements(buffer, dst, 0); return (jint) realReadSize; } diff --git a/media/jni/tuner/DemuxClient.cpp b/media/jni/tuner/DemuxClient.cpp index 1a2f8c065bd3..748d45808932 100644 --- a/media/jni/tuner/DemuxClient.cpp +++ b/media/jni/tuner/DemuxClient.cpp @@ -88,12 +88,19 @@ sp<FilterClient> DemuxClient::openFilter(DemuxFilterType type, int bufferSize, } sp<TimeFilterClient> DemuxClient::openTimeFilter() { - // TODO: pending aidl interface + if (mTunerDemux != NULL) { + shared_ptr<ITunerTimeFilter> tunerTimeFilter; + Status s = mTunerDemux->openTimeFilter(&tunerTimeFilter); + if (ClientHelper::getServiceSpecificErrorCode(s) != Result::SUCCESS) { + return NULL; + } + return new TimeFilterClient(tunerTimeFilter); + } if (mDemux != NULL) { sp<ITimeFilter> hidlTimeFilter = openHidlTimeFilter(); if (hidlTimeFilter != NULL) { - sp<TimeFilterClient> timeFilterClient = new TimeFilterClient(); + sp<TimeFilterClient> timeFilterClient = new TimeFilterClient(NULL); timeFilterClient->setHidlTimeFilter(hidlTimeFilter); return timeFilterClient; } @@ -103,7 +110,14 @@ sp<TimeFilterClient> DemuxClient::openTimeFilter() { } int DemuxClient::getAvSyncHwId(sp<FilterClient> filterClient) { - // pending aidl interface + if (mTunerDemux != NULL) { + int hwId; + Status s = mTunerDemux->getAvSyncHwId(filterClient->getAidlFilter(), &hwId); + if (ClientHelper::getServiceSpecificErrorCode(s) != Result::SUCCESS) { + return INVALID_AV_SYNC_HW_ID; + } + return hwId; + } if (mDemux != NULL) { uint32_t avSyncHwId; @@ -119,11 +133,18 @@ int DemuxClient::getAvSyncHwId(sp<FilterClient> filterClient) { } } - return -1; + return INVALID_AV_SYNC_HW_ID; } long DemuxClient::getAvSyncTime(int avSyncHwId) { - // pending aidl interface + if (mTunerDemux != NULL) { + int64_t time; + Status s = mTunerDemux->getAvSyncTime(avSyncHwId, &time); + if (ClientHelper::getServiceSpecificErrorCode(s) != Result::SUCCESS) { + return INVALID_AV_SYNC_TIME; + } + return time; + } if (mDemux != NULL) { uint64_t time; @@ -138,7 +159,7 @@ long DemuxClient::getAvSyncTime(int avSyncHwId) { } } - return -1; + return INVALID_AV_SYNC_TIME; } sp<DvrClient> DemuxClient::openDvr(DvrType dvbType, int bufferSize, sp<DvrClientCallback> cb) { @@ -167,7 +188,10 @@ sp<DvrClient> DemuxClient::openDvr(DvrType dvbType, int bufferSize, sp<DvrClient } Result DemuxClient::connectCiCam(int ciCamId) { - // pending aidl interface + if (mTunerDemux != NULL) { + Status s = mTunerDemux->connectCiCam(ciCamId); + return ClientHelper::getServiceSpecificErrorCode(s); + } if (mDemux != NULL) { return mDemux->connectCiCam(static_cast<uint32_t>(ciCamId)); @@ -177,7 +201,10 @@ Result DemuxClient::connectCiCam(int ciCamId) { } Result DemuxClient::disconnectCiCam() { - // pending aidl interface + if (mTunerDemux != NULL) { + Status s = mTunerDemux->disconnectCiCam(); + return ClientHelper::getServiceSpecificErrorCode(s); + } if (mDemux != NULL) { return mDemux->disconnectCiCam(); diff --git a/media/jni/tuner/DemuxClient.h b/media/jni/tuner/DemuxClient.h index 463944a7dc00..31eb35a1a56d 100644 --- a/media/jni/tuner/DemuxClient.h +++ b/media/jni/tuner/DemuxClient.h @@ -31,7 +31,9 @@ using Status = ::ndk::ScopedAStatus; using ::aidl::android::media::tv::tuner::ITunerDemux; +using ::aidl::android::media::tv::tuner::ITunerTimeFilter; +using ::android::hardware::tv::tuner::V1_0::IDemux; using ::android::hardware::tv::tuner::V1_0::DemuxFilterType; using ::android::hardware::tv::tuner::V1_0::DvrType; using ::android::hardware::tv::tuner::V1_0::IDemux; @@ -39,6 +41,9 @@ using ::android::hardware::tv::tuner::V1_0::ITimeFilter; using namespace std; +const int64_t INVALID_AV_SYNC_TIME = -1; +const int INVALID_AV_SYNC_HW_ID = -1; + namespace android { struct DemuxClient : public RefBase { diff --git a/media/jni/tuner/FilterClient.cpp b/media/jni/tuner/FilterClient.cpp index ca9eb4694595..8b4ca371056e 100644 --- a/media/jni/tuner/FilterClient.cpp +++ b/media/jni/tuner/FilterClient.cpp @@ -18,6 +18,7 @@ #include <aidlcommonsupport/NativeHandle.h> #include <android-base/logging.h> +#include <fmq/ConvertMQDescriptors.h> #include <utils/Log.h> #include "FilterClient.h" @@ -68,18 +69,12 @@ void FilterClient::setHidlFilter(sp<IFilter> filter) { mFilter_1_1 = ::android::hardware::tv::tuner::V1_1::IFilter::castFrom(mFilter); } -int FilterClient::read(uint8_t* buffer, int size) { - // TODO: pending aidl interface - - if (mFilter != NULL) { - Result res = getFilterMq(); - if (res != Result::SUCCESS) { - return -1; - } - return copyData(buffer, size); +int FilterClient::read(int8_t* buffer, int size) { + Result res = getFilterMq(); + if (res != Result::SUCCESS) { + return -1; } - - return -1; + return copyData(buffer, size); } SharedHandleInfo FilterClient::getAvSharedHandleInfo() { @@ -106,7 +101,10 @@ Result FilterClient::configure(DemuxFilterSettings configure) { } Result FilterClient::configureMonitorEvent(int monitorEventType) { - // TODO: pending aidl interface + if (mTunerFilter != NULL) { + Status s = mTunerFilter->configureMonitorEvent(monitorEventType); + return ClientHelper::getServiceSpecificErrorCode(s); + } if (mFilter_1_1 != NULL) { return mFilter_1_1->configureMonitorEvent(monitorEventType); @@ -116,7 +114,10 @@ Result FilterClient::configureMonitorEvent(int monitorEventType) { } Result FilterClient::configureIpFilterContextId(int cid) { - // TODO: pending aidl interface + if (mTunerFilter != NULL) { + Status s = mTunerFilter->configureIpFilterContextId(cid); + return ClientHelper::getServiceSpecificErrorCode(s); + } if (mFilter_1_1 != NULL) { return mFilter_1_1->configureIpCid(cid); @@ -126,7 +127,19 @@ Result FilterClient::configureIpFilterContextId(int cid) { } Result FilterClient::configureAvStreamType(AvStreamType avStreamType) { - // TODO: pending aidl interface + if (mTunerFilter != NULL) { + int type; + switch (avStreamType.getDiscriminator()) { + case AvStreamType::hidl_discriminator::audio: + type = (int)avStreamType.audio(); + break; + case AvStreamType::hidl_discriminator::video: + type = (int)avStreamType.video(); + break; + } + Status s = mTunerFilter->configureAvStreamType(type); + return ClientHelper::getServiceSpecificErrorCode(s); + } if (mFilter_1_1 != NULL) { return mFilter_1_1->configureAvStreamType(avStreamType); @@ -228,7 +241,10 @@ Result FilterClient::releaseAvHandle(native_handle_t* handle, uint64_t avDataId) } Result FilterClient::setDataSource(sp<FilterClient> filterClient){ - // TODO: pending aidl interface + if (mTunerFilter != NULL) { + Status s = mTunerFilter->setDataSource(filterClient->getAidlFilter()); + return ClientHelper::getServiceSpecificErrorCode(s); + } if (mFilter != NULL) { sp<IFilter> sourceFilter = filterClient->getHalFilter(); @@ -891,29 +907,43 @@ void TunerFilterCallback::getHidlRestartEvent(const vector<TunerFilterEvent>& fi } Result FilterClient::getFilterMq() { - if (mFilter == NULL) { - return Result::INVALID_STATE; - } - if (mFilterMQ != NULL) { return Result::SUCCESS; } - Result getQueueDescResult = Result::UNKNOWN_ERROR; - MQDescriptorSync<uint8_t> filterMQDesc; - mFilter->getQueueDesc( - [&](Result r, const MQDescriptorSync<uint8_t>& desc) { - filterMQDesc = desc; - getQueueDescResult = r; - }); - if (getQueueDescResult == Result::SUCCESS) { - mFilterMQ = std::make_unique<MQ>(filterMQDesc, true); - EventFlag::createEventFlag(mFilterMQ->getEventFlagWord(), &mFilterMQEventFlag); + AidlMQDesc aidlMqDesc; + Result res = Result::UNAVAILABLE; + + if (mTunerFilter != NULL) { + Status s = mTunerFilter->getQueueDesc(&aidlMqDesc); + res = ClientHelper::getServiceSpecificErrorCode(s); + if (res == Result::SUCCESS) { + mFilterMQ = new (nothrow) AidlMQ(aidlMqDesc); + EventFlag::createEventFlag(mFilterMQ->getEventFlagWord(), &mFilterMQEventFlag); + } + return res; + } + + if (mFilter != NULL) { + MQDescriptorSync<uint8_t> filterMQDesc; + mFilter->getQueueDesc( + [&](Result r, const MQDescriptorSync<uint8_t>& desc) { + filterMQDesc = desc; + res = r; + }); + if (res == Result::SUCCESS) { + AidlMQDesc aidlMQDesc; + unsafeHidlToAidlMQDescriptor<uint8_t, int8_t, SynchronizedReadWrite>( + filterMQDesc, &aidlMQDesc); + mFilterMQ = new (nothrow) AidlMessageQueue(aidlMQDesc); + EventFlag::createEventFlag(mFilterMQ->getEventFlagWord(), &mFilterMQEventFlag); + } } - return getQueueDescResult; + + return res; } -int FilterClient::copyData(uint8_t* buffer, int size) { +int FilterClient::copyData(int8_t* buffer, int size) { if (mFilter == NULL || mFilterMQ == NULL || mFilterMQEventFlag == NULL) { return -1; } diff --git a/media/jni/tuner/FilterClient.h b/media/jni/tuner/FilterClient.h index 21919ac10282..bbabc282464a 100644 --- a/media/jni/tuner/FilterClient.h +++ b/media/jni/tuner/FilterClient.h @@ -25,12 +25,14 @@ #include <android/hardware/tv/tuner/1.1/IFilter.h> #include <android/hardware/tv/tuner/1.1/IFilterCallback.h> #include <android/hardware/tv/tuner/1.1/types.h> +#include <fmq/AidlMessageQueue.h> #include <fmq/MessageQueue.h> #include "ClientHelper.h" #include "FilterClientCallback.h" using Status = ::ndk::ScopedAStatus; +using ::aidl::android::hardware::common::fmq::SynchronizedReadWrite; using ::aidl::android::media::tv::tuner::BnTunerFilterCallback; using ::aidl::android::media::tv::tuner::ITunerFilter; using ::aidl::android::media::tv::tuner::TunerDemuxIpAddress; @@ -69,10 +71,13 @@ using ::android::hardware::tv::tuner::V1_1::IFilterCallback; using namespace std; -using MQ = MessageQueue<uint8_t, kSynchronizedReadWrite>; - namespace android { +using MQ = MessageQueue<uint8_t, kSynchronizedReadWrite>; +using MQDesc = MQDescriptorSync<uint8_t>; +using AidlMQ = AidlMessageQueue<int8_t, SynchronizedReadWrite>; +using AidlMQDesc = MQDescriptor<int8_t, SynchronizedReadWrite>; + struct SharedHandleInfo { native_handle_t* sharedHandle; uint64_t size; @@ -139,7 +144,7 @@ public: * * @return the actual reading size. -1 if failed to read. */ - int read(uint8_t* buffer, int size); + int read(int8_t* buffer, int size); /** * Get the a/v shared memory handle information @@ -234,7 +239,7 @@ private: void getAidlIpAddress(DemuxIpAddress ipAddr, TunerDemuxIpAddress& srcIpAddress, TunerDemuxIpAddress& dstIpAddress); Result getFilterMq(); - int copyData(uint8_t* buffer, int size); + int copyData(int8_t* buffer, int size); void checkIsMediaFilter(DemuxFilterType type); void handleAvShareMemory(); void closeAvSharedMemory(); @@ -259,7 +264,7 @@ private: */ sp<::android::hardware::tv::tuner::V1_1::IFilter> mFilter_1_1; - unique_ptr<MQ> mFilterMQ; + AidlMQ* mFilterMQ; EventFlag* mFilterMQEventFlag; sp<FilterClientCallback> mCallback; diff --git a/media/jni/tuner/TimeFilterClient.cpp b/media/jni/tuner/TimeFilterClient.cpp index 27ea6e596204..432238d261e5 100644 --- a/media/jni/tuner/TimeFilterClient.cpp +++ b/media/jni/tuner/TimeFilterClient.cpp @@ -19,6 +19,7 @@ #include <android-base/logging.h> #include <utils/Log.h> +#include "ClientHelper.h" #include "TimeFilterClient.h" using ::android::hardware::tv::tuner::V1_0::Result; @@ -28,13 +29,12 @@ namespace android { /////////////// TimeFilterClient /////////////////////// -// TODO: pending aidl interface -TimeFilterClient::TimeFilterClient() { - //mTunerTimeFilter = tunerTimeFilter; +TimeFilterClient::TimeFilterClient(shared_ptr<ITunerTimeFilter> tunerTimeFilter) { + mTunerTimeFilter = tunerTimeFilter; } TimeFilterClient::~TimeFilterClient() { - //mTunerTimeFilter = NULL; + mTunerTimeFilter = NULL; mTimeFilter = NULL; } @@ -44,7 +44,10 @@ void TimeFilterClient::setHidlTimeFilter(sp<ITimeFilter> timeFilter) { } Result TimeFilterClient::setTimeStamp(long timeStamp) { - // TODO: pending aidl interface + if (mTunerTimeFilter != NULL) { + Status s = mTunerTimeFilter->setTimeStamp(timeStamp); + return ClientHelper::getServiceSpecificErrorCode(s); + } if (mTimeFilter != NULL) { return mTimeFilter->setTimeStamp(timeStamp); @@ -54,7 +57,10 @@ Result TimeFilterClient::setTimeStamp(long timeStamp) { } Result TimeFilterClient::clearTimeStamp() { - // TODO: pending aidl interface + if (mTunerTimeFilter != NULL) { + Status s = mTunerTimeFilter->clearTimeStamp(); + return ClientHelper::getServiceSpecificErrorCode(s); + } if (mTimeFilter != NULL) { return mTimeFilter->clearTimeStamp(); @@ -64,7 +70,14 @@ Result TimeFilterClient::clearTimeStamp() { } long TimeFilterClient::getTimeStamp() { - // TODO: pending aidl interface + if (mTunerTimeFilter != NULL) { + int64_t timeStamp; + Status s = mTunerTimeFilter->getTimeStamp(&timeStamp); + if (ClientHelper::getServiceSpecificErrorCode(s) != Result::SUCCESS) { + return (long)Constant64Bit::INVALID_PRESENTATION_TIME_STAMP; + } + return timeStamp; + } if (mTimeFilter != NULL) { Result res; @@ -84,27 +97,37 @@ long TimeFilterClient::getTimeStamp() { } long TimeFilterClient::getSourceTime() { - // TODO: pending aidl interface + if (mTunerTimeFilter != NULL) { + int64_t sourceTime; + Status s = mTunerTimeFilter->getTimeStamp(&sourceTime); + if (ClientHelper::getServiceSpecificErrorCode(s) != Result::SUCCESS) { + return (long)Constant64Bit::INVALID_PRESENTATION_TIME_STAMP; + } + return sourceTime; + } if (mTimeFilter != NULL) { Result res; - long timestamp; + long sourceTime; mTimeFilter->getSourceTime( [&](Result r, uint64_t t) { res = r; - timestamp = t; + sourceTime = t; }); if (res != Result::SUCCESS) { return (long)Constant64Bit::INVALID_PRESENTATION_TIME_STAMP; } - return timestamp; + return sourceTime; } return (long)Constant64Bit::INVALID_PRESENTATION_TIME_STAMP; } Result TimeFilterClient::close() { - // TODO: pending aidl interface + if (mTunerTimeFilter != NULL) { + Status s = mTunerTimeFilter->close(); + return ClientHelper::getServiceSpecificErrorCode(s); + } if (mTimeFilter != NULL) { return mTimeFilter->close(); diff --git a/media/jni/tuner/TimeFilterClient.h b/media/jni/tuner/TimeFilterClient.h index 9a9d172665ec..56ddd68d363e 100644 --- a/media/jni/tuner/TimeFilterClient.h +++ b/media/jni/tuner/TimeFilterClient.h @@ -17,12 +17,13 @@ #ifndef _ANDROID_MEDIA_TV_TIME_FILTER_CLIENT_H_ #define _ANDROID_MEDIA_TV_TIME_FILTER_CLIENT_H_ -//#include <aidl/android/media/tv/tuner/ITunerTimeFilter.h> +#include <aidl/android/media/tv/tuner/ITunerTimeFilter.h> #include <android/hardware/tv/tuner/1.0/ITimeFilter.h> #include <android/hardware/tv/tuner/1.1/types.h> -//using ::aidl::android::media::tv::tuner::ITunerTimeFilter; +using ::aidl::android::media::tv::tuner::ITunerTimeFilter; +using Status = ::ndk::ScopedAStatus; using ::android::hardware::Return; using ::android::hardware::Void; using ::android::hardware::hidl_vec; @@ -36,8 +37,7 @@ namespace android { struct TimeFilterClient : public RefBase { public: - // TODO: add TunerTimeFilter as parameter. - TimeFilterClient(); + TimeFilterClient(shared_ptr<ITunerTimeFilter> tunerTimeFilter); ~TimeFilterClient(); // TODO: remove after migration to Tuner Service is done. @@ -73,8 +73,7 @@ private: * An AIDL Tuner TimeFilter Singleton assigned at the first time the Tuner Client * opens an TimeFilter. Default null when time filter is not opened. */ - // TODO: pending on aidl interface - //shared_ptr<ITunerTimeFilter> mTunerTimeFilter; + shared_ptr<ITunerTimeFilter> mTunerTimeFilter; /** * A TimeFilter HAL interface that is ready before migrating to the TunerTimeFilter. diff --git a/media/jni/tuner/TunerClient.cpp b/media/jni/tuner/TunerClient.cpp index 14393a1081c6..a604490daf58 100644 --- a/media/jni/tuner/TunerClient.cpp +++ b/media/jni/tuner/TunerClient.cpp @@ -200,7 +200,14 @@ sp<DemuxClient> TunerClient::openDemux(int demuxHandle) { } shared_ptr<DemuxCapabilities> TunerClient::getDemuxCaps() { - // pending aidl interface + if (mTunerService != NULL) { + TunerDemuxCapabilities aidlCaps; + Status s = mTunerService->getDemuxCaps(&aidlCaps); + if (ClientHelper::getServiceSpecificErrorCode(s) != Result::SUCCESS) { + return NULL; + } + return make_shared<DemuxCapabilities>(getHidlDemuxCaps(aidlCaps)); + } if (mTuner != NULL) { Result res; @@ -459,6 +466,26 @@ sp<IDescrambler> TunerClient::openHidlDescrambler() { return descrambler; } +DemuxCapabilities TunerClient::getHidlDemuxCaps(TunerDemuxCapabilities& aidlCaps) { + DemuxCapabilities caps{ + .numDemux = (uint32_t)aidlCaps.numDemux, + .numRecord = (uint32_t)aidlCaps.numRecord, + .numPlayback = (uint32_t)aidlCaps.numPlayback, + .numTsFilter = (uint32_t)aidlCaps.numTsFilter, + .numSectionFilter = (uint32_t)aidlCaps.numSectionFilter, + .numAudioFilter = (uint32_t)aidlCaps.numAudioFilter, + .numVideoFilter = (uint32_t)aidlCaps.numVideoFilter, + .numPesFilter = (uint32_t)aidlCaps.numPesFilter, + .numPcrFilter = (uint32_t)aidlCaps.numPcrFilter, + .numBytesInSectionFilter = (uint32_t)aidlCaps.numBytesInSectionFilter, + .filterCaps = (uint32_t)aidlCaps.filterCaps, + .bTimeFilter = aidlCaps.bTimeFilter, + }; + caps.linkCaps.resize(aidlCaps.linkCaps.size()); + copy(aidlCaps.linkCaps.begin(), aidlCaps.linkCaps.end(), caps.linkCaps.begin()); + return caps; +} + FrontendInfo TunerClient::FrontendInfoAidlToHidl(TunerFrontendInfo aidlFrontendInfo) { FrontendInfo hidlFrontendInfo { .type = static_cast<FrontendType>(aidlFrontendInfo.type), diff --git a/media/jni/tuner/TunerClient.h b/media/jni/tuner/TunerClient.h index 6ce666196be8..acd018eb1e44 100644 --- a/media/jni/tuner/TunerClient.h +++ b/media/jni/tuner/TunerClient.h @@ -32,6 +32,7 @@ using Status = ::ndk::ScopedAStatus; +using ::aidl::android::media::tv::tuner::TunerDemuxCapabilities; using ::aidl::android::media::tv::tuner::ITunerService; using ::aidl::android::media::tv::tuner::TunerFrontendInfo; using ::aidl::android::media::tv::tunerresourcemanager::ITunerResourceManager; @@ -145,6 +146,7 @@ private: sp<ILnb> openHidlLnbByName(string name, LnbId& lnbId); sp<IDescrambler> openHidlDescrambler(); vector<int> getLnbHandles(); + DemuxCapabilities getHidlDemuxCaps(TunerDemuxCapabilities& aidlCaps); FrontendInfo FrontendInfoAidlToHidl(TunerFrontendInfo aidlFrontendInfo); void updateTunerResources(); void updateFrontendResources(); diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java index 2716e092c6c3..fa3213d54281 100644 --- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java +++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java @@ -3231,32 +3231,6 @@ public class ConnectivityManager { } } - /** {@hide} - returns the factory serial number */ - @UnsupportedAppUsage - @RequiresPermission(anyOf = { - NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, - android.Manifest.permission.NETWORK_FACTORY}) - public int registerNetworkFactory(Messenger messenger, String name) { - try { - return mService.registerNetworkFactory(messenger, name); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** {@hide} */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - @RequiresPermission(anyOf = { - NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, - android.Manifest.permission.NETWORK_FACTORY}) - public void unregisterNetworkFactory(Messenger messenger) { - try { - mService.unregisterNetworkFactory(messenger); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - /** * Registers the specified {@link NetworkProvider}. * Each listener must only be registered once. The listener can be unregistered with diff --git a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl index 1b4d2e413943..db8b7ed3b177 100644 --- a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl +++ b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl @@ -156,9 +156,6 @@ interface IConnectivityManager boolean requestBandwidthUpdate(in Network network); - int registerNetworkFactory(in Messenger messenger, in String name); - void unregisterNetworkFactory(in Messenger messenger); - int registerNetworkProvider(in Messenger messenger, in String name); void unregisterNetworkProvider(in Messenger messenger); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java index 44864a61ade6..a6e2af9b1674 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java @@ -42,6 +42,7 @@ import android.provider.settings.validators.GlobalSettingsValidators; import android.provider.settings.validators.SecureSettingsValidators; import android.provider.settings.validators.SystemSettingsValidators; import android.provider.settings.validators.Validator; +import android.telephony.SubscriptionManager; import android.util.ArrayMap; import android.util.ArraySet; import android.util.BackupUtils; @@ -95,10 +96,11 @@ public class SettingsBackupAgent extends BackupAgentHelper { private static final String KEY_NETWORK_POLICIES = "network_policies"; private static final String KEY_WIFI_NEW_CONFIG = "wifi_new_config"; private static final String KEY_DEVICE_SPECIFIC_CONFIG = "device_specific_config"; + private static final String KEY_SIM_SPECIFIC_SETTINGS = "sim_specific_settings"; // Versioning of the state file. Increment this version // number any time the set of state items is altered. - private static final int STATE_VERSION = 8; + private static final int STATE_VERSION = 9; // Versioning of the Network Policies backup payload. private static final int NETWORK_POLICIES_BACKUP_VERSION = 1; @@ -106,19 +108,20 @@ public class SettingsBackupAgent extends BackupAgentHelper { // Slots in the checksum array. Never insert new items in the middle // of this array; new slots must be appended. - private static final int STATE_SYSTEM = 0; - private static final int STATE_SECURE = 1; - private static final int STATE_LOCALE = 2; - private static final int STATE_WIFI_SUPPLICANT = 3; - private static final int STATE_WIFI_CONFIG = 4; - private static final int STATE_GLOBAL = 5; - private static final int STATE_LOCK_SETTINGS = 6; - private static final int STATE_SOFTAP_CONFIG = 7; - private static final int STATE_NETWORK_POLICIES = 8; - private static final int STATE_WIFI_NEW_CONFIG = 9; - private static final int STATE_DEVICE_CONFIG = 10; - - private static final int STATE_SIZE = 11; // The current number of state items + private static final int STATE_SYSTEM = 0; + private static final int STATE_SECURE = 1; + private static final int STATE_LOCALE = 2; + private static final int STATE_WIFI_SUPPLICANT = 3; + private static final int STATE_WIFI_CONFIG = 4; + private static final int STATE_GLOBAL = 5; + private static final int STATE_LOCK_SETTINGS = 6; + private static final int STATE_SOFTAP_CONFIG = 7; + private static final int STATE_NETWORK_POLICIES = 8; + private static final int STATE_WIFI_NEW_CONFIG = 9; + private static final int STATE_DEVICE_CONFIG = 10; + private static final int STATE_SIM_SPECIFIC_SETTINGS = 11; + + private static final int STATE_SIZE = 12; // The current number of state items // Number of entries in the checksum array at various version numbers private static final int STATE_SIZES[] = { @@ -130,7 +133,8 @@ public class SettingsBackupAgent extends BackupAgentHelper { 8, // version 5 added STATE_SOFTAP_CONFIG 9, // version 6 added STATE_NETWORK_POLICIES 10, // version 7 added STATE_WIFI_NEW_CONFIG - STATE_SIZE // version 8 added STATE_DEVICE_CONFIG + 11, // version 8 added STATE_DEVICE_CONFIG + STATE_SIZE // version 9 added STATE_SIM_SPECIFIC_SETTINGS }; private static final int FULL_BACKUP_ADDED_GLOBAL = 2; // added the "global" entry @@ -218,6 +222,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { byte[] netPoliciesData = getNetworkPolicies(); byte[] wifiFullConfigData = getNewWifiConfigData(); byte[] deviceSpecificInformation = getDeviceSpecificConfiguration(); + byte[] simSpecificSettingsData = getSimSpecificSettingsData(); long[] stateChecksums = readOldChecksums(oldState); @@ -246,6 +251,9 @@ public class SettingsBackupAgent extends BackupAgentHelper { stateChecksums[STATE_DEVICE_CONFIG] = writeIfChanged(stateChecksums[STATE_DEVICE_CONFIG], KEY_DEVICE_SPECIFIC_CONFIG, deviceSpecificInformation, data); + stateChecksums[STATE_SIM_SPECIFIC_SETTINGS] = + writeIfChanged(stateChecksums[STATE_SIM_SPECIFIC_SETTINGS], + KEY_SIM_SPECIFIC_SETTINGS, simSpecificSettingsData, data); writeNewChecksums(stateChecksums, newState); } @@ -386,6 +394,12 @@ public class SettingsBackupAgent extends BackupAgentHelper { preservedSettings); break; + case KEY_SIM_SPECIFIC_SETTINGS: + byte[] restoredSimSpecificSettings = new byte[size]; + data.readEntityData(restoredSimSpecificSettings, 0, size); + restoreSimSpecificSettings(restoredSimSpecificSettings); + break; + default : data.skipEntityData(); @@ -1189,6 +1203,20 @@ public class SettingsBackupAgent extends BackupAgentHelper { return true; } + private byte[] getSimSpecificSettingsData() { + SubscriptionManager subManager = SubscriptionManager.from(getBaseContext()); + byte[] simSpecificData = subManager.getAllSimSpecificSettingsForBackup(); + Log.i(TAG, "sim specific data of length + " + simSpecificData.length + + " successfully retrieved"); + + return simSpecificData; + } + + private void restoreSimSpecificSettings(byte[] data) { + SubscriptionManager subManager = SubscriptionManager.from(getBaseContext()); + subManager.restoreAllSimSpecificSettingsFromBackup(data); + } + private void updateWindowManagerIfNeeded(Integer previousDensity) { int newDensity; try { diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml index 6dff2e3ef6c2..a6321fe14894 100644 --- a/packages/SystemUI/res/values-sw600dp-land/config.xml +++ b/packages/SystemUI/res/values-sw600dp-land/config.xml @@ -19,4 +19,7 @@ <!-- Max number of columns for quick controls area --> <integer name="controls_max_columns">2</integer> + + <!-- Whether to use the split 2-column notification shade --> + <bool name="config_use_split_notification_shade">true</bool> </resources> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 44eeba1146a4..bb04c3b35628 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -565,4 +565,7 @@ <!-- Whether wallet view is shown in landscape / seascape orientations --> <bool name="global_actions_show_landscape_wallet_view">false</bool> + + <!-- Whether to use the split 2-column notification shade --> + <bool name="config_use_split_notification_shade">false</bool> </resources> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java index 3584c82bc57d..e2ca349cc5c8 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java @@ -72,9 +72,13 @@ public abstract class ActivityOptionsCompat { return ActivityOptions.makeRemoteTransition(remoteTransition.getTransition()); } + /** + * Returns ActivityOptions for overriding task transition animation. + */ public static ActivityOptions makeCustomAnimation(Context context, int enterResId, int exitResId, final Runnable callback, final Handler callbackHandler) { - return ActivityOptions.makeCustomAnimation(context, enterResId, exitResId, callbackHandler, + return ActivityOptions.makeCustomTaskAnimation(context, enterResId, exitResId, + callbackHandler, new ActivityOptions.OnAnimationStartedListener() { @Override public void onAnimationStarted() { diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java index a56c6a1f084e..e6477f158c27 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java @@ -18,9 +18,11 @@ package com.android.systemui.shared.system; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_OLD_NONE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; +import static android.view.WindowManager.TransitionOldType; import android.os.RemoteException; import android.util.Log; @@ -65,13 +67,17 @@ public class RemoteAnimationAdapterCompat { final RemoteAnimationRunnerCompat remoteAnimationAdapter) { return new IRemoteAnimationRunner.Stub() { @Override - public void onAnimationStart(RemoteAnimationTarget[] apps, + public void onAnimationStart(@TransitionOldType int transit, + RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, + RemoteAnimationTarget[] nonApps, final IRemoteAnimationFinishedCallback finishedCallback) { final RemoteAnimationTargetCompat[] appsCompat = RemoteAnimationTargetCompat.wrap(apps); final RemoteAnimationTargetCompat[] wallpapersCompat = RemoteAnimationTargetCompat.wrap(wallpapers); + final RemoteAnimationTargetCompat[] nonAppsCompat = + RemoteAnimationTargetCompat.wrap(nonApps); final Runnable animationFinishedCallback = new Runnable() { @Override public void run() { @@ -83,8 +89,8 @@ public class RemoteAnimationAdapterCompat { } } }; - remoteAnimationAdapter.onAnimationStart(appsCompat, wallpapersCompat, - animationFinishedCallback); + remoteAnimationAdapter.onAnimationStart(transit, appsCompat, wallpapersCompat, + nonAppsCompat, animationFinishedCallback); } @Override @@ -104,6 +110,9 @@ public class RemoteAnimationAdapterCompat { RemoteAnimationTargetCompat.wrap(info, false /* wallpapers */); final RemoteAnimationTargetCompat[] wallpapersCompat = RemoteAnimationTargetCompat.wrap(info, true /* wallpapers */); + // TODO(bc-unlock): Build wrapped object for non-apps target. + final RemoteAnimationTargetCompat[] nonAppsCompat = + new RemoteAnimationTargetCompat[0]; final Runnable animationFinishedCallback = new Runnable() { @Override public void run() { @@ -147,7 +156,10 @@ public class RemoteAnimationAdapterCompat { } } t.apply(); - remoteAnimationAdapter.onAnimationStart(appsCompat, wallpapersCompat, + // TODO(bc-unlcok): Pass correct transit type. + remoteAnimationAdapter.onAnimationStart( + TRANSIT_OLD_NONE, + appsCompat, wallpapersCompat, nonAppsCompat, animationFinishedCallback); } }; diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java index 33372f6bd0b9..007629254c7c 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java @@ -16,8 +16,11 @@ package com.android.systemui.shared.system; +import android.view.WindowManager; + public interface RemoteAnimationRunnerCompat { - void onAnimationStart(RemoteAnimationTargetCompat[] apps, - RemoteAnimationTargetCompat[] wallpapers, Runnable finishedCallback); + void onAnimationStart(@WindowManager.TransitionOldType int transit, + RemoteAnimationTargetCompat[] apps, RemoteAnimationTargetCompat[] wallpapers, + RemoteAnimationTargetCompat[] nonApps, Runnable finishedCallback); void onAnimationCancelled(); }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java index 85e9ca05d428..276036c400e1 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java @@ -74,16 +74,7 @@ public class KeyguardDisplayManager { @Override public void onDisplayChanged(int displayId) { - if (displayId == DEFAULT_DISPLAY) return; - final Presentation presentation = mPresentations.get(displayId); - if (presentation != null && mShowing) { - hidePresentation(displayId); - // update DisplayInfo. - final Display display = mDisplayService.getDisplay(displayId); - if (display != null) { - showPresentation(display); - } - } + } @Override @@ -305,15 +296,16 @@ public class KeyguardDisplayManager { } @Override + public void onDisplayChanged() { + updateBounds(); + getWindow().getDecorView().requestLayout(); + } + + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - final Rect bounds = getWindow().getWindowManager().getMaximumWindowMetrics() - .getBounds(); - mUsableWidth = VIDEO_SAFE_REGION * bounds.width() / 100; - mUsableHeight = VIDEO_SAFE_REGION * bounds.height() / 100; - mMarginLeft = (100 - VIDEO_SAFE_REGION) * bounds.width() / 200; - mMarginTop = (100 - VIDEO_SAFE_REGION) * bounds.height() / 200; + updateBounds(); setContentView(LayoutInflater.from(mContext) .inflate(R.layout.keyguard_presentation, null)); @@ -338,5 +330,14 @@ public class KeyguardDisplayManager { mKeyguardClockSwitchController.init(); } + + private void updateBounds() { + final Rect bounds = getWindow().getWindowManager().getMaximumWindowMetrics() + .getBounds(); + mUsableWidth = VIDEO_SAFE_REGION * bounds.width() / 100; + mUsableHeight = VIDEO_SAFE_REGION * bounds.height() / 100; + mMarginLeft = (100 - VIDEO_SAFE_REGION) * bounds.width() / 200; + mMarginTop = (100 - VIDEO_SAFE_REGION) * bounds.height() / 200; + } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index 1b033e91b76b..17f7ccf0d967 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -17,7 +17,13 @@ package com.android.systemui.keyguard; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY; +import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER; +import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_OCCLUDE; +import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_UNOCCLUDE; +import android.app.ActivityTaskManager; import android.app.Service; import android.content.Intent; import android.os.Binder; @@ -26,8 +32,17 @@ import android.os.Debug; import android.os.IBinder; import android.os.PowerManager; import android.os.Process; +import android.os.RemoteException; +import android.os.SystemProperties; import android.os.Trace; import android.util.Log; +import android.util.Slog; +import android.view.IRemoteAnimationFinishedCallback; +import android.view.IRemoteAnimationRunner; +import android.view.RemoteAnimationAdapter; +import android.view.RemoteAnimationDefinition; +import android.view.RemoteAnimationTarget; +import android.view.WindowManager; import android.view.WindowManagerPolicyConstants; import com.android.internal.policy.IKeyguardDismissCallback; @@ -43,6 +58,21 @@ public class KeyguardService extends Service { static final String TAG = "KeyguardService"; static final String PERMISSION = android.Manifest.permission.CONTROL_KEYGUARD; + /** + * Run Keyguard animation as remote animation in System UI instead of local animation in + * the server process. + * + * Note: Must be consistent with WindowManagerService. + */ + private static final String ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY = + "persist.wm.enable_remote_keyguard_animation"; + + /** + * @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY + */ + private static boolean sEnableRemoteKeyguardAnimation = + SystemProperties.getBoolean(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, false); + private final KeyguardViewMediator mKeyguardViewMediator; private final KeyguardLifecyclesDispatcher mKeyguardLifecyclesDispatcher; @@ -52,6 +82,21 @@ public class KeyguardService extends Service { super(); mKeyguardViewMediator = keyguardViewMediator; mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher; + + if (sEnableRemoteKeyguardAnimation) { + RemoteAnimationDefinition definition = new RemoteAnimationDefinition(); + final RemoteAnimationAdapter exitAnimationAdapter = + new RemoteAnimationAdapter(mExitAnimationRunner, 0, 0); + definition.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_GOING_AWAY, exitAnimationAdapter); + definition.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER, + exitAnimationAdapter); + final RemoteAnimationAdapter occludeAnimationAdapter = + new RemoteAnimationAdapter(mOccludeAnimationRunner, 0, 0); + definition.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_OCCLUDE, occludeAnimationAdapter); + definition.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_UNOCCLUDE, occludeAnimationAdapter); + ActivityTaskManager.getInstance().registerRemoteAnimationsForDisplay( + DEFAULT_DISPLAY, definition); + } } @Override @@ -76,6 +121,48 @@ public class KeyguardService extends Service { } } + private final IRemoteAnimationRunner.Stub mExitAnimationRunner = + new IRemoteAnimationRunner.Stub() { + @Override // Binder interface + public void onAnimationStart(@WindowManager.TransitionOldType int transit, + RemoteAnimationTarget[] apps, + RemoteAnimationTarget[] wallpapers, + RemoteAnimationTarget[] nonApps, + IRemoteAnimationFinishedCallback finishedCallback) { + Trace.beginSection("KeyguardService.mBinder#startKeyguardExitAnimation"); + checkPermission(); + mKeyguardViewMediator.startKeyguardExitAnimation(transit, apps, wallpapers, + null /* nonApps */, finishedCallback); + Trace.endSection(); + } + + @Override // Binder interface + public void onAnimationCancelled() { + } + }; + + private final IRemoteAnimationRunner.Stub mOccludeAnimationRunner = + new IRemoteAnimationRunner.Stub() { + @Override // Binder interface + public void onAnimationStart(@WindowManager.TransitionOldType int transit, + RemoteAnimationTarget[] apps, + RemoteAnimationTarget[] wallpapers, + RemoteAnimationTarget[] nonApps, + IRemoteAnimationFinishedCallback finishedCallback) { + // TODO(bc-unlock): Calls KeyguardViewMediator#setOccluded to update the state and + // run animation. + try { + finishedCallback.onAnimationFinished(); + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException"); + } + } + + @Override // Binder interface + public void onAnimationCancelled() { + } + }; + private final IKeyguardService.Stub mBinder = new IKeyguardService.Stub() { @Override // Binder interface @@ -225,6 +312,11 @@ public class KeyguardService extends Service { mKeyguardViewMediator.onBootCompleted(); } + /** + * @deprecated When remote animation is enabled, this won't be called anymore. Use + * {@code IRemoteAnimationRunner#onAnimationStart} instead. + */ + @Deprecated @Override public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) { Trace.beginSection("KeyguardService.mBinder#startKeyguardExitAnimation"); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index e7326698e43e..5a918d4808d6 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -27,6 +27,9 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE; import static com.android.systemui.DejankUtils.whitelistIpcs; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.AlarmManager; @@ -65,8 +68,13 @@ import android.util.EventLog; import android.util.Log; import android.util.Slog; import android.util.SparseIntArray; +import android.view.IRemoteAnimationFinishedCallback; +import android.view.RemoteAnimationTarget; +import android.view.SyncRtSurfaceTransactionApplier; +import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams; import android.view.View; import android.view.ViewGroup; +import android.view.WindowManager; import android.view.WindowManagerPolicyConstants; import android.view.animation.Animation; import android.view.animation.AnimationUtils; @@ -85,6 +93,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.keyguard.KeyguardViewController; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.Dumpable; +import com.android.systemui.Interpolators; import com.android.systemui.SystemUI; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.classifier.FalsingCollector; @@ -1318,6 +1327,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, if (mHiding && isOccluded) { // We're in the process of going away but WindowManager wants to show a // SHOW_WHEN_LOCKED activity instead. + // TODO(bc-unlock): Migrate to remote animation. startKeyguardExitAnimation(0, 0); } @@ -1703,7 +1713,9 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, Trace.beginSection( "KeyguardViewMediator#handleMessage START_KEYGUARD_EXIT_ANIM"); StartKeyguardExitAnimParams params = (StartKeyguardExitAnimParams) msg.obj; - handleStartKeyguardExitAnimation(params.startTime, params.fadeoutDuration); + handleStartKeyguardExitAnimation(params.startTime, params.fadeoutDuration, + params.mApps, params.mWallpapers, params.mNonApps, + params.mFinishedCallback); mFalsingCollector.onSuccessfulUnlock(); Trace.endSection(); break; @@ -1990,15 +2002,19 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, if (mShowing && !mOccluded) { mKeyguardGoingAwayRunnable.run(); } else { + // TODO(bc-unlock): Fill parameters handleStartKeyguardExitAnimation( SystemClock.uptimeMillis() + mHideAnimation.getStartOffset(), - mHideAnimation.getDuration()); + mHideAnimation.getDuration(), null /* apps */, null /* wallpapers */, + null /* nonApps */, null /* finishedCallback */); } } Trace.endSection(); } - private void handleStartKeyguardExitAnimation(long startTime, long fadeoutDuration) { + private void handleStartKeyguardExitAnimation(long startTime, long fadeoutDuration, + RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, + RemoteAnimationTarget[] nonApps, IRemoteAnimationFinishedCallback finishedCallback) { Trace.beginSection("KeyguardViewMediator#handleStartKeyguardExitAnimation"); if (DEBUG) Log.d(TAG, "handleStartKeyguardExitAnimation startTime=" + startTime + " fadeoutDuration=" + fadeoutDuration); @@ -2031,6 +2047,49 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, mWakeAndUnlocking = false; mDismissCallbackRegistry.notifyDismissSucceeded(); mKeyguardViewControllerLazy.get().hide(startTime, fadeoutDuration); + + // TODO(bc-animation): When remote animation is enabled for keyguard exit animation, + // apps, wallpapers and finishedCallback are set to non-null. nonApps is not yet + // supported, so it's always null. + mContext.getMainExecutor().execute(() -> { + if (finishedCallback == null) { + return; + } + + // TODO(bc-unlock): Sample animation, just to apply alpha animation on the app. + final SyncRtSurfaceTransactionApplier applier = new SyncRtSurfaceTransactionApplier( + mKeyguardViewControllerLazy.get().getViewRootImpl().getView()); + final RemoteAnimationTarget primary = apps[0]; + ValueAnimator anim = ValueAnimator.ofFloat(0, 1); + anim.setDuration(400 /* duration */); + anim.setInterpolator(Interpolators.LINEAR); + anim.addUpdateListener((ValueAnimator animation) -> { + SurfaceParams params = new SurfaceParams.Builder(primary.leash) + .withAlpha(animation.getAnimatedFraction()) + .build(); + applier.scheduleApply(params); + }); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + try { + finishedCallback.onAnimationFinished(); + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException"); + } + } + + @Override + public void onAnimationCancel(Animator animation) { + try { + finishedCallback.onAnimationFinished(); + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException"); + } + } + }); + anim.start(); + }); resetKeyguardDonePendingLocked(); mHideAnimationRun = false; adjustStatusBarLocked(); @@ -2223,10 +2282,55 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, return mKeyguardViewControllerLazy.get(); } + /** + * Notifies to System UI that the activity behind has now been drawn and it's safe to remove + * the wallpaper and keyguard flag, and WindowManager has started running keyguard exit + * animation. + * + * @param startTime the start time of the animation in uptime milliseconds. Deprecated. + * @param fadeoutDuration the duration of the exit animation, in milliseconds Deprecated. + * @deprecated Will be migrate to remote animation soon. + */ + @Deprecated public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) { + startKeyguardExitAnimation(0, startTime, fadeoutDuration, null, null, null, null); + } + + /** + * Notifies to System UI that the activity behind has now been drawn and it's safe to remove + * the wallpaper and keyguard flag, and System UI should start running keyguard exit animation. + * + * @param apps The list of apps to animate. + * @param wallpapers The list of wallpapers to animate. + * @param nonApps The list of non-app windows such as Bubbles to animate. + * @param finishedCallback The callback to invoke when the animation is finished. + */ + public void startKeyguardExitAnimation(@WindowManager.TransitionOldType int transit, + RemoteAnimationTarget[] apps, + RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, + IRemoteAnimationFinishedCallback finishedCallback) { + startKeyguardExitAnimation(transit, 0, 0, apps, wallpapers, nonApps, finishedCallback); + } + + /** + * Notifies to System UI that the activity behind has now been drawn and it's safe to remove + * the wallpaper and keyguard flag, and start running keyguard exit animation. + * + * @param startTime the start time of the animation in uptime milliseconds. Deprecated. + * @param fadeoutDuration the duration of the exit animation, in milliseconds Deprecated. + * @param apps The list of apps to animate. + * @param wallpapers The list of wallpapers to animate. + * @param nonApps The list of non-app windows such as Bubbles to animate. + * @param finishedCallback The callback to invoke when the animation is finished. + */ + private void startKeyguardExitAnimation(@WindowManager.TransitionOldType int transit, + long startTime, long fadeoutDuration, + RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, + RemoteAnimationTarget[] nonApps, IRemoteAnimationFinishedCallback finishedCallback) { Trace.beginSection("KeyguardViewMediator#startKeyguardExitAnimation"); Message msg = mHandler.obtainMessage(START_KEYGUARD_EXIT_ANIM, - new StartKeyguardExitAnimParams(startTime, fadeoutDuration)); + new StartKeyguardExitAnimParams(transit, startTime, fadeoutDuration, apps, + wallpapers, nonApps, finishedCallback)); mHandler.sendMessage(msg); Trace.endSection(); } @@ -2300,12 +2404,26 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, private static class StartKeyguardExitAnimParams { + @WindowManager.TransitionOldType int mTransit; long startTime; long fadeoutDuration; - - private StartKeyguardExitAnimParams(long startTime, long fadeoutDuration) { + RemoteAnimationTarget[] mApps; + RemoteAnimationTarget[] mWallpapers; + RemoteAnimationTarget[] mNonApps; + IRemoteAnimationFinishedCallback mFinishedCallback; + + private StartKeyguardExitAnimParams(@WindowManager.TransitionOldType int transit, + long startTime, long fadeoutDuration, + RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, + RemoteAnimationTarget[] nonApps, + IRemoteAnimationFinishedCallback finishedCallback) { + this.mTransit = transit; this.startTime = startTime; this.fadeoutDuration = fadeoutDuration; + this.mApps = apps; + this.mWallpapers = wallpapers; + this.mNonApps = nonApps; + this.mFinishedCallback = finishedCallback; } } diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java index a4e91896be9e..580cbcf8ce47 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java @@ -24,10 +24,8 @@ import android.app.INotificationManager; import android.app.people.IPeopleManager; import android.app.people.PeopleSpaceTile; import android.appwidget.AppWidgetManager; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.SharedPreferences; import android.content.pm.LauncherApps; import android.content.pm.PackageManager; import android.os.Bundle; @@ -36,12 +34,9 @@ import android.provider.Settings; import android.util.Log; import android.view.ViewGroup; -import androidx.preference.PreferenceManager; - import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.UiEventLoggerImpl; import com.android.systemui.R; -import com.android.systemui.people.widget.PeopleSpaceWidgetProvider; import java.util.List; @@ -128,26 +123,17 @@ public class PeopleSpaceActivity extends Activity { /** Stores the user selected configuration for {@code mAppWidgetId}. */ private void storeWidgetConfiguration(PeopleSpaceTile tile) { - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext); - SharedPreferences.Editor editor = sp.edit(); if (PeopleSpaceUtils.DEBUG) { Log.d(TAG, "Put " + tile.getUserName() + "'s shortcut ID: " + tile.getId() + " for widget ID: " + mAppWidgetId); } - // Ensure updates to app widget can be retrieved from both appWidget Id and tile ID. - editor.putString(String.valueOf(mAppWidgetId), tile.getId()); - editor.putInt(tile.getId(), mAppWidgetId); - editor.apply(); - AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext); - Bundle options = new Bundle(); - options.putParcelable(PeopleSpaceUtils.OPTIONS_PEOPLE_SPACE_TILE, tile); - appWidgetManager.updateAppWidgetOptions(mAppWidgetId, options); - int[] widgetIds = appWidgetManager.getAppWidgetIds( - new ComponentName(mContext, PeopleSpaceWidgetProvider.class)); + + PeopleSpaceUtils.setStorageForTile(mContext, tile, mAppWidgetId); + int[] widgetIds = new int[mAppWidgetId]; // TODO: Populate new widget with existing conversation notification, if there is any. PeopleSpaceUtils.updateSingleConversationWidgets(mContext, widgetIds, mAppWidgetManager, - mNotificationManager); + mPeopleManager); finishActivity(); } diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java index 91e7968f7546..994dc6d78507 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java @@ -18,6 +18,7 @@ package com.android.systemui.people; import static android.app.Notification.EXTRA_MESSAGES; +import android.annotation.Nullable; import android.app.INotificationManager; import android.app.Notification; import android.app.PendingIntent; @@ -70,11 +71,13 @@ import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -89,6 +92,13 @@ public class PeopleSpaceUtils { private static final int MIN_HOUR = 1; private static final int ONE_DAY = 1; public static final String OPTIONS_PEOPLE_SPACE_TILE = "options_people_space_tile"; + public static final String PACKAGE_NAME = "package_name"; + public static final String USER_ID = "user_id"; + public static final String SHORTCUT_ID = "shortcut_id"; + + public static final String EMPTY_STRING = ""; + public static final int INVALID_WIDGET_ID = -1; + public static final int INVALID_USER_ID = -1; private static final Pattern DOUBLE_EXCLAMATION_PATTERN = Pattern.compile("[!][!]+"); private static final Pattern DOUBLE_QUESTION_PATTERN = Pattern.compile("[?][?]+"); @@ -171,70 +181,174 @@ public class PeopleSpaceUtils { * notification being posted or removed. */ public static void updateSingleConversationWidgets(Context context, int[] appWidgetIds, - AppWidgetManager appWidgetManager, INotificationManager notificationManager) { - IPeopleManager peopleManager = IPeopleManager.Stub.asInterface( - ServiceManager.getService(Context.PEOPLE_SERVICE)); - LauncherApps launcherApps = context.getSystemService(LauncherApps.class); - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); + AppWidgetManager appWidgetManager, IPeopleManager peopleManager) { Map<Integer, PeopleSpaceTile> widgetIdToTile = new HashMap<>(); - try { - List<PeopleSpaceTile> tiles = - PeopleSpaceUtils.getTiles(context, notificationManager, - peopleManager, launcherApps); - for (int appWidgetId : appWidgetIds) { - String shortcutId = sp.getString(String.valueOf(appWidgetId), null); - if (DEBUG) { - Log.d(TAG, "Widget ID: " + appWidgetId + " Shortcut ID: " + shortcutId); - } + for (int appWidgetId : appWidgetIds) { + PeopleSpaceTile tile = getPeopleSpaceTile(peopleManager, appWidgetManager, context, + appWidgetId); + if (tile == null) { + if (DEBUG) Log.d(TAG, "Matching conversation not found for shortcut ID"); + //TODO: Delete app widget id when crash is fixed (b/172932636) + continue; + } - Optional<PeopleSpaceTile> entry = tiles.stream().filter( - e -> e.getId().equals(shortcutId)).findFirst(); + if (DEBUG) Log.d(TAG, "Widget: " + appWidgetId + ", " + tile.getUserName()); + RemoteViews views = createRemoteViews(context, tile, appWidgetId); - if (!entry.isPresent() || shortcutId == null) { - if (DEBUG) Log.d(TAG, "Matching conversation not found for shortcut ID"); - //TODO: Delete app widget id when crash is fixed (b/175486868) - continue; - } - // Augment current tile based on stored fields. - PeopleSpaceTile tile = augmentTileFromStorage(entry.get(), appWidgetManager, - appWidgetId); + // Tell the AppWidgetManager to perform an update on the current app widget. + appWidgetManager.updateAppWidget(appWidgetId, views); + + widgetIdToTile.put(appWidgetId, tile); + } + getBirthdaysOnBackgroundThread(context, appWidgetManager, widgetIdToTile, appWidgetIds); + } - RemoteViews views = createRemoteViews(context, tile, appWidgetId); + @Nullable + private static PeopleSpaceTile getPeopleSpaceTile(IPeopleManager peopleManager, + AppWidgetManager appWidgetManager, + Context context, int appWidgetId) { + try { + // Migrate storage for existing users. + SharedPreferences widgetSp = context.getSharedPreferences(String.valueOf(appWidgetId), + Context.MODE_PRIVATE); + String pkg = widgetSp.getString(PACKAGE_NAME, EMPTY_STRING); + int userId = widgetSp.getInt(USER_ID, INVALID_USER_ID); + String shortcutId = widgetSp.getString(SHORTCUT_ID, EMPTY_STRING); + if (pkg.isEmpty() || shortcutId.isEmpty() || userId == INVALID_WIDGET_ID) { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); + shortcutId = sp.getString(String.valueOf(appWidgetId), null); + if (shortcutId == null) { + Log.e(TAG, "Cannot restore widget"); + return null; + } + migrateExistingUsersToNewStorage(context, shortcutId, appWidgetId); + pkg = widgetSp.getString(PACKAGE_NAME, EMPTY_STRING); + userId = widgetSp.getInt(USER_ID, INVALID_USER_ID); + } - // Tell the AppWidgetManager to perform an update on the current app widget. - appWidgetManager.updateAppWidget(appWidgetId, views); + // Check if tile is cached. + Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId); + PeopleSpaceTile tile = options.getParcelable(OPTIONS_PEOPLE_SPACE_TILE); + if (tile != null) { + return tile; + } - widgetIdToTile.put(appWidgetId, tile); + // If tile is null, we need to retrieve from persisted storage. + if (DEBUG) Log.d(TAG, "Retrieving from storage after reboots"); + LauncherApps launcherApps = context.getSystemService(LauncherApps.class); + ConversationChannel channel = peopleManager.getConversation(pkg, userId, shortcutId); + if (channel == null) { + Log.d(TAG, "Could not retrieve conversation from storage"); + return null; } + return new PeopleSpaceTile.Builder(channel, launcherApps).build(); } catch (Exception e) { - Log.e(TAG, "Failed to retrieve conversations to set tiles: " + e); + Log.e(TAG, "Failed to retrieve conversation for tile: " + e); + return null; } - getBirthdaysOnBackgroundThread(context, appWidgetManager, widgetIdToTile, appWidgetIds); } - /** Augment {@link PeopleSpaceTile} with fields from stored tile. */ - @VisibleForTesting - static PeopleSpaceTile augmentTileFromStorage(PeopleSpaceTile tile, - AppWidgetManager appWidgetManager, int appWidgetId) { - Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId); - PeopleSpaceTile storedTile = options.getParcelable(OPTIONS_PEOPLE_SPACE_TILE); - if (storedTile == null) { - return tile; + /** Best-effort attempts to migrate existing users to the new storage format. */ + // TODO: Remove after sufficient time. Temporary migration storage for existing users. + private static void migrateExistingUsersToNewStorage(Context context, String shortcutId, + int appWidgetId) { + try { + List<PeopleSpaceTile> tiles = + PeopleSpaceUtils.getTiles(context, INotificationManager.Stub.asInterface( + ServiceManager.getService(Context.NOTIFICATION_SERVICE)), + IPeopleManager.Stub.asInterface( + ServiceManager.getService(Context.PEOPLE_SERVICE)), + context.getSystemService(LauncherApps.class)); + Optional<PeopleSpaceTile> entry = tiles.stream().filter( + e -> e.getId().equals(shortcutId)).findFirst(); + if (entry.isPresent()) { + if (DEBUG) Log.d(TAG, "Migrate storage for " + entry.get().getUserName()); + setStorageForTile(context, entry.get(), appWidgetId); + } else { + Log.e(TAG, "Could not migrate user"); + } + } catch (Exception e) { + Log.e(TAG, "Could not query conversations"); } - return tile.toBuilder() - .setBirthdayText(storedTile.getBirthdayText()) - .setNotificationKey(storedTile.getNotificationKey()) - .setNotificationContent(storedTile.getNotificationContent()) - .setNotificationDataUri(storedTile.getNotificationDataUri()) - .build(); } - /** If incoming notification changed tile, store the changes in the tile options. */ - public static void storeNotificationChange(StatusBarNotification sbn, + /** Sets all relevant storage for {@code appWidgetId} association to {@code tile}. */ + public static void setStorageForTile(Context context, PeopleSpaceTile tile, int appWidgetId) { + // Write relevant persisted storage. + SharedPreferences widgetSp = context.getSharedPreferences(String.valueOf(appWidgetId), + Context.MODE_PRIVATE); + SharedPreferences.Editor widgetEditor = widgetSp.edit(); + widgetEditor.putString(PeopleSpaceUtils.PACKAGE_NAME, tile.getPackageName()); + widgetEditor.putString(PeopleSpaceUtils.SHORTCUT_ID, tile.getId()); + int userId = UserHandle.getUserHandleForUid(tile.getUid()).getIdentifier(); + widgetEditor.putInt(PeopleSpaceUtils.USER_ID, userId); + widgetEditor.apply(); + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); + SharedPreferences.Editor editor = sp.edit(); + editor.putString(String.valueOf(appWidgetId), tile.getId()); + String key = PeopleSpaceUtils.getKey(tile.getId(), tile.getPackageName(), userId); + // Don't overwrite existing widgets with the same key. + Set<String> storedWidgetIds = new HashSet<>(sp.getStringSet(key, new HashSet<>())); + storedWidgetIds.add(String.valueOf(appWidgetId)); + editor.putStringSet(key, storedWidgetIds); + editor.apply(); + + // Write cached storage. + updateAppWidgetOptionsAndView(AppWidgetManager.getInstance(context), context, appWidgetId, + tile); + } + + /** Removes stored data when tile is deleted. */ + public static void removeStorageForTile(Context context, int widgetId) { + SharedPreferences widgetSp = context.getSharedPreferences(String.valueOf(widgetId), + Context.MODE_PRIVATE); + String packageName = widgetSp.getString(PACKAGE_NAME, null); + String shortcutId = widgetSp.getString(SHORTCUT_ID, null); + int userId = widgetSp.getInt(USER_ID, -1); + + // Delete widgetId mapping to key. + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); + SharedPreferences.Editor editor = sp.edit(); + String key = PeopleSpaceUtils.getKey(shortcutId, packageName, userId); + Set<String> storedWidgetIds = new HashSet<>(sp.getStringSet(key, new HashSet<>())); + storedWidgetIds.remove(String.valueOf(widgetId)); + editor.putStringSet(key, storedWidgetIds); + editor.remove(String.valueOf(widgetId)); + editor.apply(); + + // Delete all data specifically mapped to widgetId. + SharedPreferences.Editor widgetEditor = widgetSp.edit(); + widgetEditor.remove(PACKAGE_NAME); + widgetEditor.remove(USER_ID); + widgetEditor.remove(SHORTCUT_ID); + widgetEditor.apply(); + } + + /** + * Returns whether the data mapped to app widget specified by {@code appWidgetId} matches the + * requested update data. + */ + public static boolean isCorrectAppWidget(Context context, int appWidgetId, String shortcutId, + String packageName, int userId) { + SharedPreferences sp = context.getSharedPreferences(String.valueOf(appWidgetId), + Context.MODE_PRIVATE); + String storedPackage = sp.getString(PACKAGE_NAME, EMPTY_STRING); + int storedUserId = sp.getInt(USER_ID, INVALID_USER_ID); + String storedShortcutId = sp.getString(SHORTCUT_ID, EMPTY_STRING); + return storedPackage.equals(packageName) && storedShortcutId.equals(shortcutId) + && storedUserId == userId; + } + + /** + * If incoming notification changed tile, store the changes in the tile options. + */ + public static void updateWidgetWithNotificationChanged(IPeopleManager peopleManager, + Context context, + StatusBarNotification sbn, NotificationAction notificationAction, AppWidgetManager appWidgetManager, int appWidgetId) { - Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId); - PeopleSpaceTile storedTile = options.getParcelable(OPTIONS_PEOPLE_SPACE_TILE); + PeopleSpaceTile storedTile = getPeopleSpaceTile(peopleManager, appWidgetManager, context, + appWidgetId); if (storedTile == null) { if (DEBUG) Log.d(TAG, "Could not find stored tile to add notification to"); return; @@ -263,7 +377,7 @@ public class PeopleSpaceUtils { .setNotificationDataUri(null) .build(); } - updateAppWidgetOptions(appWidgetManager, appWidgetId, storedTile); + updateAppWidgetOptionsAndView(appWidgetManager, context, appWidgetId, storedTile); } private static void updateAppWidgetOptions(AppWidgetManager appWidgetManager, int appWidgetId, @@ -677,4 +791,23 @@ public class PeopleSpaceUtils { } return lookupKeysWithBirthdaysToday; } + + /** + * Returns the uniquely identifying key for the conversation. + * + * <p>{@code userId} will always be a number, so we put user ID as the + * delimiter between the app-provided strings of shortcut ID and package name. + * + * <p>There aren't restrictions on shortcut ID characters, but there are restrictions requiring + * a {@code packageName} to always start with a letter. This restriction means we are + * guaranteed to avoid cases like "a/b/0/0/package.name" having two potential keys, as the first + * case is impossible given the package name restrictions: + * <ul> + * <li>"a/b" + "/" + 0 + "/" + "0/packageName"</li> + * <li>"a/b/0" + "/" + 0 + "/" + "packageName"</li> + * </ul> + */ + public static String getKey(String shortcutId, String packageName, int userId) { + return shortcutId + "/" + userId + "/" + packageName; + } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java index ad6046ba489d..bee54e4dca0a 100644 --- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java +++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java @@ -16,8 +16,8 @@ package com.android.systemui.people.widget; -import android.app.INotificationManager; import android.app.NotificationChannel; +import android.app.people.IPeopleManager; import android.appwidget.AppWidgetManager; import android.content.ComponentName; import android.content.Context; @@ -29,6 +29,7 @@ import android.provider.Settings; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.util.Log; +import android.widget.RemoteViews; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.appwidget.IAppWidgetService; @@ -37,7 +38,8 @@ import com.android.systemui.people.PeopleSpaceUtils; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationListener.NotificationHandler; -import java.util.Objects; +import java.util.HashSet; +import java.util.Set; import javax.inject.Inject; import javax.inject.Singleton; @@ -51,7 +53,7 @@ public class PeopleSpaceWidgetManager { private final Context mContext; private IAppWidgetService mAppWidgetService; private AppWidgetManager mAppWidgetManager; - private INotificationManager mNotificationManager; + private IPeopleManager mPeopleManager; @Inject public PeopleSpaceWidgetManager(Context context, IAppWidgetService appWidgetService) { @@ -59,11 +61,13 @@ public class PeopleSpaceWidgetManager { mContext = context; mAppWidgetService = appWidgetService; mAppWidgetManager = AppWidgetManager.getInstance(context); - mNotificationManager = INotificationManager.Stub.asInterface( - ServiceManager.getService(Context.NOTIFICATION_SERVICE)); + mPeopleManager = IPeopleManager.Stub.asInterface( + ServiceManager.getService(Context.PEOPLE_SERVICE)); } - /** Constructor used for testing. */ + /** + * Constructor used for testing. + */ @VisibleForTesting protected PeopleSpaceWidgetManager(Context context) { if (DEBUG) Log.d(TAG, "constructor"); @@ -72,16 +76,20 @@ public class PeopleSpaceWidgetManager { ServiceManager.getService(Context.APPWIDGET_SERVICE)); } - /** AppWidgetManager setter used for testing. */ + /** + * AppWidgetManager setter used for testing. + */ @VisibleForTesting protected void setAppWidgetManager(IAppWidgetService appWidgetService, - AppWidgetManager appWidgetManager, INotificationManager notificationManager) { + AppWidgetManager appWidgetManager, IPeopleManager peopleManager) { mAppWidgetService = appWidgetService; mAppWidgetManager = appWidgetManager; - mNotificationManager = notificationManager; + mPeopleManager = peopleManager; } - /** Updates People Space widgets. */ + /** + * Updates People Space widgets. + */ public void updateWidgets() { try { if (DEBUG) Log.d(TAG, "updateWidgets called"); @@ -99,7 +107,7 @@ public class PeopleSpaceWidgetManager { if (showSingleConversation) { PeopleSpaceUtils.updateSingleConversationWidgets(mContext, widgetIds, - mAppWidgetManager, mNotificationManager); + mAppWidgetManager, mPeopleManager); } else { mAppWidgetService .notifyAppWidgetViewDataChanged(mContext.getOpPackageName(), widgetIds, @@ -114,30 +122,38 @@ public class PeopleSpaceWidgetManager { * Check if any existing People tiles match the incoming notification change, and store the * change in the tile if so. */ - public void storeNotificationChange(StatusBarNotification sbn, + public void updateWidgetWithNotificationChanged(StatusBarNotification sbn, PeopleSpaceUtils.NotificationAction notificationAction) { - if (DEBUG) Log.d(TAG, "storeNotificationChange called"); + RemoteViews views = new RemoteViews( + mContext.getPackageName(), R.layout.people_space_small_avatar_tile); + if (DEBUG) Log.d(TAG, "updateWidgetWithNotificationChanged called"); boolean showSingleConversation = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0) == 0; if (!showSingleConversation) { return; } try { + String sbnShortcutId = sbn.getShortcutId(); + if (sbnShortcutId == null) { + return; + } int[] widgetIds = mAppWidgetService.getAppWidgetIds( new ComponentName(mContext, PeopleSpaceWidgetProvider.class) ); if (widgetIds.length == 0) { return; } - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext); - for (int widgetId : widgetIds) { - String shortcutId = sp.getString(String.valueOf(widgetId), null); - if (!Objects.equals(sbn.getShortcutId(), shortcutId)) { - continue; - } + int userId = UserHandle.getUserHandleForUid(sbn.getUid()).getIdentifier(); + String key = PeopleSpaceUtils.getKey(sbnShortcutId, sbn.getPackageName(), userId); + Set<String> storedWidgetIds = new HashSet<>(sp.getStringSet(key, new HashSet<>())); + if (storedWidgetIds.isEmpty()) { + return; + } + for (String widgetIdString : storedWidgetIds) { + int widgetId = Integer.parseInt(widgetIdString); if (DEBUG) Log.d(TAG, "Storing notification change, key:" + sbn.getKey()); - PeopleSpaceUtils.storeNotificationChange( + PeopleSpaceUtils.updateWidgetWithNotificationChanged(mPeopleManager, mContext, sbn, notificationAction, mAppWidgetManager, widgetId); } } catch (Exception e) { @@ -159,8 +175,7 @@ public class PeopleSpaceWidgetManager { public void onNotificationPosted( StatusBarNotification sbn, NotificationListenerService.RankingMap rankingMap) { if (DEBUG) Log.d(TAG, "onNotificationPosted"); - storeNotificationChange(sbn, PeopleSpaceUtils.NotificationAction.POSTED); - updateWidgets(); + updateWidgetWithNotificationChanged(sbn, PeopleSpaceUtils.NotificationAction.POSTED); } @Override @@ -169,8 +184,7 @@ public class PeopleSpaceWidgetManager { NotificationListenerService.RankingMap rankingMap ) { if (DEBUG) Log.d(TAG, "onNotificationRemoved"); - storeNotificationChange(sbn, PeopleSpaceUtils.NotificationAction.REMOVED); - updateWidgets(); + updateWidgetWithNotificationChanged(sbn, PeopleSpaceUtils.NotificationAction.REMOVED); } @Override @@ -179,8 +193,7 @@ public class PeopleSpaceWidgetManager { NotificationListenerService.RankingMap rankingMap, int reason) { if (DEBUG) Log.d(TAG, "onNotificationRemoved with reason " + reason); - storeNotificationChange(sbn, PeopleSpaceUtils.NotificationAction.REMOVED); - updateWidgets(); + updateWidgetWithNotificationChanged(sbn, PeopleSpaceUtils.NotificationAction.REMOVED); } @Override @@ -207,4 +220,4 @@ public class PeopleSpaceWidgetManager { } }; -} +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java index 7f204cc68c54..f5577d30c75c 100644 --- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java +++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java @@ -16,8 +16,8 @@ package com.android.systemui.people.widget; -import android.app.INotificationManager; import android.app.PendingIntent; +import android.app.people.IPeopleManager; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.Context; @@ -53,8 +53,8 @@ public class PeopleSpaceWidgetProvider extends AppWidgetProvider { Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0) == 0; if (showSingleConversation) { PeopleSpaceUtils.updateSingleConversationWidgets(context, appWidgetIds, - appWidgetManager, INotificationManager.Stub.asInterface( - ServiceManager.getService(Context.NOTIFICATION_SERVICE))); + appWidgetManager, IPeopleManager.Stub.asInterface( + ServiceManager.getService(Context.PEOPLE_SERVICE))); return; } // Perform this loop procedure for each App Widget that belongs to this provider @@ -91,6 +91,7 @@ public class PeopleSpaceWidgetProvider extends AppWidgetProvider { for (int widgetId : appWidgetIds) { if (DEBUG) Log.d(TAG, "Widget removed"); mUiEventLogger.log(PeopleSpaceUtils.PeopleSpaceWidgetEvent.PEOPLE_SPACE_WIDGET_DELETED); + PeopleSpaceUtils.removeStorageForTile(context, widgetId); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java index 88b9c6cbddfd..d08f9736adf6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java @@ -33,6 +33,7 @@ import android.view.RemoteAnimationTarget; import android.view.SyncRtSurfaceTransactionApplier; import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams; import android.view.View; +import android.view.WindowManager; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.policy.ScreenDecorationsUtils; @@ -159,8 +160,10 @@ public class ActivityLaunchAnimator { } @Override - public void onAnimationStart(RemoteAnimationTarget[] remoteAnimationTargets, + public void onAnimationStart(@WindowManager.TransitionOldType int transit, + RemoteAnimationTarget[] remoteAnimationTargets, RemoteAnimationTarget[] remoteAnimationWallpaperTargets, + RemoteAnimationTarget[] remoteAnimationNonAppTargets, IRemoteAnimationFinishedCallback iRemoteAnimationFinishedCallback) throws RemoteException { mMainExecutor.execute(() -> { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index a30f193c50a3..3b4759464891 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -95,6 +95,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; import com.android.systemui.qs.QSDetailDisplayer; import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.GestureRecorder; import com.android.systemui.statusbar.KeyguardAffordanceView; import com.android.systemui.statusbar.KeyguardIndicationController; @@ -292,6 +293,7 @@ public class NotificationPanelViewController extends PanelViewController { private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory; private final QSDetailDisplayer mQSDetailDisplayer; + private final FeatureFlags mFeatureFlags; private final ScrimController mScrimController; // Maximum # notifications to show on Keyguard; extras will be collapsed in an overflow card. // If there are exactly 1 + mMaxKeyguardNotifications, then still shows all notifications @@ -554,7 +556,9 @@ public class NotificationPanelViewController extends PanelViewController { QSDetailDisplayer qsDetailDisplayer, ScrimController scrimController, MediaDataManager mediaDataManager, - AmbientState ambientState) { + AmbientState ambientState, + FeatureFlags featureFlags + ) { super(view, falsingManager, dozeLog, keyguardStateController, (SysuiStatusBarStateController) statusBarStateController, vibratorHelper, latencyTracker, flingAnimationUtilsBuilder.get(), statusBarTouchableRegionManager, @@ -571,6 +575,7 @@ public class NotificationPanelViewController extends PanelViewController { mNotificationIconAreaController = notificationIconAreaController; mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory; mQSDetailDisplayer = qsDetailDisplayer; + mFeatureFlags = featureFlags; mView.setWillNotDraw(!DEBUG); mLayoutInflater = layoutInflater; mFalsingManager = falsingManager; @@ -768,6 +773,26 @@ public class NotificationPanelViewController extends PanelViewController { lp.width = panelWidth; mNotificationStackScrollLayoutController.setLayoutParams(lp); } + + if (shouldUseSplitNotificationShade()) { + // In order to change the constraints at runtime, all children of the Constraint Layout + // must have ids. + ensureAllViewsHaveIds(mNotificationContainerParent); + } + } + + private boolean shouldUseSplitNotificationShade() { + return mFeatureFlags.isTwoColumnNotificationShadeEnabled() + && mResources.getBoolean(R.bool.config_use_split_notification_shade); + } + + private static void ensureAllViewsHaveIds(ViewGroup parentView) { + for (int i = 0; i < parentView.getChildCount(); i++) { + View childView = parentView.getChildAt(i); + if (childView.getId() == View.NO_ID) { + childView.setId(View.generateViewId()); + } + } } private void reInflateViews() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java index b7d1bc6c4c63..6db21f9159ec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java @@ -362,34 +362,6 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase { } @Test - public void testAugmentTileFromStorageWithNotification() { - PeopleSpaceTile tile = - new PeopleSpaceTile - .Builder("id", "userName", ICON, new Intent()) - .build(); - PeopleSpaceTile actual = PeopleSpaceUtils - .augmentTileFromStorage(tile, mAppWidgetManager, WIDGET_ID_WITH_SHORTCUT); - - assertThat(actual.getNotificationKey()).isEqualTo(NOTIFICATION_KEY); - assertThat(actual.getNotificationContent()).isEqualTo(NOTIFICATION_CONTENT); - assertThat(actual.getNotificationDataUri()).isEqualTo(URI); - } - - @Test - public void testAugmentTileFromStorageWithoutNotification() { - PeopleSpaceTile tile = - new PeopleSpaceTile - .Builder("id", "userName", ICON, new Intent()) - .build(); - PeopleSpaceTile actual = PeopleSpaceUtils - .augmentTileFromStorage(tile, mAppWidgetManager, WIDGET_ID_WITHOUT_SHORTCUT); - - assertThat(actual.getNotificationKey()).isEqualTo(null); - assertThat(actual.getNotificationKey()).isEqualTo(null); - assertThat(actual.getNotificationDataUri()).isEqualTo(null); - } - - @Test public void testDoNotUpdateSingleConversationAppWidgetWhenNotBirthday() { int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT}; when(mMockCursor.moveToNext()).thenReturn(true, false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java index 8c0afb89b361..ef314ad16556 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java @@ -21,6 +21,8 @@ import static android.app.NotificationManager.IMPORTANCE_HIGH; import static com.android.systemui.people.PeopleSpaceUtils.OPTIONS_PEOPLE_SPACE_TILE; +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; @@ -31,26 +33,24 @@ import static org.mockito.Mockito.when; import static java.util.Objects.requireNonNull; -import android.app.INotificationManager; import android.app.Notification; import android.app.NotificationChannel; import android.app.Person; +import android.app.people.ConversationChannel; +import android.app.people.IPeopleManager; import android.app.people.PeopleSpaceTile; import android.appwidget.AppWidgetManager; +import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; -import android.content.pm.ParceledListSlice; import android.content.pm.ShortcutInfo; import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Bundle; -import android.os.RemoteException; import android.os.UserHandle; import android.provider.Settings; -import android.service.notification.ConversationChannelWrapper; import android.service.notification.StatusBarNotification; import android.testing.AndroidTestingRunner; -import android.widget.RemoteViews; import androidx.preference.PreferenceManager; import androidx.test.filters.SmallTest; @@ -58,6 +58,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.appwidget.IAppWidgetService; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.people.PeopleSpaceUtils; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationListener.NotificationHandler; import com.android.systemui.statusbar.SbnBuilder; @@ -74,26 +75,27 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.util.ArrayList; -import java.util.List; +import java.util.HashSet; +import java.util.Set; @SmallTest @RunWith(AndroidTestingRunner.class) public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { private static final long MIN_LINGER_DURATION = 5; - private static final String TEST_PACKAGE_A = "com.test.package_a"; + private static final String TEST_PACKAGE_A = "com.android.systemui.tests"; private static final String TEST_PACKAGE_B = "com.test.package_b"; private static final String TEST_CHANNEL_ID = "channel_id"; private static final String TEST_CHANNEL_NAME = "channel_name"; private static final String TEST_PARENT_CHANNEL_ID = "parent_channel_id"; private static final String TEST_CONVERSATION_ID = "conversation_id"; private static final int WIDGET_ID_WITH_SHORTCUT = 1; + private static final int SECOND_WIDGET_ID_WITH_SHORTCUT = 3; private static final int WIDGET_ID_WITHOUT_SHORTCUT = 2; private static final String SHORTCUT_ID = "101"; private static final String OTHER_SHORTCUT_ID = "102"; - private static final String NOTIFICATION_KEY = "notification_key"; - private static final String NOTIFICATION_CONTENT = "notification_content"; + private static final String NOTIFICATION_KEY = "0|com.android.systemui.tests|0|null|0"; + private static final String NOTIFICATION_CONTENT = "message text"; private static final Uri URI = Uri.parse("fake_uri"); private static final Icon ICON = Icon.createWithResource("package", R.drawable.ic_android); private static final Person PERSON = new Person.Builder() @@ -105,10 +107,14 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { private static final PeopleSpaceTile PERSON_TILE = new PeopleSpaceTile .Builder(SHORTCUT_ID, "username", ICON, new Intent()) - .setNotificationKey(NOTIFICATION_KEY) + .setPackageName(TEST_PACKAGE_A) + .setUid(0) + .setNotificationKey(NOTIFICATION_KEY + "1") .setNotificationContent(NOTIFICATION_CONTENT) .setNotificationDataUri(URI) .build(); + private final ShortcutInfo mShortcutInfo = new ShortcutInfo.Builder(mContext, + SHORTCUT_ID).setLongLabel("name").build(); private PeopleSpaceWidgetManager mManager; @@ -119,175 +125,101 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { @Mock private AppWidgetManager mAppWidgetManager; @Mock - private INotificationManager mINotificationManager; + private IPeopleManager mIPeopleManager; @Captor private ArgumentCaptor<NotificationHandler> mListenerCaptor; + @Captor + private ArgumentCaptor<Bundle> mBundleArgumentCaptor; private final NoManSimulator mNoMan = new NoManSimulator(); private final FakeSystemClock mClock = new FakeSystemClock(); @Before - public void setUp() { + public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mManager = new PeopleSpaceWidgetManager(mContext); - mManager.setAppWidgetManager(mIAppWidgetService, mAppWidgetManager, mINotificationManager); + mManager.setAppWidgetManager(mIAppWidgetService, mAppWidgetManager, mIPeopleManager); mManager.attach(mListenerService); verify(mListenerService).addNotificationHandler(mListenerCaptor.capture()); NotificationHandler serviceListener = requireNonNull(mListenerCaptor.getValue()); mNoMan.addListener(serviceListener); + // Default to single People tile widgets. Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 2); + Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0); - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext); - SharedPreferences.Editor editor = sp.edit(); - editor.putString(String.valueOf(WIDGET_ID_WITH_SHORTCUT), SHORTCUT_ID); - editor.apply(); - Bundle options = new Bundle(); - options.putParcelable(OPTIONS_PEOPLE_SPACE_TILE, PERSON_TILE); + setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, WIDGET_ID_WITH_SHORTCUT); + Bundle options = new Bundle(); + options.putParcelable(PeopleSpaceUtils.OPTIONS_PEOPLE_SPACE_TILE, PERSON_TILE); when(mAppWidgetManager.getAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT))) .thenReturn(options); when(mAppWidgetManager.getAppWidgetOptions(eq(WIDGET_ID_WITHOUT_SHORTCUT))) .thenReturn(new Bundle()); + when(mIPeopleManager.getConversation(TEST_PACKAGE_A, 0, SHORTCUT_ID)).thenReturn( + getConversationWithShortcutId(SHORTCUT_ID)); } @Test - public void testDoNotNotifyAppWidgetIfNoWidgets() throws RemoteException { - int[] widgetIdsArray = {}; - when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray); - - NotifEvent notif1 = mNoMan.postNotif( - new NotificationEntryBuilder() - .setId(0) - .setPkg(TEST_PACKAGE_A)); - mClock.advanceTime(MIN_LINGER_DURATION); - - verify(mIAppWidgetService, never()).notifyAppWidgetViewDataChanged(any(), any(), anyInt()); - } - - @Test - public void testDoNotNotifySingleConversationAppWidgetIfNoWidgets() throws RemoteException { - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0); + public void testDoNotUpdateAppWidgetIfNoWidgets() throws Exception { int[] widgetIdsArray = {}; when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray); - NotifEvent notif1 = mNoMan.postNotif( - new NotificationEntryBuilder() - .setId(0) - .setPkg(TEST_PACKAGE_A)); - mClock.advanceTime(MIN_LINGER_DURATION); - - verify(mAppWidgetManager, never()).updateAppWidget(anyInt(), any(RemoteViews.class)); - verify(mIAppWidgetService, never()).notifyAppWidgetViewDataChanged(any(), any(), anyInt()); - } - - @Test - public void testNotifyAppWidgetIfNotificationPosted() throws RemoteException { - int[] widgetIdsArray = {1}; - when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray); - - NotifEvent notif1 = mNoMan.postNotif( - new NotificationEntryBuilder() - .setId(0) - .setPkg(TEST_PACKAGE_A)); - mClock.advanceTime(MIN_LINGER_DURATION); - - verify(mIAppWidgetService, times(1)) - .notifyAppWidgetViewDataChanged(any(), eq(widgetIdsArray), anyInt()); - verify(mIAppWidgetService, never()).updateAppWidgetIds(any(), any(), - any(RemoteViews.class)); - } - - @Test - public void testNotifySingleConversationAppWidgetOnceIfNotificationPosted() - throws RemoteException { - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0); - when(mINotificationManager.getConversations(false)).thenReturn( - new ParceledListSlice(getConversationWithShortcutId())); - int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT}; - when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray); - + StatusBarNotification sbn = createConversationNotification(OTHER_SHORTCUT_ID); NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder() - .setPkg(TEST_PACKAGE_A) + .setSbn(sbn) .setId(1)); + mClock.advanceTime(MIN_LINGER_DURATION); - verify(mIAppWidgetService, never()) - .notifyAppWidgetViewDataChanged(any(), eq(widgetIdsArray), anyInt()); - verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT), - any(RemoteViews.class)); - verify(mAppWidgetManager, never()).updateAppWidget(eq(WIDGET_ID_WITHOUT_SHORTCUT), - any(RemoteViews.class)); - } - - @Test - public void testNotifySingleConversationAppWidgetTwiceIfTwoNotificationsPosted() - throws RemoteException { - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0); - when(mINotificationManager.getConversations(false)).thenReturn( - new ParceledListSlice(getConversationWithShortcutId())); - int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT}; - when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray); - - NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder() - .setPkg(TEST_PACKAGE_A) - .setId(1)); - mClock.advanceTime(4); - NotifEvent notif2 = mNoMan.postNotif(new NotificationEntryBuilder() - .setPkg(TEST_PACKAGE_B) - .setId(2)); - - verify(mIAppWidgetService, never()) - .notifyAppWidgetViewDataChanged(any(), eq(widgetIdsArray), anyInt()); - verify(mAppWidgetManager, times(2)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT), - any(RemoteViews.class)); - verify(mAppWidgetManager, never()).updateAppWidget(eq(WIDGET_ID_WITHOUT_SHORTCUT), - any(RemoteViews.class)); + verify(mAppWidgetManager, never()).updateAppWidget(anyInt(), + any()); } @Test - public void testNotifyAppWidgetTwiceIfTwoNotificationsPosted() throws RemoteException { - int[] widgetIdsArray = {1, 2}; + public void testDoNotUpdateAppWidgetIfNoShortcutInfo() throws Exception { + int[] widgetIdsArray = {}; when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray); + Notification notificationWithoutShortcut = new Notification.Builder(mContext) + .setContentTitle("TEST_TITLE") + .setContentText("TEST_TEXT") + .setStyle(new Notification.MessagingStyle(PERSON) + .addMessage(new Notification.MessagingStyle.Message("text3", 10, PERSON)) + ) + .build(); NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder() - .setPkg(TEST_PACKAGE_A) + .setSbn(new SbnBuilder() + .setNotification(notificationWithoutShortcut) + .setPkg(TEST_PACKAGE_A) + .build()) .setId(1)); - mClock.advanceTime(4); - NotifEvent notif2 = mNoMan.postNotif(new NotificationEntryBuilder() - .setPkg(TEST_PACKAGE_B) - .setId(2)); + mClock.advanceTime(MIN_LINGER_DURATION); - verify(mIAppWidgetService, times(2)) - .notifyAppWidgetViewDataChanged(any(), eq(widgetIdsArray), anyInt()); verify(mAppWidgetManager, never()).updateAppWidget(anyInt(), - any(RemoteViews.class)); + any()); } @Test - public void testNotifyAppWidgetTwiceIfNotificationPostedAndRemoved() throws RemoteException { - int[] widgetIdsArray = {1, 2}; + public void testDoNotUpdateAppWidgetIfNoPackage() throws Exception { + int[] widgetIdsArray = {}; when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray); + StatusBarNotification sbnWithoutPackageName = new SbnBuilder() + .setNotification(createMessagingStyleNotification(SHORTCUT_ID)) + .build(); NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder() - .setPkg(TEST_PACKAGE_A) + .setSbn(sbnWithoutPackageName) .setId(1)); - mClock.advanceTime(4); - NotifEvent notif1b = mNoMan.retractNotif(notif1.sbn, 0); + mClock.advanceTime(MIN_LINGER_DURATION); - verify(mIAppWidgetService, times(2)) - .notifyAppWidgetViewDataChanged(any(), eq(widgetIdsArray), anyInt()); verify(mAppWidgetManager, never()).updateAppWidget(anyInt(), - any(RemoteViews.class)); + any()); } @Test - public void testDoNotNotifyAppWidgetIfNonConversationChannelModified() throws RemoteException { + public void testDoNotUpdateAppWidgetIfNonConversationChannelModified() throws Exception { int[] widgetIdsArray = {1}; when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray); @@ -298,13 +230,12 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { UserHandle.getUserHandleForUid(0), channel, IMPORTANCE_HIGH); mClock.advanceTime(MIN_LINGER_DURATION); - verify(mIAppWidgetService, never()).notifyAppWidgetViewDataChanged(any(), any(), anyInt()); verify(mAppWidgetManager, never()).updateAppWidget(anyInt(), - any(RemoteViews.class)); + any()); } @Test - public void testNotifyAppWidgetIfConversationChannelModified() throws RemoteException { + public void testUpdateAppWidgetIfConversationChannelModified() throws Exception { int[] widgetIdsArray = {1}; when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray); @@ -316,45 +247,57 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { UserHandle.getUserHandleForUid(0), channel, IMPORTANCE_HIGH); mClock.advanceTime(MIN_LINGER_DURATION); - verify(mIAppWidgetService, times(1)) - .notifyAppWidgetViewDataChanged(any(), eq(widgetIdsArray), anyInt()); - verify(mAppWidgetManager, never()).updateAppWidget(anyInt(), - any(RemoteViews.class)); + verify(mAppWidgetManager, times(1)).updateAppWidget(anyInt(), + any()); } @Test - public void testDoNotUpdateNotificationPostedIfNoExistingTile() throws RemoteException { - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0); - when(mINotificationManager.getConversations(false)).thenReturn( - new ParceledListSlice(getConversationWithShortcutId())); + public void testDoNotUpdateNotificationPostedIfDifferentShortcutId() throws Exception { int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT}; when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray); StatusBarNotification sbn = createConversationNotification(OTHER_SHORTCUT_ID); NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder() .setSbn(sbn) - .setPkg(TEST_PACKAGE_A) .setId(1)); mClock.advanceTime(MIN_LINGER_DURATION); verify(mAppWidgetManager, never()) - .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT), any()); + .updateAppWidgetOptions(anyInt(), any()); + verify(mAppWidgetManager, never()).updateAppWidget(anyInt(), + any()); } @Test - public void testDoNotUpdateNotificationRemovedIfNoExistingTile() throws RemoteException { + public void testDoNotUpdateNotificationPostedIfDifferentPackageName() throws Exception { Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0); - when(mINotificationManager.getConversations(false)).thenReturn( - new ParceledListSlice(getConversationWithShortcutId())); + int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT}; + when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray); + + StatusBarNotification sbnWithDifferentPackageName = new SbnBuilder() + .setNotification(createMessagingStyleNotification(SHORTCUT_ID)) + .setPkg(TEST_PACKAGE_B) + .build(); + NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder() + .setSbn(sbnWithDifferentPackageName) + .setId(1)); + mClock.advanceTime(MIN_LINGER_DURATION); + + verify(mAppWidgetManager, never()) + .updateAppWidgetOptions(anyInt(), any()); + verify(mAppWidgetManager, never()).updateAppWidget(anyInt(), + any()); + } + + @Test + public void testDoNotUpdateNotificationRemovedIfDifferentShortcutId() throws Exception { int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT}; when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray); StatusBarNotification sbn = createConversationNotification(OTHER_SHORTCUT_ID); NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder() .setSbn(sbn) - .setPkg(TEST_PACKAGE_A) .setId(1)); mClock.advanceTime(4); NotifEvent notif1b = mNoMan.retractNotif(notif1.sbn, 0); @@ -362,99 +305,215 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { verify(mAppWidgetManager, never()) .updateAppWidgetOptions(anyInt(), any()); + verify(mAppWidgetManager, never()).updateAppWidget(anyInt(), + any()); } @Test - public void testUpdateNotificationPostedIfExistingTile() throws RemoteException { - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0); - when(mINotificationManager.getConversations(false)).thenReturn( - new ParceledListSlice(getConversationWithShortcutId())); + public void testDoNotUpdateNotificationRemovedIfDifferentPackageName() throws Exception { int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT}; when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray); - StatusBarNotification sbn = createConversationNotification(SHORTCUT_ID); + StatusBarNotification sbnWithDifferentPackageName = new SbnBuilder() + .setNotification(createMessagingStyleNotification(SHORTCUT_ID)) + .setPkg(TEST_PACKAGE_B) + .build(); NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder() - .setSbn(sbn) - .setPkg(TEST_PACKAGE_A) + .setSbn(sbnWithDifferentPackageName) + .setId(1)); + mClock.advanceTime(4); + NotifEvent notif1b = mNoMan.retractNotif(notif1.sbn, 0); + mClock.advanceTime(MIN_LINGER_DURATION); + + verify(mAppWidgetManager, never()) + .updateAppWidgetOptions(anyInt(), any()); + verify(mAppWidgetManager, never()).updateAppWidget(anyInt(), + any()); + } + + @Test + public void testUpdateNotificationPostedIfExistingTile() throws Exception { + int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT}; + when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray); + + NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder() + .setSbn(createConversationNotification(SHORTCUT_ID)) .setId(1)); mClock.advanceTime(MIN_LINGER_DURATION); verify(mAppWidgetManager, times(1)) - .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT), any()); + .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT), + mBundleArgumentCaptor.capture()); + Bundle bundle = mBundleArgumentCaptor.getValue(); + PeopleSpaceTile tile = bundle.getParcelable(OPTIONS_PEOPLE_SPACE_TILE); + assertThat(tile.getNotificationKey()).isEqualTo(NOTIFICATION_KEY); + assertThat(tile.getNotificationContent()).isEqualTo(NOTIFICATION_CONTENT); + verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT), + any()); + } + + @Test + public void testUpdateNotificationPostedOnTwoExistingTiles() throws Exception { + Bundle options = new Bundle(); + options.putParcelable(PeopleSpaceUtils.OPTIONS_PEOPLE_SPACE_TILE, PERSON_TILE); + when(mAppWidgetManager.getAppWidgetOptions(eq(SECOND_WIDGET_ID_WITH_SHORTCUT))) + .thenReturn(options); + // Set the same Person associated on another People Tile widget ID. + setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, WIDGET_ID_WITH_SHORTCUT); + setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, SECOND_WIDGET_ID_WITH_SHORTCUT); + + int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT, + SECOND_WIDGET_ID_WITH_SHORTCUT}; + when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray); + + NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder() + .setSbn(createConversationNotification(SHORTCUT_ID)) + .setId(1)); + mClock.advanceTime(MIN_LINGER_DURATION); + + verify(mAppWidgetManager, times(1)) + .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT), + any()); + verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT), + any()); + verify(mAppWidgetManager, times(1)) + .updateAppWidgetOptions(eq(SECOND_WIDGET_ID_WITH_SHORTCUT), + any()); + verify(mAppWidgetManager, times(1)).updateAppWidget(eq(SECOND_WIDGET_ID_WITH_SHORTCUT), + any()); + } + + @Test + public void testUpdateNotificationOnExistingTileAfterRemovingTileForSamePerson() + throws Exception { + Bundle options = new Bundle(); + options.putParcelable(PeopleSpaceUtils.OPTIONS_PEOPLE_SPACE_TILE, PERSON_TILE); + when(mAppWidgetManager.getAppWidgetOptions(eq(SECOND_WIDGET_ID_WITH_SHORTCUT))) + .thenReturn(options); + // Set the same Person associated on another People Tile widget ID. + setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, WIDGET_ID_WITH_SHORTCUT); + setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, SECOND_WIDGET_ID_WITH_SHORTCUT); + + int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT, + SECOND_WIDGET_ID_WITH_SHORTCUT}; + when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray); + + PeopleSpaceUtils.removeStorageForTile(mContext, SECOND_WIDGET_ID_WITH_SHORTCUT); + NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder() + .setSbn(createConversationNotification(SHORTCUT_ID)) + .setId(1)); + mClock.advanceTime(MIN_LINGER_DURATION); + + verify(mAppWidgetManager, times(1)) + .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT), + any()); + verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT), + any()); + verify(mAppWidgetManager, never()) + .updateAppWidgetOptions(eq(SECOND_WIDGET_ID_WITH_SHORTCUT), + any()); + verify(mAppWidgetManager, never()).updateAppWidget(eq(SECOND_WIDGET_ID_WITH_SHORTCUT), + any()); } @Test public void testDoNotUpdateNotificationPostedWithoutMessagesIfExistingTile() - throws RemoteException { - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0); - when(mINotificationManager.getConversations(false)).thenReturn( - new ParceledListSlice(getConversationWithShortcutId())); + throws Exception { int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT}; when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray); - Notification notification = new Notification.Builder(mContext) + Notification notificationWithoutMessagingStyle = new Notification.Builder(mContext) .setContentTitle("TEST_TITLE") .setContentText("TEST_TEXT") .setShortcutId(SHORTCUT_ID) .build(); StatusBarNotification sbn = new SbnBuilder() - .setNotification(notification) + .setNotification(notificationWithoutMessagingStyle) .build(); NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder() .setSbn(sbn) - .setPkg(TEST_PACKAGE_A) .setId(1)); mClock.advanceTime(MIN_LINGER_DURATION); verify(mAppWidgetManager, never()) .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT), any()); + verify(mAppWidgetManager, never()).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT), + any()); } @Test - public void testUpdateNotificationRemovedIfExistingTile() throws RemoteException { - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0); - when(mINotificationManager.getConversations(false)).thenReturn( - new ParceledListSlice(getConversationWithShortcutId())); + public void testUpdateNotificationRemovedIfExistingTile() throws Exception { int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT}; when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray); StatusBarNotification sbn = createConversationNotification(SHORTCUT_ID); NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder() .setSbn(sbn) - .setPkg(TEST_PACKAGE_A) .setId(1)); mClock.advanceTime(MIN_LINGER_DURATION); NotifEvent notif1b = mNoMan.retractNotif(notif1.sbn, 0); mClock.advanceTime(MIN_LINGER_DURATION); - verify(mAppWidgetManager, times(2)) - .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT), any()); + verify(mAppWidgetManager, times(2)).updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT), + mBundleArgumentCaptor.capture()); + Bundle bundle = mBundleArgumentCaptor.getValue(); + PeopleSpaceTile tile = bundle.getParcelable(OPTIONS_PEOPLE_SPACE_TILE); + assertThat(tile.getNotificationKey()).isEqualTo(null); + assertThat(tile.getNotificationContent()).isEqualTo(null); + assertThat(tile.getNotificationDataUri()).isEqualTo(null); + verify(mAppWidgetManager, times(2)).updateAppWidget(anyInt(), + any()); } - /** Returns a list of a single conversation associated with {@code SHORTCUT_ID}. */ - private List<ConversationChannelWrapper> getConversationWithShortcutId() { - List<ConversationChannelWrapper> convos = new ArrayList<>(); - ConversationChannelWrapper convo1 = new ConversationChannelWrapper(); - convo1.setShortcutInfo(new ShortcutInfo.Builder(mContext, SHORTCUT_ID).setLongLabel( - "name").build()); - convos.add(convo1); - return convos; + /** + * Returns a single conversation associated with {@code shortcutId}. + */ + private ConversationChannel getConversationWithShortcutId(String shortcutId) throws Exception { + ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(mContext, shortcutId).setLongLabel( + "name").build(); + ConversationChannel convo = new ConversationChannel(shortcutInfo, 0, null, null, + 0L, false); + return convo; } - private StatusBarNotification createConversationNotification(String shortcutId) { - Notification notification = new Notification.Builder(mContext) + private Notification createMessagingStyleNotification(String shortcutId) { + return new Notification.Builder(mContext) .setContentTitle("TEST_TITLE") .setContentText("TEST_TEXT") .setShortcutId(shortcutId) .setStyle(new Notification.MessagingStyle(PERSON) - .addMessage(new Notification.MessagingStyle.Message("text3", 10, PERSON)) + .addMessage( + new Notification.MessagingStyle.Message(NOTIFICATION_CONTENT, 10, + PERSON)) ) .build(); + } + + private StatusBarNotification createConversationNotification(String shortcutId) { + Notification notification = createMessagingStyleNotification(shortcutId); return new SbnBuilder() .setNotification(notification) + .setPkg(TEST_PACKAGE_A) .build(); } + + private void setStorageForTile(String shortcutId, String packageName, int widgetId) { + SharedPreferences widgetSp = mContext.getSharedPreferences( + String.valueOf(widgetId), + Context.MODE_PRIVATE); + SharedPreferences.Editor widgetEditor = widgetSp.edit(); + widgetEditor.putString(PeopleSpaceUtils.PACKAGE_NAME, packageName); + widgetEditor.putString(PeopleSpaceUtils.SHORTCUT_ID, shortcutId); + widgetEditor.putInt(PeopleSpaceUtils.USER_ID, 0); + widgetEditor.apply(); + + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext); + SharedPreferences.Editor editor = sp.edit(); + editor.putString(String.valueOf(widgetId), shortcutId); + String key = PeopleSpaceUtils.getKey(shortcutId, packageName, 0); + Set<String> storedWidgetIds = new HashSet<>(sp.getStringSet(key, new HashSet<>())); + storedWidgetIds.add(String.valueOf(widgetId)); + editor.putStringSet(key, storedWidgetIds); + editor.apply(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java index 8d4470bb1cb6..fa78ca6626e1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java @@ -68,6 +68,7 @@ import com.android.systemui.media.MediaDataManager; import com.android.systemui.media.MediaHierarchyManager; import com.android.systemui.qs.QSDetailDisplayer; import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.KeyguardAffordanceView; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationShelfController; @@ -204,6 +205,10 @@ public class NotificationPanelViewTest extends SysuiTestCase { @Mock private MediaDataManager mMediaDataManager; @Mock + private FeatureFlags mFeatureFlags; + @Mock + private NotificationsQuickSettingsContainer mNotificationContainerParent; + @Mock private AmbientState mAmbientState; private NotificationPanelViewController mNotificationPanelViewController; private View.AccessibilityDelegate mAccessibiltyDelegate; @@ -219,6 +224,8 @@ public class NotificationPanelViewTest extends SysuiTestCase { when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics); mDisplayMetrics.density = 100; when(mResources.getBoolean(R.bool.config_enableNotificationShadeDrag)).thenReturn(true); + when(mResources.getDimensionPixelSize(R.dimen.qs_panel_width)).thenReturn(400); + when(mResources.getDimensionPixelSize(R.dimen.notification_panel_width)).thenReturn(400); when(mView.getContext()).thenReturn(getContext()); when(mView.findViewById(R.id.keyguard_header)).thenReturn(mKeyguardStatusBar); when(mView.findViewById(R.id.keyguard_clock_container)).thenReturn(mKeyguardClockSwitch); @@ -237,6 +244,8 @@ public class NotificationPanelViewTest extends SysuiTestCase { when(mView.findViewById(R.id.keyguard_status_view)) .thenReturn(mock(KeyguardStatusView.class)); when(mView.findViewById(R.id.keyguard_header)).thenReturn(mKeyguardStatusBar); + when(mView.findViewById(R.id.notification_container_parent)) + .thenReturn(mNotificationContainerParent); FlingAnimationUtils.Builder flingAnimationUtilsBuilder = new FlingAnimationUtils.Builder( mDisplayMetrics); @@ -264,6 +273,11 @@ public class NotificationPanelViewTest extends SysuiTestCase { .thenReturn(mKeyguardClockSwitchController); when(mKeyguardStatusViewComponent.getKeyguardStatusViewController()) .thenReturn(mKeyguardStatusViewController); + when(mQsFrame.getLayoutParams()).thenReturn( + new ViewGroup.LayoutParams(600, 400)); + when(mNotificationStackScrollLayoutController.getLayoutParams()).thenReturn( + new ViewGroup.LayoutParams(600, 400)); + mNotificationPanelViewController = new NotificationPanelViewController(mView, mResources, mLayoutInflater, @@ -285,7 +299,8 @@ public class NotificationPanelViewTest extends SysuiTestCase { new QSDetailDisplayer(), mScrimController, mMediaDataManager, - mAmbientState); + mAmbientState, + mFeatureFlags); mNotificationPanelViewController.initDependencies( mStatusBar, mNotificationShelfController); @@ -400,6 +415,25 @@ public class NotificationPanelViewTest extends SysuiTestCase { verify(mStatusBarKeyguardViewManager).showBouncer(true); } + @Test + public void testAllChildrenOfNotificationContainer_haveIds() { + when(mNotificationContainerParent.getChildCount()).thenReturn(2); + when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(true); + when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(true); + + View view1 = new View(mContext); + view1.setId(1); + when(mNotificationContainerParent.getChildAt(0)).thenReturn(view1); + + View view2 = mock(View.class); + when(mNotificationContainerParent.getChildAt(1)).thenReturn(view2); + + mNotificationPanelViewController.updateResources(); + + assertThat(mNotificationContainerParent.getChildAt(0).getId()).isEqualTo(1); + assertThat(mNotificationContainerParent.getChildAt(1).getId()).isNotEqualTo(View.NO_ID); + } + private void onTouchEvent(MotionEvent ev) { mTouchHandler.onTouch(mView, ev); } diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index dad8bd826e3d..9ccb0c786ebe 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -1137,7 +1137,8 @@ public abstract class PackageManagerInternal { */ public abstract void requestChecksums(@NonNull String packageName, boolean includeSplits, @Checksum.Type int optional, @Checksum.Type int required, - @Nullable List trustedInstallers, @NonNull IntentSender statusReceiver, int userId, + @Nullable List trustedInstallers, + @NonNull IOnChecksumsReadyListener onChecksumsReadyListener, int userId, @NonNull Executor executor, @NonNull Handler handler); /** diff --git a/services/core/java/com/android/server/BundleUtils.java b/services/core/java/com/android/server/BundleUtils.java new file mode 100644 index 000000000000..20ebe2985a18 --- /dev/null +++ b/services/core/java/com/android/server/BundleUtils.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Bundle; + +/** + * Utility methods for handling {@link Bundle}. + * + */ +public final class BundleUtils { + private BundleUtils() { + } + + /** + * Returns true if {@code in} is null or empty. + */ + public static boolean isEmpty(@Nullable Bundle in) { + return (in == null) || (in.size() == 0); + } + + /** + * Creates a copy of the {@code in} Bundle. If {@code in} is null, it'll return an empty + * bundle. + * + * <p>The resulting {@link Bundle} is always writable. (i.e. it won't return + * {@link Bundle#EMPTY}) + */ + public static @NonNull Bundle clone(@Nullable Bundle in) { + return (in != null) ? new Bundle(in) : new Bundle(); + } + +} diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 08390b4e52e4..bf9d564e434a 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -1227,6 +1227,14 @@ public class ConnectivityService extends IConnectivityManager.Stub mDnsManager = new DnsManager(mContext, mDnsResolver); registerPrivateDnsSettingsCallbacks(); + + mNoServiceNetwork = new NetworkAgentInfo(null, + new Network(NO_SERVICE_NET_ID), + new NetworkInfo(TYPE_NONE, 0, "", ""), + new LinkProperties(), new NetworkCapabilities(), 0, mContext, + null, new NetworkAgentConfig(), this, null, + null, null, 0, INVALID_UID, + mQosCallbackTracker); } private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) { @@ -1376,7 +1384,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } private NetworkState getUnfilteredActiveNetworkState(int uid) { - NetworkAgentInfo nai = getDefaultNetwork(); + NetworkAgentInfo nai = getDefaultNetworkForUid(uid); final Network[] networks = getVpnUnderlyingNetworks(uid); if (networks != null) { @@ -1509,7 +1517,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - NetworkAgentInfo nai = getDefaultNetwork(); + NetworkAgentInfo nai = getDefaultNetworkForUid(uid); if (nai == null || isNetworkWithCapabilitiesBlocked(nai.networkCapabilities, uid, ignoreBlocked)) { return null; @@ -1648,21 +1656,28 @@ public class ConnectivityService extends IConnectivityManager.Stub HashMap<Network, NetworkCapabilities> result = new HashMap<>(); - final NetworkAgentInfo nai = getDefaultNetwork(); - NetworkCapabilities nc = getNetworkCapabilitiesInternal(nai); - if (nc != null) { - result.put( - nai.network, - createWithLocationInfoSanitizedIfNecessaryWhenParceled( - nc, mDeps.getCallingUid(), callingPackageName)); + for (final NetworkRequestInfo nri : mDefaultNetworkRequests) { + if (!nri.isBeingSatisfied()) { + continue; + } + final NetworkAgentInfo nai = nri.getSatisfier(); + final NetworkCapabilities nc = getNetworkCapabilitiesInternal(nai); + if (null != nc + && nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) + && !result.containsKey(nai.network)) { + result.put( + nai.network, + createWithLocationInfoSanitizedIfNecessaryWhenParceled( + nc, mDeps.getCallingUid(), callingPackageName)); + } } // No need to check mLockdownEnabled. If it's true, getVpnUnderlyingNetworks returns null. final Network[] networks = getVpnUnderlyingNetworks(Binder.getCallingUid()); - if (networks != null) { - for (Network network : networks) { - nc = getNetworkCapabilitiesInternal(network); - if (nc != null) { + if (null != networks) { + for (final Network network : networks) { + final NetworkCapabilities nc = getNetworkCapabilitiesInternal(network); + if (null != nc) { result.put( network, createWithLocationInfoSanitizedIfNecessaryWhenParceled( @@ -1684,9 +1699,7 @@ public class ConnectivityService extends IConnectivityManager.Stub /** * Return LinkProperties for the active (i.e., connected) default - * network interface. It is assumed that at most one default network - * is active at a time. If more than one is active, it is indeterminate - * which will be returned. + * network interface for the calling uid. * @return the ip properties for the active network, or {@code null} if * none is active */ @@ -2129,8 +2142,8 @@ public class ConnectivityService extends IConnectivityManager.Stub private boolean isUidBlockedByRules(int uid, int uidRules, boolean isNetworkMetered, boolean isBackgroundRestricted) { - return NetworkPolicyManagerInternal.isUidNetworkingBlocked(uid, uidRules, - isNetworkMetered, isBackgroundRestricted); + return mPolicyManager.checkUidNetworkingBlocked(uid, uidRules, isNetworkMetered, + isBackgroundRestricted); } /** @@ -2713,9 +2726,9 @@ public class ConnectivityService extends IConnectivityManager.Stub pw.println(nai.requestAt(i).toString()); } pw.decreaseIndent(); - pw.println("Lingered:"); + pw.println("Inactivity Timers:"); pw.increaseIndent(); - nai.dumpLingerTimers(pw); + nai.dumpInactivityTimers(pw); pw.decreaseIndent(); pw.decreaseIndent(); } @@ -3310,27 +3323,27 @@ public class ConnectivityService extends IConnectivityManager.Stub } /** - * Updates the linger state from the network requests inside the NAI. + * Updates the inactivity state from the network requests inside the NAI. * @param nai the agent info to update * @param now the timestamp of the event causing this update - * @return whether the network was lingered as a result of this update + * @return whether the network was inactive as a result of this update */ - private boolean updateLingerState(@NonNull final NetworkAgentInfo nai, final long now) { - // 1. Update the linger timer. If it's changed, reschedule or cancel the alarm. - // 2. If the network was lingering and there are now requests, unlinger it. + private boolean updateInactivityState(@NonNull final NetworkAgentInfo nai, final long now) { + // 1. Update the inactivity timer. If it's changed, reschedule or cancel the alarm. + // 2. If the network was inactive and there are now requests, unset inactive. // 3. If this network is unneeded (which implies it is not lingering), and there is at least - // one lingered request, start lingering. - nai.updateLingerTimer(); + // one lingered request, set inactive. + nai.updateInactivityTimer(); if (nai.isLingering() && nai.numForegroundNetworkRequests() > 0) { - if (DBG) log("Unlingering " + nai.toShortString()); - nai.unlinger(); + if (DBG) log("Unsetting inactive " + nai.toShortString()); + nai.unsetInactive(); logNetworkEvent(nai, NetworkEvent.NETWORK_UNLINGER); - } else if (unneeded(nai, UnneededFor.LINGER) && nai.getLingerExpiry() > 0) { + } else if (unneeded(nai, UnneededFor.LINGER) && nai.getInactivityExpiry() > 0) { if (DBG) { - final int lingerTime = (int) (nai.getLingerExpiry() - now); - log("Lingering " + nai.toShortString() + " for " + lingerTime + "ms"); + final int lingerTime = (int) (nai.getInactivityExpiry() - now); + log("Setting inactive " + nai.toShortString() + " for " + lingerTime + "ms"); } - nai.linger(); + nai.setInactive(); logNetworkEvent(nai, NetworkEvent.NETWORK_LINGER); return true; } @@ -3344,7 +3357,6 @@ public class ConnectivityService extends IConnectivityManager.Stub if (VDBG) log("NetworkFactory connected"); // Finish setting up the full connection NetworkProviderInfo npi = mNetworkProviderInfos.get(msg.replyTo); - npi.completeConnection(); sendAllRequestsToProvider(npi); } else { loge("Error connecting NetworkFactory"); @@ -3446,13 +3458,16 @@ public class ConnectivityService extends IConnectivityManager.Stub propagateUnderlyingNetworkCapabilities(nai.network); // Remove all previously satisfied requests. for (int i = 0; i < nai.numNetworkRequests(); i++) { - NetworkRequest request = nai.requestAt(i); + final NetworkRequest request = nai.requestAt(i); final NetworkRequestInfo nri = mNetworkRequests.get(request); final NetworkAgentInfo currentNetwork = nri.getSatisfier(); if (currentNetwork != null && currentNetwork.network.getNetId() == nai.network.getNetId()) { + // uid rules for this network will be removed in destroyNativeNetwork(nai). nri.setSatisfier(null, null); - sendUpdatedScoreToFactories(request, null); + if (request.isRequest()) { + sendUpdatedScoreToFactories(request, null); + } if (mDefaultRequest == nri) { // TODO : make battery stats aware that since 2013 multiple interfaces may be @@ -3466,7 +3481,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } } } - nai.clearLingerState(); + nai.clearInactivityState(); // TODO: mLegacyTypeTracker.remove seems redundant given there's a full rematch right after. // Currently, deleting it breaks tests that check for the default network disconnecting. // Find out why, fix the rematch code, and delete this. @@ -3565,8 +3580,8 @@ public class ConnectivityService extends IConnectivityManager.Stub } } rematchAllNetworksAndRequests(); - // If an active request exists, return as its score has already been sent if needed. - if (null != nri.getActiveRequest()) { + // If the nri is satisfied, return as its score has already been sent if needed. + if (nri.isBeingSatisfied()) { return; } @@ -3709,7 +3724,7 @@ public class ConnectivityService extends IConnectivityManager.Stub if (mNetworkRequests.get(nri.mRequests.get(0)) == null) { return; } - if (nri.getSatisfier() != null) { + if (nri.isBeingSatisfied()) { return; } if (VDBG || (DBG && nri.mRequests.get(0).isRequest())) { @@ -3808,7 +3823,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // If there are still lingered requests on this network, don't tear it down, // but resume lingering instead. final long now = SystemClock.elapsedRealtime(); - if (updateLingerState(nai, now)) { + if (updateInactivityState(nai, now)) { notifyNetworkLosing(nai, now); } if (unneeded(nai, UnneededFor.TEARDOWN)) { @@ -4900,7 +4915,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // see VpnService.setUnderlyingNetworks()'s javadoc about how to interpret // the underlyingNetworks list. if (underlyingNetworks == null) { - final NetworkAgentInfo defaultNai = getDefaultNetwork(); + final NetworkAgentInfo defaultNai = getDefaultNetworkForUid( + nai.networkCapabilities.getOwnerUid()); if (defaultNai != null) { underlyingNetworks = new Network[] { defaultNai.network }; } @@ -4951,8 +4967,10 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - private Network[] underlyingNetworksOrDefault(Network[] underlyingNetworks) { - final Network defaultNetwork = getNetwork(getDefaultNetwork()); + // TODO This needs to be the default network that applies to the NAI. + private Network[] underlyingNetworksOrDefault(final int ownerUid, + Network[] underlyingNetworks) { + final Network defaultNetwork = getNetwork(getDefaultNetworkForUid(ownerUid)); if (underlyingNetworks == null && defaultNetwork != null) { // null underlying networks means to track the default. underlyingNetworks = new Network[] { defaultNetwork }; @@ -4965,7 +4983,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // TODO: support more than one level of underlying networks, either via a fixed-depth search // (e.g., 2 levels of underlying networks), or via loop detection, or.... if (!nai.supportsUnderlyingNetworks()) return false; - final Network[] underlying = underlyingNetworksOrDefault(nai.declaredUnderlyingNetworks); + final Network[] underlying = underlyingNetworksOrDefault( + nai.networkCapabilities.getOwnerUid(), nai.declaredUnderlyingNetworks); return ArrayUtils.contains(underlying, network); } @@ -5423,27 +5442,21 @@ public class ConnectivityService extends IConnectivityManager.Stub private static class NetworkProviderInfo { public final String name; public final Messenger messenger; - private final AsyncChannel mAsyncChannel; private final IBinder.DeathRecipient mDeathRecipient; public final int providerId; NetworkProviderInfo(String name, Messenger messenger, AsyncChannel asyncChannel, - int providerId, IBinder.DeathRecipient deathRecipient) { + int providerId, @NonNull IBinder.DeathRecipient deathRecipient) { this.name = name; this.messenger = messenger; this.providerId = providerId; - mAsyncChannel = asyncChannel; mDeathRecipient = deathRecipient; - if ((mAsyncChannel == null) == (mDeathRecipient == null)) { - throw new AssertionError("Must pass exactly one of asyncChannel or deathRecipient"); + if (mDeathRecipient == null) { + throw new AssertionError("Must pass a deathRecipient"); } } - boolean isLegacyNetworkFactory() { - return mAsyncChannel != null; - } - void sendMessageToNetworkProvider(int what, int arg1, int arg2, Object obj) { try { messenger.send(Message.obtain(null /* handler */, what, arg1, arg2, obj)); @@ -5454,38 +5467,19 @@ public class ConnectivityService extends IConnectivityManager.Stub } void requestNetwork(NetworkRequest request, int score, int servingProviderId) { - if (isLegacyNetworkFactory()) { - mAsyncChannel.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK, score, - servingProviderId, request); - } else { - sendMessageToNetworkProvider(NetworkProvider.CMD_REQUEST_NETWORK, score, + sendMessageToNetworkProvider(NetworkProvider.CMD_REQUEST_NETWORK, score, servingProviderId, request); - } } void cancelRequest(NetworkRequest request) { - if (isLegacyNetworkFactory()) { - mAsyncChannel.sendMessage(android.net.NetworkFactory.CMD_CANCEL_REQUEST, request); - } else { - sendMessageToNetworkProvider(NetworkProvider.CMD_CANCEL_REQUEST, 0, 0, request); - } + sendMessageToNetworkProvider(NetworkProvider.CMD_CANCEL_REQUEST, 0, 0, request); } void connect(Context context, Handler handler) { - if (isLegacyNetworkFactory()) { - mAsyncChannel.connect(context, handler, messenger); - } else { - try { - messenger.getBinder().linkToDeath(mDeathRecipient, 0); - } catch (RemoteException e) { - mDeathRecipient.binderDied(); - } - } - } - - void completeConnection() { - if (isLegacyNetworkFactory()) { - mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION); + try { + messenger.getBinder().linkToDeath(mDeathRecipient, 0); + } catch (RemoteException e) { + mDeathRecipient.binderDied(); } } } @@ -5521,9 +5515,8 @@ public class ConnectivityService extends IConnectivityManager.Stub mActiveRequest = activeRequest; } - // The network currently satisfying this request, or null if none. Must only be touched - // on the handler thread. This only makes sense for network requests and not for listens, - // as defined by NetworkRequest#isRequest(). For listens, this is always null. + // The network currently satisfying this NRI. Only one request in an NRI can have a + // satisfier. For non-multilayer requests, only REQUEST-type requests can have a satisfier. @Nullable private NetworkAgentInfo mSatisfier; NetworkAgentInfo getSatisfier() { @@ -5546,6 +5539,18 @@ public class ConnectivityService extends IConnectivityManager.Stub final int mUid; final Messenger messenger; + /** + * Get the list of UIDs this nri applies to. + */ + @NonNull + private Set<UidRange> getUids() { + // networkCapabilities.getUids() returns a defensive copy. + // multilayer requests will all have the same uids so return the first one. + final Set<UidRange> uids = null == mRequests.get(0).networkCapabilities.getUids() + ? new ArraySet<>() : mRequests.get(0).networkCapabilities.getUids(); + return uids; + } + NetworkRequestInfo(NetworkRequest r, PendingIntent pi) { mRequests = initializeRequests(r); ensureAllNetworkRequestsHaveType(mRequests); @@ -5579,6 +5584,13 @@ public class ConnectivityService extends IConnectivityManager.Stub this(r, null); } + // True if this NRI is being satisfied. It also accounts for if the nri has its satisifer + // set to the mNoServiceNetwork in which case mActiveRequest will be null thus returning + // false. + boolean isBeingSatisfied() { + return (null != mSatisfier && null != mActiveRequest); + } + boolean isMultilayerRequest() { return mRequests.size() > 1; } @@ -5604,7 +5616,9 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public String toString() { - return "uid/pid:" + mUid + "/" + mPid + " " + mRequests + return "uid/pid:" + mUid + "/" + mPid + " active request Id: " + + (mActiveRequest == null ? null : mActiveRequest.requestId) + + " " + mRequests + (mPendingIntent == null ? "" : " to trigger " + mPendingIntent); } } @@ -5960,15 +5974,6 @@ public class ConnectivityService extends IConnectivityManager.Stub EVENT_RELEASE_NETWORK_REQUEST, getCallingUid(), 0, networkRequest)); } - @Override - public int registerNetworkFactory(Messenger messenger, String name) { - enforceNetworkFactoryPermission(); - NetworkProviderInfo npi = new NetworkProviderInfo(name, messenger, new AsyncChannel(), - nextNetworkProviderId(), null /* deathRecipient */); - mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_PROVIDER, npi)); - return npi.providerId; - } - private void handleRegisterNetworkProvider(NetworkProviderInfo npi) { if (mNetworkProviderInfos.containsKey(npi.messenger)) { // Avoid creating duplicates. even if an app makes a direct AIDL call. @@ -5982,10 +5987,7 @@ public class ConnectivityService extends IConnectivityManager.Stub if (DBG) log("Got NetworkProvider Messenger for " + npi.name); mNetworkProviderInfos.put(npi.messenger, npi); npi.connect(mContext, mTrackerHandler); - if (!npi.isLegacyNetworkFactory()) { - // Legacy NetworkFactories get their requests when their AsyncChannel connects. - sendAllRequestsToProvider(npi); - } + sendAllRequestsToProvider(npi); } @Override @@ -6004,11 +6006,6 @@ public class ConnectivityService extends IConnectivityManager.Stub mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_NETWORK_PROVIDER, messenger)); } - @Override - public void unregisterNetworkFactory(Messenger messenger) { - unregisterNetworkProvider(messenger); - } - private void handleUnregisterNetworkProvider(Messenger messenger) { NetworkProviderInfo npi = mNetworkProviderInfos.remove(messenger); if (npi == null) { @@ -6064,6 +6061,26 @@ public class ConnectivityService extends IConnectivityManager.Stub @NonNull private final ArraySet<NetworkRequestInfo> mDefaultNetworkRequests = new ArraySet<>(); + private boolean isPerAppDefaultRequest(@NonNull final NetworkRequestInfo nri) { + return (mDefaultNetworkRequests.contains(nri) && mDefaultRequest != nri); + } + + /** + * Determine if an nri is a managed default request that disallows default networking. + * @param nri the request to evaluate + * @return true if device-default networking is disallowed + */ + private boolean isDefaultBlocked(@NonNull final NetworkRequestInfo nri) { + // Check if this nri is a managed default that supports the default network at its + // lowest priority request. + final NetworkRequest defaultNetworkRequest = mDefaultRequest.mRequests.get(0); + final NetworkCapabilities lowestPriorityNetCap = + nri.mRequests.get(nri.mRequests.size() - 1).networkCapabilities; + return isPerAppDefaultRequest(nri) + && !(defaultNetworkRequest.networkCapabilities.equalRequestableCapabilities( + lowestPriorityNetCap)); + } + // Request used to optionally keep mobile data active even when higher // priority networks like Wi-Fi are active. private final NetworkRequest mDefaultMobileDataRequest; @@ -6075,12 +6092,39 @@ public class ConnectivityService extends IConnectivityManager.Stub // Request used to optionally keep vehicle internal network always active private final NetworkRequest mDefaultVehicleRequest; - // TODO: b/178729499 update this in favor of a method taking in a UID. + // TODO replace with INetd.DUMMY_NET_ID when available. + private static final int NO_SERVICE_NET_ID = 51; + // Sentinel NAI used to direct apps with default networks that should have no connectivity to a + // network with no service. This NAI should never be matched against, nor should any public API + // ever return the associated network. For this reason, this NAI is not in the list of available + // NAIs. It is used in computeNetworkReassignment() to be set as the satisfier for non-device + // default requests that don't support using the device default network which will ultimately + // allow ConnectivityService to use this no-service network when calling makeDefaultForApps(). + @VisibleForTesting + final NetworkAgentInfo mNoServiceNetwork; + // The NetworkAgentInfo currently satisfying the default request, if any. private NetworkAgentInfo getDefaultNetwork() { return mDefaultRequest.mSatisfier; } + private NetworkAgentInfo getDefaultNetworkForUid(final int uid) { + for (final NetworkRequestInfo nri : mDefaultNetworkRequests) { + // Currently, all network requests will have the same uids therefore checking the first + // one is sufficient. If/when uids are tracked at the nri level, this can change. + final Set<UidRange> uids = nri.mRequests.get(0).networkCapabilities.getUids(); + if (null == uids) { + continue; + } + for (final UidRange range : uids) { + if (range.contains(uid)) { + return nri.getSatisfier(); + } + } + } + return getDefaultNetwork(); + } + @Nullable private Network getNetwork(@Nullable NetworkAgentInfo nai) { return nai != null ? nai.network : null; @@ -6165,8 +6209,6 @@ public class ConnectivityService extends IConnectivityManager.Stub LinkProperties lp = new LinkProperties(linkProperties); - // TODO: Instead of passing mDefaultRequest, provide an API to determine whether a Network - // satisfies mDefaultRequest. final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities); final NetworkAgentInfo nai = new NetworkAgentInfo(na, new Network(mNetIdManager.reserveNetId()), new NetworkInfo(networkInfo), lp, nc, @@ -6589,7 +6631,8 @@ public class ConnectivityService extends IConnectivityManager.Stub @VisibleForTesting void applyUnderlyingCapabilities(@Nullable Network[] underlyingNetworks, @NonNull NetworkCapabilities agentCaps, @NonNull NetworkCapabilities newNc) { - underlyingNetworks = underlyingNetworksOrDefault(underlyingNetworks); + underlyingNetworks = underlyingNetworksOrDefault( + agentCaps.getOwnerUid(), underlyingNetworks); int[] transportTypes = agentCaps.getTransportTypes(); int downKbps = NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED; int upKbps = NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED; @@ -7195,7 +7238,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // If we get here it means that the last linger timeout for this network expired. So there // must be no other active linger timers, and we must stop lingering. - oldNetwork.clearLingerState(); + oldNetwork.clearInactivityState(); if (unneeded(oldNetwork, UnneededFor.TEARDOWN)) { // Tear the network down. @@ -7233,21 +7276,20 @@ public class ConnectivityService extends IConnectivityManager.Stub log("Switching to new default network for: " + nri + " using " + newDefaultNetwork); } - try { - // TODO http://b/176191930 update netd calls in follow-up CL for multinetwork changes. - if (mDefaultRequest != nri) { - return; - } + // Fix up the NetworkCapabilities of any networks that have this network as underlying. + if (newDefaultNetwork != null) { + propagateUnderlyingNetworkCapabilities(newDefaultNetwork.network); + } - if (null != newDefaultNetwork) { - mNetd.networkSetDefault(newDefaultNetwork.network.getNetId()); - } else { - mNetd.networkClearDefault(); - } - } catch (RemoteException | ServiceSpecificException e) { - loge("Exception setting default network :" + e); + // Set an app level managed default and return since further processing only applies to the + // default network. + if (mDefaultRequest != nri) { + makeDefaultForApps(nri, oldDefaultNetwork, newDefaultNetwork); + return; } + makeDefaultNetwork(newDefaultNetwork); + if (oldDefaultNetwork != null) { mLingerMonitor.noteLingerDefaultNetwork(oldDefaultNetwork, newDefaultNetwork); } @@ -7258,10 +7300,6 @@ public class ConnectivityService extends IConnectivityManager.Stub updateTcpBufferSizes(null != newDefaultNetwork ? newDefaultNetwork.linkProperties.getTcpBufferSizes() : null); notifyIfacesChangedForNetworkStats(); - // Fix up the NetworkCapabilities of any networks that have this network as underlying. - if (newDefaultNetwork != null) { - propagateUnderlyingNetworkCapabilities(newDefaultNetwork.network); - } // Log 0 -> X and Y -> X default network transitions, where X is the new default. final Network network = (newDefaultNetwork != null) ? newDefaultNetwork.network : null; @@ -7285,6 +7323,49 @@ public class ConnectivityService extends IConnectivityManager.Stub prevNetwork, prevScore, prevLp, prevNc); } + private void makeDefaultForApps(@NonNull final NetworkRequestInfo nri, + @Nullable final NetworkAgentInfo oldDefaultNetwork, + @Nullable final NetworkAgentInfo newDefaultNetwork) { + try { + if (VDBG) { + log("Setting default network for " + nri + + " using UIDs " + nri.getUids() + + " with old network " + (oldDefaultNetwork != null + ? oldDefaultNetwork.network().getNetId() : "null") + + " and new network " + (newDefaultNetwork != null + ? newDefaultNetwork.network().getNetId() : "null")); + } + if (nri.getUids().isEmpty()) { + throw new IllegalStateException("makeDefaultForApps called without specifying" + + " any applications to set as the default." + nri); + } + if (null != newDefaultNetwork) { + mNetd.networkAddUidRanges( + newDefaultNetwork.network.getNetId(), + toUidRangeStableParcels(nri.getUids())); + } + if (null != oldDefaultNetwork) { + mNetd.networkRemoveUidRanges( + oldDefaultNetwork.network.getNetId(), + toUidRangeStableParcels(nri.getUids())); + } + } catch (RemoteException | ServiceSpecificException e) { + loge("Exception setting OEM network preference default network :" + e); + } + } + + private void makeDefaultNetwork(@Nullable final NetworkAgentInfo newDefaultNetwork) { + try { + if (null != newDefaultNetwork) { + mNetd.networkSetDefault(newDefaultNetwork.network.getNetId()); + } else { + mNetd.networkClearDefault(); + } + } catch (RemoteException | ServiceSpecificException e) { + loge("Exception setting default network :" + e); + } + } + private void processListenRequests(@NonNull final NetworkAgentInfo nai) { // For consistency with previous behaviour, send onLost callbacks before onAvailable. processNewlyLostListenRequests(nai); @@ -7406,9 +7487,9 @@ public class ConnectivityService extends IConnectivityManager.Stub @Nullable final NetworkAgentInfo previousSatisfier, @Nullable final NetworkAgentInfo newSatisfier, final long now) { - if (newSatisfier != null) { + if (null != newSatisfier && mNoServiceNetwork != newSatisfier) { if (VDBG) log("rematch for " + newSatisfier.toShortString()); - if (previousSatisfier != null) { + if (null != previousSatisfier && mNoServiceNetwork != previousSatisfier) { if (VDBG || DDBG) { log(" accepting network in place of " + previousSatisfier.toShortString()); } @@ -7422,7 +7503,7 @@ public class ConnectivityService extends IConnectivityManager.Stub Log.wtf(TAG, "BUG: " + newSatisfier.toShortString() + " already has " + newRequest); } - } else { + } else if (null != previousSatisfier) { if (DBG) { log("Network " + previousSatisfier.toShortString() + " stopped satisfying" + " request " + previousRequest.requestId); @@ -7473,7 +7554,11 @@ public class ConnectivityService extends IConnectivityManager.Stub break; } } - if (bestNetwork != nri.mSatisfier) { + if (null == bestNetwork && isDefaultBlocked(nri)) { + // Remove default networking if disallowed for managed default requests. + bestNetwork = mNoServiceNetwork; + } + if (nri.getSatisfier() != bestNetwork) { // bestNetwork may be null if no network can satisfy this request. changes.addRequestReassignment(new NetworkReassignment.RequestReassignment( nri, nri.mActiveRequest, bestRequest, nri.getSatisfier(), bestNetwork)); @@ -7566,7 +7651,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // if the state has not changed : the source of truth is controlled with // NetworkAgentInfo#lingerRequest and NetworkAgentInfo#unlingerRequest, which have been // called while rematching the individual networks above. - if (updateLingerState(nai, now)) { + if (updateInactivityState(nai, now)) { lingeredNetworks.add(nai); } } @@ -7593,7 +7678,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // Tear down all unneeded networks. for (NetworkAgentInfo nai : mNetworkAgentInfos) { if (unneeded(nai, UnneededFor.TEARDOWN)) { - if (nai.getLingerExpiry() > 0) { + if (nai.getInactivityExpiry() > 0) { // This network has active linger timers and no requests, but is not // lingering. Linger it. // @@ -7601,7 +7686,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // and became unneeded due to another network improving its score to the // point where this network will no longer be able to satisfy any requests // even if it validates. - if (updateLingerState(nai, now)) { + if (updateInactivityState(nai, now)) { notifyNetworkLosing(nai, now); } } else { @@ -7878,7 +7963,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // Notify the requests on this NAI that the network is now lingered. private void notifyNetworkLosing(@NonNull final NetworkAgentInfo nai, final long now) { - final int lingerTime = (int) (nai.getLingerExpiry() - now); + final int lingerTime = (int) (nai.getInactivityExpiry() - now); notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOSING, lingerTime); } @@ -7977,7 +8062,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } NetworkAgentInfo newDefaultAgent = null; if (nai.isSatisfyingRequest(mDefaultRequest.mRequests.get(0).requestId)) { - newDefaultAgent = getDefaultNetwork(); + newDefaultAgent = mDefaultRequest.getSatisfier(); if (newDefaultAgent != null) { intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO, newDefaultAgent.networkInfo); @@ -8025,9 +8110,14 @@ public class ConnectivityService extends IConnectivityManager.Stub private Network[] getDefaultNetworks() { ensureRunningOnConnectivityServiceThread(); final ArrayList<Network> defaultNetworks = new ArrayList<>(); - final NetworkAgentInfo defaultNetwork = getDefaultNetwork(); + final Set<Integer> activeNetIds = new ArraySet<>(); + for (final NetworkRequestInfo nri : mDefaultNetworkRequests) { + if (nri.isBeingSatisfied()) { + activeNetIds.add(nri.getSatisfier().network().netId); + } + } for (NetworkAgentInfo nai : mNetworkAgentInfos) { - if (nai.everConnected && (nai == defaultNetwork || nai.isVPN())) { + if (nai.everConnected && (activeNetIds.contains(nai.network().netId) || nai.isVPN())) { defaultNetworks.add(nai.network); } } diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java index 916bec27af39..4dce59f23a79 100644 --- a/services/core/java/com/android/server/VcnManagementService.java +++ b/services/core/java/com/android/server/VcnManagementService.java @@ -66,6 +66,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; import java.util.concurrent.TimeUnit; /** @@ -291,8 +292,9 @@ public class VcnManagementService extends IVcnManagementService.Stub { @NonNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config, - @NonNull TelephonySubscriptionSnapshot snapshot) { - return new Vcn(vcnContext, subscriptionGroup, config, snapshot); + @NonNull TelephonySubscriptionSnapshot snapshot, + @NonNull VcnSafemodeCallback safemodeCallback) { + return new Vcn(vcnContext, subscriptionGroup, config, snapshot, safemodeCallback); } /** Gets the subId indicated by the given {@link WifiInfo}. */ @@ -438,7 +440,12 @@ public class VcnManagementService extends IVcnManagementService.Stub { // TODO(b/176939047): Support multiple VCNs active at the same time, or limit to one active // VCN. - final Vcn newInstance = mDeps.newVcn(mVcnContext, subscriptionGroup, config, mLastSnapshot); + final VcnSafemodeCallbackImpl safemodeCallback = + new VcnSafemodeCallbackImpl(subscriptionGroup); + + final Vcn newInstance = + mDeps.newVcn( + mVcnContext, subscriptionGroup, config, mLastSnapshot, safemodeCallback); mVcns.put(subscriptionGroup, newInstance); // Now that a new VCN has started, notify all registered listeners to refresh their @@ -536,7 +543,7 @@ public class VcnManagementService extends IVcnManagementService.Stub { } } - /** Get current configuration list for testing purposes */ + /** Get current VCNs for testing purposes */ @VisibleForTesting(visibility = Visibility.PRIVATE) public Map<ParcelUuid, Vcn> getAllVcns() { synchronized (mLock) { @@ -638,8 +645,8 @@ public class VcnManagementService extends IVcnManagementService.Stub { synchronized (mLock) { ParcelUuid subGroup = mLastSnapshot.getGroupForSubId(subId); - // TODO(b/178140910): only mark the Network as VCN-managed if not in safe mode - if (mVcns.containsKey(subGroup)) { + Vcn vcn = mVcns.get(subGroup); + if (vcn != null && vcn.isActive()) { isVcnManagedNetwork = true; } } @@ -651,4 +658,31 @@ public class VcnManagementService extends IVcnManagementService.Stub { return new VcnUnderlyingNetworkPolicy(false /* isTearDownRequested */, networkCapabilities); } + + /** Callback for signalling when a Vcn has entered Safemode. */ + public interface VcnSafemodeCallback { + /** Called by a Vcn to signal that it has entered Safemode. */ + void onEnteredSafemode(); + } + + /** VcnSafemodeCallback is used by Vcns to notify VcnManagementService on entering Safemode. */ + private class VcnSafemodeCallbackImpl implements VcnSafemodeCallback { + @NonNull private final ParcelUuid mSubGroup; + + private VcnSafemodeCallbackImpl(@NonNull final ParcelUuid subGroup) { + mSubGroup = Objects.requireNonNull(subGroup, "Missing subGroup"); + } + + @Override + public void onEnteredSafemode() { + synchronized (mLock) { + // Ignore if this subscription group doesn't exist anymore + if (!mVcns.containsKey(mSubGroup)) { + return; + } + + notifyAllPolicyListenersLocked(); + } + } + } } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index a43f0c6647e1..e7e98324605a 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -300,6 +300,7 @@ public class AudioService extends IAudioService.Stub private static final int MSG_STREAM_DEVICES_CHANGED = 32; private static final int MSG_UPDATE_VOLUME_STATES_FOR_DEVICE = 33; private static final int MSG_REINIT_VOLUMES = 34; + private static final int MSG_UPDATE_A11Y_SERVICE_UIDS = 35; // start of messages handled under wakelock // these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(), // and not with sendMsg(..., ..., SENDMSG_QUEUE, ...) @@ -699,8 +700,9 @@ public class AudioService extends IAudioService.Stub private long mLoweredFromNormalToVibrateTime; // Array of Uids of valid accessibility services to check if caller is one of them - private int[] mAccessibilityServiceUids; private final Object mAccessibilityServiceUidsLock = new Object(); + @GuardedBy("mAccessibilityServiceUidsLock") + private int[] mAccessibilityServiceUids; // Uid of the active input method service to check if caller is the one or not. private int mInputMethodServiceUid = android.os.Process.INVALID_UID; @@ -2311,11 +2313,9 @@ public class AudioService extends IAudioService.Stub /** @see AudioManager#adjustVolume(int, int) */ public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags, String callingPackage, String caller) { - boolean hasModifyAudioSettings = - mContext.checkCallingPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS) - == PackageManager.PERMISSION_GRANTED; adjustSuggestedStreamVolume(direction, suggestedStreamType, flags, callingPackage, - caller, Binder.getCallingUid(), hasModifyAudioSettings, VOL_ADJUST_NORMAL); + caller, Binder.getCallingUid(), callingHasAudioSettingsPermission(), + VOL_ADJUST_NORMAL); } public void setFastScrollSoundEffectsEnabled(boolean enabled) { @@ -2442,13 +2442,12 @@ public class AudioService extends IAudioService.Stub + "CHANGE_ACCESSIBILITY_VOLUME / callingPackage=" + callingPackage); return; } - final boolean hasModifyAudioSettings = - mContext.checkCallingPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS) - == PackageManager.PERMISSION_GRANTED; + sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_STREAM_VOL, streamType, direction/*val1*/, flags/*val2*/, callingPackage)); adjustStreamVolume(streamType, direction, flags, callingPackage, callingPackage, - Binder.getCallingUid(), hasModifyAudioSettings, VOL_ADJUST_NORMAL); + Binder.getCallingUid(), callingHasAudioSettingsPermission(), + VOL_ADJUST_NORMAL); } protected void adjustStreamVolume(int streamType, int direction, int flags, @@ -2966,13 +2965,11 @@ public class AudioService extends IAudioService.Stub + " MODIFY_AUDIO_ROUTING callingPackage=" + callingPackage); return; } - final boolean hasModifyAudioSettings = - mContext.checkCallingOrSelfPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS) - == PackageManager.PERMISSION_GRANTED; + sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType, index/*val1*/, flags/*val2*/, callingPackage)); setStreamVolume(streamType, index, flags, callingPackage, callingPackage, - Binder.getCallingUid(), hasModifyAudioSettings); + Binder.getCallingUid(), callingOrSelfHasAudioSettingsPermission()); } private boolean canChangeAccessibilityVolume() { @@ -3639,8 +3636,7 @@ public class AudioService extends IAudioService.Stub ensureValidStreamType(streamType); final boolean isPrivileged = Binder.getCallingUid() == Process.SYSTEM_UID - || (mContext.checkCallingPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS) - == PackageManager.PERMISSION_GRANTED) + || callingHasAudioSettingsPermission() || (mContext.checkCallingPermission(Manifest.permission.MODIFY_AUDIO_ROUTING) == PackageManager.PERMISSION_GRANTED); return (mStreamStates[streamType].getMinIndex(isPrivileged) + 5) / 10; @@ -4383,13 +4379,10 @@ public class AudioService extends IAudioService.Stub throw new SecurityException("Should only be called from system process"); } - final boolean hasModifyAudioSettings = - mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS, pid, uid) - == PackageManager.PERMISSION_GRANTED; // direction and stream type swap here because the public // adjustSuggested has a different order than the other methods. adjustSuggestedStreamVolume(direction, streamType, flags, packageName, packageName, uid, - hasModifyAudioSettings, VOL_ADJUST_NORMAL); + hasAudioSettingsPermission(uid, pid), VOL_ADJUST_NORMAL); } /** @see AudioManager#adjustStreamVolumeForUid(int, int, int, String, int, int, int) */ @@ -4407,11 +4400,9 @@ public class AudioService extends IAudioService.Stub new StringBuilder(packageName).append(" uid:").append(uid) .toString())); } - final boolean hasModifyAudioSettings = - mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS, pid, uid) - == PackageManager.PERMISSION_GRANTED; + adjustStreamVolume(streamType, direction, flags, packageName, packageName, uid, - hasModifyAudioSettings, VOL_ADJUST_NORMAL); + hasAudioSettingsPermission(uid, pid), VOL_ADJUST_NORMAL); } /** @see AudioManager#setStreamVolumeForUid(int, int, int, String, int, int, int) */ @@ -4423,11 +4414,8 @@ public class AudioService extends IAudioService.Stub throw new SecurityException("Should only be called from system process"); } - final boolean hasModifyAudioSettings = - mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS, pid, uid) - == PackageManager.PERMISSION_GRANTED; setStreamVolume(streamType, index, flags, packageName, packageName, uid, - hasModifyAudioSettings); + hasAudioSettingsPermission(uid, pid)); } //========================================================================================== @@ -5393,8 +5381,7 @@ public class AudioService extends IAudioService.Stub } boolean checkAudioSettingsPermission(String method) { - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS) - == PackageManager.PERMISSION_GRANTED) { + if (callingOrSelfHasAudioSettingsPermission()) { return true; } String msg = "Audio Settings Permission Denial: " + method + " from pid=" @@ -5404,6 +5391,21 @@ public class AudioService extends IAudioService.Stub return false; } + private boolean callingOrSelfHasAudioSettingsPermission() { + return mContext.checkCallingOrSelfPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS) + == PackageManager.PERMISSION_GRANTED; + } + + private boolean callingHasAudioSettingsPermission() { + return mContext.checkCallingPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS) + == PackageManager.PERMISSION_GRANTED; + } + + private boolean hasAudioSettingsPermission(int uid, int pid) { + return mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS, pid, uid) + == PackageManager.PERMISSION_GRANTED; + } + /** * Minimum attenuation that can be set for alarms over speaker by an application that * doesn't have the MODIFY_AUDIO_SETTINGS permission. @@ -7082,6 +7084,10 @@ public class AudioService extends IAudioService.Stub case MSG_REINIT_VOLUMES: onReinitVolumes((String) msg.obj); break; + case MSG_UPDATE_A11Y_SERVICE_UIDS: + onUpdateAccessibilityServiceUids(); + break; + } } } @@ -8148,6 +8154,9 @@ public class AudioService extends IAudioService.Stub + " FromRestrictions=" + mMicMuteFromRestrictions + " FromApi=" + mMicMuteFromApi + " from system=" + mMicMuteFromSystemCached); + pw.print("\n mAssistantUid="); pw.println(mAssistantUid); + pw.print(" mCurrentImeUid="); pw.println(mCurrentImeUid); + dumpAccessibilityServiceUids(pw); dumpAudioPolicies(pw); mDynPolicyLogger.dump(pw); @@ -8181,6 +8190,19 @@ public class AudioService extends IAudioService.Stub } } + private void dumpAccessibilityServiceUids(PrintWriter pw) { + synchronized (mSupportedSystemUsagesLock) { + if (mAccessibilityServiceUids != null && mAccessibilityServiceUids.length > 0) { + pw.println(" Accessibility service Uids:"); + for (int uid : mAccessibilityServiceUids) { + pw.println(" - " + uid); + } + } else { + pw.println(" No accessibility service Uids."); + } + } + } + /** * Audio Analytics ids. */ @@ -8484,7 +8506,8 @@ public class AudioService extends IAudioService.Stub mAccessibilityServiceUids = uids.toArray(); } } - AudioSystem.setA11yServicesUids(mAccessibilityServiceUids); + sendMsg(mAudioHandler, MSG_UPDATE_A11Y_SERVICE_UIDS, SENDMSG_REPLACE, + 0, 0, null, 0); } } @@ -8502,6 +8525,14 @@ public class AudioService extends IAudioService.Stub } } + private void onUpdateAccessibilityServiceUids() { + int[] accessibilityServiceUids; + synchronized (mAccessibilityServiceUidsLock) { + accessibilityServiceUids = mAccessibilityServiceUids; + } + AudioSystem.setA11yServicesUids(accessibilityServiceUids); + } + //========================================================================================== // Audio policy management //========================================================================================== diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index 1a4f20c7101e..a9a705f07ac4 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -210,23 +210,23 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { // network is taken down. This usually only happens to the default network. Lingering ends with // either the linger timeout expiring and the network being taken down, or the network // satisfying a request again. - public static class LingerTimer implements Comparable<LingerTimer> { + public static class InactivityTimer implements Comparable<InactivityTimer> { public final int requestId; public final long expiryMs; - public LingerTimer(int requestId, long expiryMs) { + public InactivityTimer(int requestId, long expiryMs) { this.requestId = requestId; this.expiryMs = expiryMs; } public boolean equals(Object o) { - if (!(o instanceof LingerTimer)) return false; - LingerTimer other = (LingerTimer) o; + if (!(o instanceof InactivityTimer)) return false; + InactivityTimer other = (InactivityTimer) o; return (requestId == other.requestId) && (expiryMs == other.expiryMs); } public int hashCode() { return Objects.hash(requestId, expiryMs); } - public int compareTo(LingerTimer other) { + public int compareTo(InactivityTimer other) { return (expiryMs != other.expiryMs) ? Long.compare(expiryMs, other.expiryMs) : Integer.compare(requestId, other.requestId); @@ -269,30 +269,31 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { */ public static final int ARG_AGENT_SUCCESS = 1; - // All linger timers for this network, sorted by expiry time. A linger timer is added whenever + // All inactivity timers for this network, sorted by expiry time. A timer is added whenever // a request is moved to a network with a better score, regardless of whether the network is or // was lingering or not. // TODO: determine if we can replace this with a smaller or unsorted data structure. (e.g., // SparseLongArray) combined with the timestamp of when the last timer is scheduled to fire. - private final SortedSet<LingerTimer> mLingerTimers = new TreeSet<>(); + private final SortedSet<InactivityTimer> mInactivityTimers = new TreeSet<>(); - // For fast lookups. Indexes into mLingerTimers by request ID. - private final SparseArray<LingerTimer> mLingerTimerForRequest = new SparseArray<>(); + // For fast lookups. Indexes into mInactivityTimers by request ID. + private final SparseArray<InactivityTimer> mInactivityTimerForRequest = new SparseArray<>(); - // Linger expiry timer. Armed whenever mLingerTimers is non-empty, regardless of whether the - // network is lingering or not. Always set to the expiry of the LingerTimer that expires last. - // When the timer fires, all linger state is cleared, and if the network has no requests, it is - // torn down. - private WakeupMessage mLingerMessage; + // Inactivity expiry timer. Armed whenever mInactivityTimers is non-empty, regardless of + // whether the network is inactive or not. Always set to the expiry of the mInactivityTimers + // that expires last. When the timer fires, all inactivity state is cleared, and if the network + // has no requests, it is torn down. + private WakeupMessage mInactivityMessage; - // Linger expiry. Holds the expiry time of the linger timer, or 0 if the timer is not armed. - private long mLingerExpiryMs; + // Inactivity expiry. Holds the expiry time of the inactivity timer, or 0 if the timer is not + // armed. + private long mInactivityExpiryMs; - // Whether the network is lingering or not. Must be maintained separately from the above because + // Whether the network is inactive or not. Must be maintained separately from the above because // it depends on the state of other networks and requests, which only ConnectivityService knows. // (Example: we don't linger a network if it would become the best for a NetworkRequest if it // validated). - private boolean mLingering; + private boolean mInactive; // This represents the quality of the network with no clear scale. private int mScore; @@ -898,17 +899,17 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { * ConnectivityService when the request is moved to another network with a higher score. */ public void lingerRequest(int requestId, long now, long duration) { - if (mLingerTimerForRequest.get(requestId) != null) { + if (mInactivityTimerForRequest.get(requestId) != null) { // Cannot happen. Once a request is lingering on a particular network, we cannot // re-linger it unless that network becomes the best for that request again, in which // case we should have unlingered it. Log.wtf(TAG, toShortString() + ": request " + requestId + " already lingered"); } final long expiryMs = now + duration; - LingerTimer timer = new LingerTimer(requestId, expiryMs); - if (VDBG) Log.d(TAG, "Adding LingerTimer " + timer + " to " + toShortString()); - mLingerTimers.add(timer); - mLingerTimerForRequest.put(requestId, timer); + InactivityTimer timer = new InactivityTimer(requestId, expiryMs); + if (VDBG) Log.d(TAG, "Adding InactivityTimer " + timer + " to " + toShortString()); + mInactivityTimers.add(timer); + mInactivityTimerForRequest.put(requestId, timer); } /** @@ -916,23 +917,25 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { * Returns true if the given requestId was lingering on this network, false otherwise. */ public boolean unlingerRequest(int requestId) { - LingerTimer timer = mLingerTimerForRequest.get(requestId); + InactivityTimer timer = mInactivityTimerForRequest.get(requestId); if (timer != null) { - if (VDBG) Log.d(TAG, "Removing LingerTimer " + timer + " from " + toShortString()); - mLingerTimers.remove(timer); - mLingerTimerForRequest.remove(requestId); + if (VDBG) { + Log.d(TAG, "Removing InactivityTimer " + timer + " from " + toShortString()); + } + mInactivityTimers.remove(timer); + mInactivityTimerForRequest.remove(requestId); return true; } return false; } - public long getLingerExpiry() { - return mLingerExpiryMs; + public long getInactivityExpiry() { + return mInactivityExpiryMs; } - public void updateLingerTimer() { - long newExpiry = mLingerTimers.isEmpty() ? 0 : mLingerTimers.last().expiryMs; - if (newExpiry == mLingerExpiryMs) return; + public void updateInactivityTimer() { + long newExpiry = mInactivityTimers.isEmpty() ? 0 : mInactivityTimers.last().expiryMs; + if (newExpiry == mInactivityExpiryMs) return; // Even if we're going to reschedule the timer, cancel it first. This is because the // semantics of WakeupMessage guarantee that if cancel is called then the alarm will @@ -940,49 +943,52 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { // WakeupMessage makes no such guarantees about rescheduling a message, so if mLingerMessage // has already been dispatched, rescheduling to some time in the future won't stop it // from calling its callback immediately. - if (mLingerMessage != null) { - mLingerMessage.cancel(); - mLingerMessage = null; + if (mInactivityMessage != null) { + mInactivityMessage.cancel(); + mInactivityMessage = null; } if (newExpiry > 0) { - mLingerMessage = new WakeupMessage( + mInactivityMessage = new WakeupMessage( mContext, mHandler, "NETWORK_LINGER_COMPLETE." + network.getNetId() /* cmdName */, EVENT_NETWORK_LINGER_COMPLETE /* cmd */, 0 /* arg1 (unused) */, 0 /* arg2 (unused) */, this /* obj (NetworkAgentInfo) */); - mLingerMessage.schedule(newExpiry); + mInactivityMessage.schedule(newExpiry); } - mLingerExpiryMs = newExpiry; + mInactivityExpiryMs = newExpiry; } - public void linger() { - mLingering = true; + public void setInactive() { + mInactive = true; } - public void unlinger() { - mLingering = false; + public void unsetInactive() { + mInactive = false; } public boolean isLingering() { - return mLingering; + return mInactive; } - public void clearLingerState() { - if (mLingerMessage != null) { - mLingerMessage.cancel(); - mLingerMessage = null; + public void clearInactivityState() { + if (mInactivityMessage != null) { + mInactivityMessage.cancel(); + mInactivityMessage = null; } - mLingerTimers.clear(); - mLingerTimerForRequest.clear(); - updateLingerTimer(); // Sets mLingerExpiryMs, cancels and nulls out mLingerMessage. - mLingering = false; + mInactivityTimers.clear(); + mInactivityTimerForRequest.clear(); + // Sets mInactivityExpiryMs, cancels and nulls out mInactivityMessage. + updateInactivityTimer(); + mInactive = false; } - public void dumpLingerTimers(PrintWriter pw) { - for (LingerTimer timer : mLingerTimers) { pw.println(timer); } + public void dumpInactivityTimers(PrintWriter pw) { + for (InactivityTimer timer : mInactivityTimers) { + pw.println(timer); + } } /** diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index d956ba375ba1..e8062ae0eb57 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -203,6 +203,7 @@ public class Vpn { protected final NetworkCapabilities mNetworkCapabilities; private final SystemServices mSystemServices; private final Ikev2SessionCreator mIkev2SessionCreator; + private final UserManager mUserManager; /** * Whether to keep the connection active after rebooting, or upgrading or reinstalling. This @@ -277,6 +278,10 @@ public class Vpn { return LocalServices.getService(DeviceIdleInternal.class); } + public PendingIntent getIntentForStatusPanel(Context context) { + return VpnConfig.getIntentForStatusPanel(context); + } + public void sendArgumentsToDaemon( final String daemon, final LocalSocket socket, final String[] arguments, final RetryScheduler retryScheduler) throws IOException, InterruptedException { @@ -405,6 +410,7 @@ public class Vpn { mLooper = looper; mSystemServices = systemServices; mIkev2SessionCreator = ikev2SessionCreator; + mUserManager = mContext.getSystemService(UserManager.class); mPackage = VpnConfig.LEGACY_VPN; mOwnerUID = getAppUid(mPackage, mUserId); @@ -1431,7 +1437,7 @@ public class Vpn { final long token = Binder.clearCallingIdentity(); List<UserInfo> users; try { - users = UserManager.get(mContext).getAliveUsers(); + users = mUserManager.getAliveUsers(); } finally { Binder.restoreCallingIdentity(token); } @@ -1515,7 +1521,7 @@ public class Vpn { */ public void onUserAdded(int userId) { // If the user is restricted tie them to the parent user's VPN - UserInfo user = UserManager.get(mContext).getUserInfo(userId); + UserInfo user = mUserManager.getUserInfo(userId); if (user.isRestricted() && user.restrictedProfileParentId == mUserId) { synchronized(Vpn.this) { final Set<UidRange> existingRanges = mNetworkCapabilities.getUids(); @@ -1543,7 +1549,7 @@ public class Vpn { */ public void onUserRemoved(int userId) { // clean up if restricted - UserInfo user = UserManager.get(mContext).getUserInfo(userId); + UserInfo user = mUserManager.getUserInfo(userId); if (user.isRestricted() && user.restrictedProfileParentId == mUserId) { synchronized(Vpn.this) { final Set<UidRange> existingRanges = mNetworkCapabilities.getUids(); @@ -1768,7 +1774,7 @@ public class Vpn { private void prepareStatusIntent() { final long token = Binder.clearCallingIdentity(); try { - mStatusIntent = VpnConfig.getIntentForStatusPanel(mContext); + mStatusIntent = mDeps.getIntentForStatusPanel(mContext); } finally { Binder.restoreCallingIdentity(token); } @@ -1968,8 +1974,7 @@ public class Vpn { private void enforceNotRestrictedUser() { Binder.withCleanCallingIdentity(() -> { - final UserManager mgr = UserManager.get(mContext); - final UserInfo user = mgr.getUserInfo(mUserId); + final UserInfo user = mUserManager.getUserInfo(mUserId); if (user.isRestricted()) { throw new SecurityException("Restricted users cannot configure VPNs"); @@ -2004,9 +2009,8 @@ public class Vpn { */ public void startLegacyVpnPrivileged(VpnProfile profile, KeyStore keyStore, @Nullable Network underlying, @NonNull LinkProperties egress) { - UserManager mgr = UserManager.get(mContext); - UserInfo user = mgr.getUserInfo(mUserId); - if (user.isRestricted() || mgr.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN, + UserInfo user = mUserManager.getUserInfo(mUserId); + if (user.isRestricted() || mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN, new UserHandle(mUserId))) { throw new SecurityException("Restricted users cannot establish VPNs"); } diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java index afc97c7fd803..dac94f6aa9d2 100644 --- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java +++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java @@ -27,6 +27,7 @@ import android.os.FileUtils; import android.system.ErrnoException; import android.system.Os; import android.text.FontConfig; +import android.util.ArrayMap; import android.util.Base64; import android.util.Slog; @@ -39,7 +40,6 @@ import java.io.FileOutputStream; import java.io.IOException; import java.security.SecureRandom; import java.time.Instant; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -116,7 +116,7 @@ final class UpdatableFontDir { * FontFileInfo}. All files in this map are validated, and have higher revision numbers than * corresponding font files in {@link #mPreinstalledFontDirs}. */ - private final Map<String, FontFileInfo> mFontFileInfoMap = new HashMap<>(); + private final ArrayMap<String, FontFileInfo> mFontFileInfoMap = new ArrayMap<>(); UpdatableFontDir(File filesDir, List<File> preinstalledFontDirs, FontFileParser parser, FsverityUtil fsverityUtil) { @@ -205,7 +205,7 @@ final class UpdatableFontDir { */ public void update(List<FontUpdateRequest> requests) throws SystemFontException { // Backup the mapping for rollback. - HashMap<String, FontFileInfo> backupMap = new HashMap<>(mFontFileInfoMap); + ArrayMap<String, FontFileInfo> backupMap = new ArrayMap<>(mFontFileInfoMap); long backupLastModifiedDate = mLastModifiedDate; boolean success = false; try { @@ -464,7 +464,7 @@ final class UpdatableFontDir { } Map<String, File> getFontFileMap() { - Map<String, File> map = new HashMap<>(); + Map<String, File> map = new ArrayMap<>(); for (Map.Entry<String, FontFileInfo> entry : mFontFileInfoMap.entrySet()) { map.put(entry.getKey(), entry.getValue().getFile()); } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index e5b53501d6e3..d8e124a00104 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -110,7 +110,6 @@ import android.os.ServiceManager; import android.os.ShellCallback; import android.os.ShellCommand; import android.os.SystemClock; -import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; @@ -300,45 +299,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private static final String ACTION_SHOW_INPUT_METHOD_PICKER = "com.android.server.inputmethod.InputMethodManagerService.SHOW_INPUT_METHOD_PICKER"; - /** - * Debug flag for overriding runtime {@link SystemProperties}. - */ - @AnyThread - private static final class DebugFlag { - private static final Object LOCK = new Object(); - private final String mKey; - private final boolean mDefaultValue; - @GuardedBy("LOCK") - private boolean mValue; - - public DebugFlag(String key, boolean defaultValue) { - mKey = key; - mDefaultValue = defaultValue; - mValue = SystemProperties.getBoolean(key, defaultValue); - } - - void refresh() { - synchronized (LOCK) { - mValue = SystemProperties.getBoolean(mKey, mDefaultValue); - } - } - - boolean value() { - synchronized (LOCK) { - return mValue; - } - } - } - - /** - * Debug flags that can be overridden using "adb shell setprop <key>" - * Note: These flags are cached. To refresh, run "adb shell ime refresh_debug_properties". - */ - private static final class DebugFlags { - static final DebugFlag FLAG_OPTIMIZE_START_INPUT = - new DebugFlag("debug.optimize_startinput", false); - } - @UserIdInt private int mLastSwitchUserId; @@ -3687,12 +3647,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } res = startInputUncheckedLocked(cs, inputContext, missingMethods, attribute, startInputFlags, startInputReason); - } else if (!DebugFlags.FLAG_OPTIMIZE_START_INPUT.value() - || (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0) { + } else { res = startInputUncheckedLocked(cs, inputContext, missingMethods, attribute, startInputFlags, startInputReason); - } else { - res = InputBindResult.NO_EDITOR; } } else { res = InputBindResult.NULL_EDITOR_INFO; @@ -5467,10 +5424,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread @ShellCommandResult private int onCommandWithSystemIdentity(@Nullable String cmd) { - if ("refresh_debug_properties".equals(cmd)) { - return refreshDebugProperties(); - } - if ("get-last-switch-user-id".equals(cmd)) { return mService.getLastSwitchUserId(this); } @@ -5505,13 +5458,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } @BinderThread - @ShellCommandResult - private int refreshDebugProperties() { - DebugFlags.FLAG_OPTIMIZE_START_INPUT.refresh(); - return ShellCommandResult.SUCCESS; - } - - @BinderThread @Override public void onHelp() { try (PrintWriter pw = getOutPrintWriter()) { diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java index 02a36dc74d68..f646d5d22263 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java @@ -220,7 +220,8 @@ public class InputMethodMenuController { */ @VisibleForTesting public Context getSettingsContext(int displayId) { - if (mSettingsContext == null) { + // TODO(b/178462039): Cover the case when IME is moved to another ImeContainer. + if (mSettingsContext == null || mSettingsContext.getDisplayId() != displayId) { final Context systemUiContext = ActivityThread.currentActivityThread() .createSystemUiContext(displayId); final Context windowContext = systemUiContext.createWindowContext( @@ -229,11 +230,6 @@ public class InputMethodMenuController { windowContext, com.android.internal.R.style.Theme_DeviceDefault_Settings); mSwitchingDialogToken = mSettingsContext.getWindowContextToken(); } - // TODO(b/159767464): register the listener to another display again if window token is not - // yet created. - if (mSettingsContext.getDisplayId() != displayId) { - mWindowManagerInternal.moveWindowTokenToDisplay(mSwitchingDialogToken, displayId); - } return mSettingsContext; } diff --git a/services/core/java/com/android/server/utils/eventlog/LocalEventLog.java b/services/core/java/com/android/server/location/eventlog/LocalEventLog.java index fe51d7408153..b5746bbf310a 100644 --- a/services/core/java/com/android/server/utils/eventlog/LocalEventLog.java +++ b/services/core/java/com/android/server/location/eventlog/LocalEventLog.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.utils.eventlog; +package com.android.server.location.eventlog; import android.os.SystemClock; import android.util.TimeUtils; diff --git a/services/core/java/com/android/server/location/injector/LocationEventLog.java b/services/core/java/com/android/server/location/injector/LocationEventLog.java index b8b54b3a42e7..8d73518bced1 100644 --- a/services/core/java/com/android/server/location/injector/LocationEventLog.java +++ b/services/core/java/com/android/server/location/injector/LocationEventLog.java @@ -31,7 +31,7 @@ import android.location.util.identity.CallerIdentity; import android.os.Build; import android.os.PowerManager.LocationPowerSaveMode; -import com.android.server.utils.eventlog.LocalEventLog; +import com.android.server.location.eventlog.LocalEventLog; /** In memory event log for location events. */ public class LocationEventLog extends LocalEventLog { diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java index f92f3dcd77ef..39ed7e8b1e1a 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java @@ -16,8 +16,6 @@ package com.android.server.net; -import static com.android.server.net.NetworkPolicyManagerService.isUidNetworkingBlockedInternal; - import android.annotation.NonNull; import android.net.Network; import android.net.NetworkTemplate; @@ -39,28 +37,6 @@ public abstract class NetworkPolicyManagerInternal { public abstract void resetUserState(int userId); /** - * Figure out if networking is blocked for a given set of conditions. - * - * This is used by ConnectivityService via passing stale copies of conditions, so it must not - * take any locks. - * - * @param uid The target uid. - * @param uidRules The uid rules which are obtained from NetworkPolicyManagerService. - * @param isNetworkMetered True if the network is metered. - * @param isBackgroundRestricted True if data saver is enabled. - * - * @return true if networking is blocked for the UID under the specified conditions. - */ - public static boolean isUidNetworkingBlocked(int uid, int uidRules, boolean isNetworkMetered, - boolean isBackgroundRestricted) { - // Log of invoking internal function is disabled because it will be called very - // frequently. And metrics are unlikely needed on this method because the callers are - // external and this method doesn't take any locks or perform expensive operations. - return isUidNetworkingBlockedInternal(uid, uidRules, isNetworkMetered, - isBackgroundRestricted, null); - } - - /** * Informs that an appId has been added or removed from the temp-powersave-allowlist so that * that network rules for that appId can be updated. * diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index ca21640feb8f..b99a55271943 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -5402,6 +5402,17 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } @Override + public boolean checkUidNetworkingBlocked(int uid, int uidRules, + boolean isNetworkMetered, boolean isBackgroundRestricted) { + mContext.enforceCallingOrSelfPermission(OBSERVE_NETWORK_POLICY, TAG); + // Log of invoking this function is disabled because it will be called very frequently. And + // metrics are unlikely needed on this method because the callers are external and this + // method doesn't take any locks or perform expensive operations. + return isUidNetworkingBlockedInternal(uid, uidRules, isNetworkMetered, + isBackgroundRestricted, null); + } + + @Override public boolean isUidRestrictedOnMeteredNetworks(int uid) { mContext.enforceCallingOrSelfPermission(OBSERVE_NETWORK_POLICY, TAG); final int uidRules; @@ -5410,9 +5421,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { uidRules = mUidRules.get(uid, RULE_ALLOW_ALL); isBackgroundRestricted = mRestrictBackground; } - //TODO(b/177490332): The logic here might not be correct because it doesn't consider - // RULE_REJECT_METERED condition. And it could be replaced by - // isUidNetworkingBlockedInternal(). + // TODO(b/177490332): The logic here might not be correct because it doesn't consider + // RULE_REJECT_METERED condition. And it could be replaced by + // isUidNetworkingBlockedInternal(). return isBackgroundRestricted && !hasRule(uidRules, RULE_ALLOW_METERED) && !hasRule(uidRules, RULE_TEMPORARY_ALLOW_METERED); diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 461d51912983..619fc4e8e3f2 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -23,8 +23,8 @@ import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL; import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE; import static android.app.NotificationManager.IMPORTANCE_NONE; import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; +import static android.util.StatsLog.ANNOTATION_ID_IS_UID; -import static com.android.internal.util.FrameworkStatsLog.ANNOTATION_ID_IS_UID; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES; diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index de7737202927..18c689f1973d 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -21,8 +21,8 @@ import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ENABLED; import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_REMOVED; import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY; import static android.service.notification.DNDModeProto.ROOT_CONFIG; +import static android.util.StatsLog.ANNOTATION_ID_IS_UID; -import static com.android.internal.util.FrameworkStatsLog.ANNOTATION_ID_IS_UID; import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE; import android.app.AppOpsManager; diff --git a/services/core/java/com/android/server/pm/ApkChecksums.java b/services/core/java/com/android/server/pm/ApkChecksums.java index 5373f996d7f8..66ea55401cef 100644 --- a/services/core/java/com/android/server/pm/ApkChecksums.java +++ b/services/core/java/com/android/server/pm/ApkChecksums.java @@ -23,7 +23,6 @@ import static android.content.pm.Checksum.TYPE_WHOLE_MERKLE_ROOT_4K_SHA256; import static android.content.pm.Checksum.TYPE_WHOLE_SHA1; import static android.content.pm.Checksum.TYPE_WHOLE_SHA256; import static android.content.pm.Checksum.TYPE_WHOLE_SHA512; -import static android.content.pm.PackageManager.EXTRA_CHECKSUMS; import static android.content.pm.parsing.ApkLiteParseUtils.APK_FILE_EXTENSION; import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256; import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512; @@ -32,15 +31,15 @@ import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKE import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; -import android.content.Intent; -import android.content.IntentSender; import android.content.pm.ApkChecksum; import android.content.pm.Checksum; +import android.content.pm.IOnChecksumsReadyListener; import android.content.pm.PackageManagerInternal; import android.content.pm.PackageParser; import android.content.pm.Signature; import android.content.pm.parsing.ApkLiteParseUtils; import android.os.Handler; +import android.os.RemoteException; import android.os.SystemClock; import android.os.incremental.IncrementalManager; import android.os.incremental.IncrementalStorage; @@ -294,21 +293,21 @@ public class ApkChecksums { /** * Fetch or calculate checksums for the collection of files. * - * @param filesToChecksum split name, null for base and File to fetch checksums for - * @param optional mask to fetch readily available checksums - * @param required mask to forcefully calculate if not available - * @param installerPackageName package name of the installer of the packages - * @param trustedInstallers array of certificate to trust, two specific cases: - * null - trust anybody, - * [] - trust nobody. - * @param statusReceiver to receive the resulting checksums + * @param filesToChecksum split name, null for base and File to fetch checksums for + * @param optional mask to fetch readily available checksums + * @param required mask to forcefully calculate if not available + * @param installerPackageName package name of the installer of the packages + * @param trustedInstallers array of certificate to trust, two specific cases: + * null - trust anybody, + * [] - trust nobody. + * @param onChecksumsReadyListener to receive the resulting checksums */ public static void getChecksums(List<Pair<String, File>> filesToChecksum, @Checksum.Type int optional, @Checksum.Type int required, @Nullable String installerPackageName, @Nullable Certificate[] trustedInstallers, - @NonNull IntentSender statusReceiver, + @NonNull IOnChecksumsReadyListener onChecksumsReadyListener, @NonNull Injector injector) { List<Map<Integer, ApkChecksum>> result = new ArrayList<>(filesToChecksum.size()); for (int i = 0, size = filesToChecksum.size(); i < size; ++i) { @@ -326,14 +325,14 @@ public class ApkChecksums { } long startTime = SystemClock.uptimeMillis(); - processRequiredChecksums(filesToChecksum, result, required, statusReceiver, injector, - startTime); + processRequiredChecksums(filesToChecksum, result, required, onChecksumsReadyListener, + injector, startTime); } private static void processRequiredChecksums(List<Pair<String, File>> filesToChecksum, List<Map<Integer, ApkChecksum>> result, @Checksum.Type int required, - @NonNull IntentSender statusReceiver, + @NonNull IOnChecksumsReadyListener onChecksumsReadyListener, @NonNull Injector injector, long startTime) { final boolean timeout = @@ -350,7 +349,7 @@ public class ApkChecksums { // Not ready, come back later. injector.getHandler().postDelayed(() -> { processRequiredChecksums(filesToChecksum, result, required, - statusReceiver, injector, startTime); + onChecksumsReadyListener, injector, startTime); }, PROCESS_REQUIRED_CHECKSUMS_DELAY_MILLIS); return; } @@ -363,13 +362,9 @@ public class ApkChecksums { } } - final Intent intent = new Intent(); - intent.putExtra(EXTRA_CHECKSUMS, - allChecksums.toArray(new ApkChecksum[allChecksums.size()])); - try { - statusReceiver.sendIntent(injector.getContext(), 1, intent, null, null); - } catch (IntentSender.SendIntentException e) { + onChecksumsReadyListener.onChecksumsReady(allChecksums); + } catch (RemoteException e) { Slog.w(TAG, e); } } diff --git a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFilter.java b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFilter.java new file mode 100644 index 000000000000..a32e107d9e73 --- /dev/null +++ b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFilter.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import static java.util.Objects.requireNonNull; + +import android.annotation.IntDef; +import android.content.IntentFilter; + +import com.android.internal.annotations.Immutable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Representation of an immutable default cross-profile intent filter. + */ +@Immutable +final class DefaultCrossProfileIntentFilter { + + @IntDef({ + Direction.TO_PARENT, + Direction.TO_PROFILE + }) + @Retention(RetentionPolicy.SOURCE) + @interface Direction { + int TO_PARENT = 0; + int TO_PROFILE = 1; + } + + /** The intent filter that's used */ + public final IntentFilter filter; + + /** + * The flags related to the forwarding, e.g. + * {@link android.content.pm.PackageManager#SKIP_CURRENT_PROFILE} or + * {@link android.content.pm.PackageManager#ONLY_IF_NO_MATCH_FOUND}. + */ + public final int flags; + + /** + * The direction of forwarding, can be either {@link Direction#TO_PARENT} or + * {@link Direction#TO_PROFILE}. + */ + public final @Direction int direction; + + /** + * Whether this cross profile intent filter would allow personal data to be shared into + * the work profile. If this is {@code true}, this intent filter should be only added to + * the profile if the admin does not enable + * {@link android.os.UserManager#DISALLOW_SHARE_INTO_MANAGED_PROFILE}. + */ + public final boolean letsPersonalDataIntoProfile; + + private DefaultCrossProfileIntentFilter(IntentFilter filter, int flags, + @Direction int direction, boolean letsPersonalDataIntoProfile) { + this.filter = requireNonNull(filter); + this.flags = flags; + this.direction = direction; + this.letsPersonalDataIntoProfile = letsPersonalDataIntoProfile; + } + + static final class Builder { + private IntentFilter mFilter = new IntentFilter(); + private int mFlags; + private @Direction int mDirection; + private boolean mLetsPersonalDataIntoProfile; + + Builder(@Direction int direction, int flags, boolean letsPersonalDataIntoProfile) { + mDirection = direction; + mFlags = flags; + mLetsPersonalDataIntoProfile = letsPersonalDataIntoProfile; + } + + Builder addAction(String action) { + mFilter.addAction(action); + return this; + } + + Builder addCategory(String category) { + mFilter.addCategory(category); + return this; + } + + Builder addDataType(String type) { + try { + mFilter.addDataType(type); + } catch (IntentFilter.MalformedMimeTypeException e) { + // ignore + } + return this; + } + + Builder addDataScheme(String scheme) { + mFilter.addDataScheme(scheme); + return this; + } + + DefaultCrossProfileIntentFilter build() { + return new DefaultCrossProfileIntentFilter(mFilter, mFlags, mDirection, + mLetsPersonalDataIntoProfile); + } + } +} diff --git a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java new file mode 100644 index 000000000000..3019439a430b --- /dev/null +++ b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import static android.content.pm.PackageManager.ONLY_IF_NO_MATCH_FOUND; +import static android.content.pm.PackageManager.SKIP_CURRENT_PROFILE; +import static android.speech.RecognizerIntent.ACTION_RECOGNIZE_SPEECH; + +import android.content.Intent; +import android.hardware.usb.UsbManager; +import android.provider.AlarmClock; +import android.provider.MediaStore; + +import java.util.Arrays; +import java.util.List; + +/** + * Utility Class for {@link DefaultCrossProfileIntentFilter}. + */ +public class DefaultCrossProfileIntentFiltersUtils { + + private DefaultCrossProfileIntentFiltersUtils() { + } + + // Intents from profile to parent user + /** Emergency call intent with mime type is always resolved by primary user. */ + private static final DefaultCrossProfileIntentFilter + EMERGENCY_CALL_MIME = + new DefaultCrossProfileIntentFilter.Builder( + DefaultCrossProfileIntentFilter.Direction.TO_PARENT, + SKIP_CURRENT_PROFILE, + /* letsPersonalDataIntoProfile= */ false) + .addAction(Intent.ACTION_CALL_EMERGENCY) + .addAction(Intent.ACTION_CALL_PRIVILEGED) + .addCategory(Intent.CATEGORY_DEFAULT) + .addCategory(Intent.CATEGORY_BROWSABLE) + .addDataType("vnd.android.cursor.item/phone") + .addDataType("vnd.android.cursor.item/phone_v2") + .addDataType("vnd.android.cursor.item/person") + .addDataType("vnd.android.cursor.dir/calls") + .addDataType("vnd.android.cursor.item/calls") + .build(); + + /** Emergency call intent with data schemes is always resolved by primary user. */ + private static final DefaultCrossProfileIntentFilter + EMERGENCY_CALL_DATA = + new DefaultCrossProfileIntentFilter.Builder( + DefaultCrossProfileIntentFilter.Direction.TO_PARENT, + SKIP_CURRENT_PROFILE, + /* letsPersonalDataIntoProfile= */ false) + .addAction(Intent.ACTION_CALL_EMERGENCY) + .addAction(Intent.ACTION_CALL_PRIVILEGED) + .addCategory(Intent.CATEGORY_DEFAULT) + .addCategory(Intent.CATEGORY_BROWSABLE) + .addDataScheme("tel") + .addDataScheme("sip") + .addDataScheme("voicemail") + .build(); + + /** Dial intent with mime type can be handled by either managed profile or its parent user. */ + private static final DefaultCrossProfileIntentFilter DIAL_MIME = + new DefaultCrossProfileIntentFilter.Builder( + DefaultCrossProfileIntentFilter.Direction.TO_PARENT, + ONLY_IF_NO_MATCH_FOUND, + /* letsPersonalDataIntoProfile= */ false) + .addAction(Intent.ACTION_DIAL) + .addAction(Intent.ACTION_VIEW) + .addCategory(Intent.CATEGORY_DEFAULT) + .addCategory(Intent.CATEGORY_BROWSABLE) + .addDataType("vnd.android.cursor.item/phone") + .addDataType("vnd.android.cursor.item/phone_v2") + .addDataType("vnd.android.cursor.item/person") + .addDataType("vnd.android.cursor.dir/calls") + .addDataType("vnd.android.cursor.item/calls") + .build(); + + /** Dial intent with data scheme can be handled by either managed profile or its parent user. */ + private static final DefaultCrossProfileIntentFilter DIAL_DATA = + new DefaultCrossProfileIntentFilter.Builder( + DefaultCrossProfileIntentFilter.Direction.TO_PARENT, + ONLY_IF_NO_MATCH_FOUND, + /* letsPersonalDataIntoProfile= */ false) + .addAction(Intent.ACTION_DIAL) + .addAction(Intent.ACTION_VIEW) + .addCategory(Intent.CATEGORY_DEFAULT) + .addCategory(Intent.CATEGORY_BROWSABLE) + .addDataScheme("tel") + .addDataScheme("sip") + .addDataScheme("voicemail") + .build(); + + /** + * Dial intent with no data scheme or type can be handled by either managed profile or its + * parent user. + */ + private static final DefaultCrossProfileIntentFilter DIAL_RAW = + new DefaultCrossProfileIntentFilter.Builder( + DefaultCrossProfileIntentFilter.Direction.TO_PARENT, + ONLY_IF_NO_MATCH_FOUND, + /* letsPersonalDataIntoProfile= */ false) + .addAction(Intent.ACTION_DIAL) + .addCategory(Intent.CATEGORY_DEFAULT) + .addCategory(Intent.CATEGORY_BROWSABLE) + .build(); + + /** Pressing the call button can be handled by either managed profile or its parent user. */ + private static final DefaultCrossProfileIntentFilter CALL_BUTTON = + new DefaultCrossProfileIntentFilter.Builder( + DefaultCrossProfileIntentFilter.Direction.TO_PARENT, + ONLY_IF_NO_MATCH_FOUND, + /* letsPersonalDataIntoProfile= */ false) + .addAction(Intent.ACTION_CALL_BUTTON) + .addCategory(Intent.CATEGORY_DEFAULT) + .build(); + + /** SMS and MMS are exclusively handled by the primary user. */ + private static final DefaultCrossProfileIntentFilter SMS_MMS = + new DefaultCrossProfileIntentFilter.Builder( + DefaultCrossProfileIntentFilter.Direction.TO_PARENT, + SKIP_CURRENT_PROFILE, + /* letsPersonalDataIntoProfile= */ false) + .addAction(Intent.ACTION_VIEW) + .addAction(Intent.ACTION_SENDTO) + .addCategory(Intent.CATEGORY_DEFAULT) + .addCategory(Intent.CATEGORY_BROWSABLE) + .addDataScheme("sms") + .addDataScheme("smsto") + .addDataScheme("mms") + .addDataScheme("mmsto") + .build(); + + /** Mobile network settings is always shown in the primary user. */ + private static final DefaultCrossProfileIntentFilter + MOBILE_NETWORK_SETTINGS = + new DefaultCrossProfileIntentFilter.Builder( + DefaultCrossProfileIntentFilter.Direction.TO_PARENT, + SKIP_CURRENT_PROFILE, + /* letsPersonalDataIntoProfile= */ false) + .addAction(android.provider.Settings.ACTION_DATA_ROAMING_SETTINGS) + .addAction(android.provider.Settings.ACTION_NETWORK_OPERATOR_SETTINGS) + .addCategory(Intent.CATEGORY_DEFAULT) + .build(); + + /** HOME intent is always resolved by the primary user. */ + static final DefaultCrossProfileIntentFilter HOME = + new DefaultCrossProfileIntentFilter.Builder( + DefaultCrossProfileIntentFilter.Direction.TO_PARENT, + SKIP_CURRENT_PROFILE, + /* letsPersonalDataIntoProfile= */ false) + .addAction(Intent.ACTION_MAIN) + .addCategory(Intent.CATEGORY_DEFAULT) + .addCategory(Intent.CATEGORY_HOME) + .build(); + + /** Get content can be forwarded to parent user. */ + private static final DefaultCrossProfileIntentFilter GET_CONTENT = + new DefaultCrossProfileIntentFilter.Builder( + DefaultCrossProfileIntentFilter.Direction.TO_PARENT, + /* flags= */0, + /* letsPersonalDataIntoProfile= */ true) + .addAction(Intent.ACTION_GET_CONTENT) + .addCategory(Intent.CATEGORY_DEFAULT) + .addCategory(Intent.CATEGORY_OPENABLE) + .addDataType("*/*") + .build(); + + /** Open document intent can be forwarded to parent user. */ + private static final DefaultCrossProfileIntentFilter OPEN_DOCUMENT = + new DefaultCrossProfileIntentFilter.Builder( + DefaultCrossProfileIntentFilter.Direction.TO_PARENT, + /* flags= */0, + /* letsPersonalDataIntoProfile= */ true) + .addAction(Intent.ACTION_OPEN_DOCUMENT) + .addCategory(Intent.CATEGORY_DEFAULT) + .addCategory(Intent.CATEGORY_OPENABLE) + .addDataType("*/*") + .build(); + + /** Pick for any data type can be forwarded to parent user. */ + private static final DefaultCrossProfileIntentFilter ACTION_PICK_DATA = + new DefaultCrossProfileIntentFilter.Builder( + DefaultCrossProfileIntentFilter.Direction.TO_PARENT, + /* flags= */0, + /* letsPersonalDataIntoProfile= */ true) + .addAction(Intent.ACTION_PICK) + .addCategory(Intent.CATEGORY_DEFAULT) + .addDataType("*/*") + .build(); + + /** Pick without data type can be forwarded to parent user. */ + private static final DefaultCrossProfileIntentFilter ACTION_PICK_RAW = + new DefaultCrossProfileIntentFilter.Builder( + DefaultCrossProfileIntentFilter.Direction.TO_PARENT, + /* flags= */0, + /* letsPersonalDataIntoProfile= */ true) + .addAction(Intent.ACTION_PICK) + .addCategory(Intent.CATEGORY_DEFAULT) + .build(); + + /** Speech recognition can be performed by primary user. */ + private static final DefaultCrossProfileIntentFilter RECOGNIZE_SPEECH = + new DefaultCrossProfileIntentFilter.Builder( + DefaultCrossProfileIntentFilter.Direction.TO_PARENT, + /* flags= */0, + /* letsPersonalDataIntoProfile= */ false) + .addAction(ACTION_RECOGNIZE_SPEECH) + .addCategory(Intent.CATEGORY_DEFAULT) + .build(); + + /** Media capture can be performed by primary user. */ + private static final DefaultCrossProfileIntentFilter MEDIA_CAPTURE = + new DefaultCrossProfileIntentFilter.Builder( + DefaultCrossProfileIntentFilter.Direction.TO_PARENT, + /* flags= */0, + /* letsPersonalDataIntoProfile= */ true) + .addAction(MediaStore.ACTION_IMAGE_CAPTURE) + .addAction(MediaStore.ACTION_IMAGE_CAPTURE_SECURE) + .addAction(MediaStore.ACTION_VIDEO_CAPTURE) + .addAction(MediaStore.Audio.Media.RECORD_SOUND_ACTION) + .addAction(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA) + .addAction(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE) + .addAction(MediaStore.INTENT_ACTION_VIDEO_CAMERA) + .addCategory(Intent.CATEGORY_DEFAULT) + .build(); + + /** Alarm setting can be performed by primary user. */ + private static final DefaultCrossProfileIntentFilter SET_ALARM = + new DefaultCrossProfileIntentFilter.Builder( + DefaultCrossProfileIntentFilter.Direction.TO_PARENT, + /* flags= */0, + /* letsPersonalDataIntoProfile= */ false) + .addAction(AlarmClock.ACTION_SET_ALARM) + .addAction(AlarmClock.ACTION_SHOW_ALARMS) + .addAction(AlarmClock.ACTION_SET_TIMER) + .addCategory(Intent.CATEGORY_DEFAULT) + .build(); + + // Intents from parent to profile user + + /** ACTION_SEND can be forwarded to the managed profile on user's choice. */ + private static final DefaultCrossProfileIntentFilter ACTION_SEND = + new DefaultCrossProfileIntentFilter.Builder( + DefaultCrossProfileIntentFilter.Direction.TO_PROFILE, + /* flags= */0, + /* letsPersonalDataIntoProfile= */ true) + .addAction(Intent.ACTION_SEND) + .addAction(Intent.ACTION_SEND_MULTIPLE) + .addCategory(Intent.CATEGORY_DEFAULT) + .addDataType("*/*") + .build(); + + /** USB devices attached can get forwarded to the profile. */ + private static final DefaultCrossProfileIntentFilter + USB_DEVICE_ATTACHED = + new DefaultCrossProfileIntentFilter.Builder( + DefaultCrossProfileIntentFilter.Direction.TO_PROFILE, + /* flags= */0, + /* letsPersonalDataIntoProfile= */ false) + .addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED) + .addAction(UsbManager.ACTION_USB_ACCESSORY_ATTACHED) + .addCategory(Intent.CATEGORY_DEFAULT) + .build(); + + public static List<DefaultCrossProfileIntentFilter> getDefaultManagedProfileFilters() { + return Arrays.asList( + EMERGENCY_CALL_MIME, + EMERGENCY_CALL_DATA, + DIAL_MIME, + DIAL_DATA, + DIAL_RAW, + CALL_BUTTON, + SMS_MMS, + SET_ALARM, + MEDIA_CAPTURE, + RECOGNIZE_SPEECH, + ACTION_PICK_RAW, + ACTION_PICK_DATA, + OPEN_DOCUMENT, + GET_CONTENT, + USB_DEVICE_ATTACHED, + ACTION_SEND, + HOME, + MOBILE_NETWORK_SETTINGS); + } +} diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index f444ed6df2a3..498c314cbc68 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -177,6 +177,7 @@ import android.content.pm.DataLoaderType; import android.content.pm.FallbackCategoryProvider; import android.content.pm.FeatureInfo; import android.content.pm.IDexModuleRegisterCallback; +import android.content.pm.IOnChecksumsReadyListener; import android.content.pm.IPackageChangeObserver; import android.content.pm.IPackageDataObserver; import android.content.pm.IPackageDeleteObserver; @@ -5523,19 +5524,19 @@ public class PackageManagerService extends IPackageManager.Stub public void requestChecksums(@NonNull String packageName, boolean includeSplits, @Checksum.Type int optional, @Checksum.Type int required, @Nullable List trustedInstallers, - @NonNull IntentSender statusReceiver, int userId) { + @NonNull IOnChecksumsReadyListener onChecksumsReadyListener, int userId) { requestChecksumsInternal(packageName, includeSplits, optional, required, trustedInstallers, - statusReceiver, userId, mInjector.getBackgroundExecutor(), + onChecksumsReadyListener, userId, mInjector.getBackgroundExecutor(), mInjector.getBackgroundHandler()); } private void requestChecksumsInternal(@NonNull String packageName, boolean includeSplits, - @Checksum.Type int optional, - @Checksum.Type int required, @Nullable List trustedInstallers, - @NonNull IntentSender statusReceiver, int userId, @NonNull Executor executor, - @NonNull Handler handler) { + @Checksum.Type int optional, @Checksum.Type int required, + @Nullable List trustedInstallers, + @NonNull IOnChecksumsReadyListener onChecksumsReadyListener, int userId, + @NonNull Executor executor, @NonNull Handler handler) { Objects.requireNonNull(packageName); - Objects.requireNonNull(statusReceiver); + Objects.requireNonNull(onChecksumsReadyListener); Objects.requireNonNull(executor); Objects.requireNonNull(handler); @@ -5571,7 +5572,7 @@ public class PackageManagerService extends IPackageManager.Stub () -> mInjector.getIncrementalManager(), () -> mPmInternal); ApkChecksums.getChecksums(filesToChecksum, optional, required, installerPackageName, - trustedCerts, statusReceiver, injector); + trustedCerts, onChecksumsReadyListener, injector); }); } @@ -27110,12 +27111,14 @@ public class PackageManagerService extends IPackageManager.Stub ps.setStatesOnCrashOrAnr(); } + @Override public void requestChecksums(@NonNull String packageName, boolean includeSplits, @Checksum.Type int optional, @Checksum.Type int required, - @Nullable List trustedInstallers, @NonNull IntentSender statusReceiver, int userId, + @Nullable List trustedInstallers, + @NonNull IOnChecksumsReadyListener onChecksumsReadyListener, int userId, @NonNull Executor executor, @NonNull Handler handler) { requestChecksumsInternal(packageName, includeSplits, optional, required, - trustedInstallers, statusReceiver, userId, executor, handler); + trustedInstallers, onChecksumsReadyListener, userId, executor, handler); } @Override diff --git a/services/core/java/com/android/server/pm/ProcessLoggingHandler.java b/services/core/java/com/android/server/pm/ProcessLoggingHandler.java index ce77c9163843..8c5084afcdf9 100644 --- a/services/core/java/com/android/server/pm/ProcessLoggingHandler.java +++ b/services/core/java/com/android/server/pm/ProcessLoggingHandler.java @@ -16,22 +16,16 @@ package com.android.server.pm; -import static android.content.pm.PackageManager.EXTRA_CHECKSUMS; - import android.app.admin.SecurityLog; import android.content.Context; -import android.content.IIntentReceiver; -import android.content.IIntentSender; -import android.content.Intent; -import android.content.IntentSender; import android.content.pm.ApkChecksum; import android.content.pm.Checksum; +import android.content.pm.IOnChecksumsReadyListener; import android.content.pm.PackageManagerInternal; import android.os.Bundle; import android.os.Handler; import android.os.HandlerExecutor; -import android.os.IBinder; -import android.os.Parcelable; +import android.os.RemoteException; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Slog; @@ -39,7 +33,6 @@ import android.util.Slog; import com.android.internal.os.BackgroundThread; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.concurrent.Executor; @@ -109,26 +102,23 @@ public final class ProcessLoggingHandler extends Handler { // Capturing local loggingInfo to still log even if hash was invalidated. try { pmi.requestChecksums(packageName, false, 0, CHECKSUM_TYPE, null, - new IntentSender((IIntentSender) new IIntentSender.Stub() { + new IOnChecksumsReadyListener.Stub() { @Override - public void send(int code, Intent intent, String resolvedType, - IBinder allowlistToken, IIntentReceiver finishedReceiver, - String requiredPermission, Bundle options) { - processChecksums(loggingInfo, intent); + public void onChecksumsReady(List<ApkChecksum> checksums) + throws RemoteException { + processChecksums(loggingInfo, checksums); } - }), context.getUserId(), mExecutor, this); + }, context.getUserId(), + mExecutor, this); } catch (Throwable t) { Slog.e(TAG, "requestChecksums() failed", t); enqueueProcessChecksum(loggingInfo, null); } } - void processChecksums(final LoggingInfo loggingInfo, Intent intent) { - Parcelable[] parcelables = intent.getParcelableArrayExtra(EXTRA_CHECKSUMS); - ApkChecksum[] checksums = Arrays.copyOf(parcelables, parcelables.length, - ApkChecksum[].class); - - for (ApkChecksum checksum : checksums) { + void processChecksums(final LoggingInfo loggingInfo, List<ApkChecksum> checksums) { + for (int i = 0, size = checksums.size(); i < size; ++i) { + ApkChecksum checksum = checksums.get(i); if (checksum.getType() == CHECKSUM_TYPE) { processChecksum(loggingInfo, checksum.getValue()); return; diff --git a/services/core/java/com/android/server/pm/RestrictionsSet.java b/services/core/java/com/android/server/pm/RestrictionsSet.java index ea61ca47deb0..e5a70c3a2365 100644 --- a/services/core/java/com/android/server/pm/RestrictionsSet.java +++ b/services/core/java/com/android/server/pm/RestrictionsSet.java @@ -26,6 +26,7 @@ import android.util.TypedXmlPullParser; import android.util.TypedXmlSerializer; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.BundleUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -78,7 +79,7 @@ public class RestrictionsSet { if (!changed) { return false; } - if (!UserRestrictionsUtils.isEmpty(restrictions)) { + if (!BundleUtils.isEmpty(restrictions)) { mUserRestrictions.put(userId, restrictions); } else { mUserRestrictions.delete(userId); diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java index c182d685c247..c17d2e4b0c62 100644 --- a/services/core/java/com/android/server/pm/UserManagerInternal.java +++ b/services/core/java/com/android/server/pm/UserManagerInternal.java @@ -298,4 +298,11 @@ public abstract class UserManagerInternal { * Gets all {@link UserInfo UserInfos}. */ public abstract @NonNull UserInfo[] getUserInfos(); + + /** + * Sets all default cross profile intent filters between {@code parentUserId} and + * {@code profileUserId}. + */ + public abstract void setDefaultCrossProfileIntentFilters( + @UserIdInt int parentUserId, @UserIdInt int profileUserId); } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index a8077774d62a..753f22da2b9d 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -80,6 +80,7 @@ import android.os.UserManager; import android.os.UserManager.EnforcingUser; import android.os.UserManager.QuietModeFlag; import android.os.storage.StorageManager; +import android.provider.Settings; import android.security.GateKeeper; import android.service.gatekeeper.IGateKeeperService; import android.stats.devicepolicy.DevicePolicyEnums; @@ -108,6 +109,7 @@ import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; import com.android.internal.widget.LockPatternUtils; +import com.android.server.BundleUtils; import com.android.server.LocalServices; import com.android.server.LockGuard; import com.android.server.SystemService; @@ -1960,11 +1962,11 @@ public class UserManagerService extends IUserManager.Stub { final Bundle global = mDevicePolicyGlobalUserRestrictions.mergeAll(); final RestrictionsSet local = getDevicePolicyLocalRestrictionsForTargetUserLR(userId); - if (UserRestrictionsUtils.isEmpty(global) && local.isEmpty()) { + if (BundleUtils.isEmpty(global) && local.isEmpty()) { // Common case first. return baseRestrictions; } - final Bundle effective = UserRestrictionsUtils.clone(baseRestrictions); + final Bundle effective = BundleUtils.clone(baseRestrictions); UserRestrictionsUtils.merge(effective, global); UserRestrictionsUtils.merge(effective, local.mergeAll()); @@ -2105,7 +2107,7 @@ public class UserManagerService extends IUserManager.Stub { @Override public Bundle getUserRestrictions(@UserIdInt int userId) { checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "getUserRestrictions"); - return UserRestrictionsUtils.clone(getEffectiveUserRestrictions(userId)); + return BundleUtils.clone(getEffectiveUserRestrictions(userId)); } @Override @@ -2129,7 +2131,7 @@ public class UserManagerService extends IUserManager.Stub { synchronized (mRestrictionsLock) { // Note we can't modify Bundles stored in mBaseUserRestrictions directly, so create // a copy. - final Bundle newRestrictions = UserRestrictionsUtils.clone( + final Bundle newRestrictions = BundleUtils.clone( mBaseUserRestrictions.getRestrictions(userId)); newRestrictions.putBoolean(key, value); @@ -2727,7 +2729,7 @@ public class UserManagerService extends IUserManager.Stub { if (userVersion < 7) { // Previously only one user could enforce global restrictions, now it is per-user. synchronized (mRestrictionsLock) { - if (!UserRestrictionsUtils.isEmpty(oldGlobalUserRestrictions) + if (!BundleUtils.isEmpty(oldGlobalUserRestrictions) && mDeviceOwnerUserId != UserHandle.USER_NULL) { mDevicePolicyGlobalUserRestrictions.updateRestrictions( mDeviceOwnerUserId, oldGlobalUserRestrictions); @@ -3562,6 +3564,9 @@ public class UserManagerService extends IUserManager.Stub { t.traceBegin("PM.onNewUserCreated-" + userId); mPm.onNewUserCreated(userId, /* convertedFromPreCreated= */ false); t.traceEnd(); + applyDefaultUserSettings(userTypeDetails, userId); + setDefaultCrossProfileIntentFilters(userId, userTypeDetails, restrictions, parentId); + if (preCreate) { // Must start user (which will be stopped right away, through // UserController.finishUserUnlockedCompleted) so services can properly @@ -3597,6 +3602,78 @@ public class UserManagerService extends IUserManager.Stub { return userInfo; } + private void applyDefaultUserSettings(UserTypeDetails userTypeDetails, @UserIdInt int userId) { + final Bundle systemSettings = userTypeDetails.getDefaultSystemSettings(); + final Bundle secureSettings = userTypeDetails.getDefaultSecureSettings(); + if (systemSettings.isEmpty() && secureSettings.isEmpty()) { + return; + } + + final int systemSettingsSize = systemSettings.size(); + final String[] systemSettingsArray = systemSettings.keySet().toArray( + new String[systemSettingsSize]); + for (int i = 0; i < systemSettingsSize; i++) { + final String setting = systemSettingsArray[i]; + if (!Settings.System.putStringForUser( + mContext.getContentResolver(), setting, systemSettings.getString(setting), + userId)) { + Slog.e(LOG_TAG, "Failed to insert default system setting: " + setting); + } + } + + final int secureSettingsSize = secureSettings.size(); + final String[] secureSettingsArray = secureSettings.keySet().toArray( + new String[secureSettingsSize]); + for (int i = 0; i < secureSettingsSize; i++) { + final String setting = secureSettingsArray[i]; + if (!Settings.Secure.putStringForUser( + mContext.getContentResolver(), setting, secureSettings.getString(setting), + userId)) { + Slog.e(LOG_TAG, "Failed to insert default secure setting: " + setting); + } + } + } + + /** + * Sets all default cross profile intent filters between {@code parentUserId} and + * {@code profileUserId}, does nothing if {@code userType} is not a profile. + */ + private void setDefaultCrossProfileIntentFilters( + @UserIdInt int profileUserId, UserTypeDetails profileDetails, + Bundle profileRestrictions, @UserIdInt int parentUserId) { + if (profileDetails == null || !profileDetails.isProfile()) { + return; + } + final List<DefaultCrossProfileIntentFilter> filters = + profileDetails.getDefaultCrossProfileIntentFilters(); + if (filters.isEmpty()) { + return; + } + + // Skip filters that allow data to be shared into the profile, if admin has disabled it. + final boolean disallowSharingIntoProfile = + profileRestrictions.getBoolean( + UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, + /* defaultValue = */ false); + final int size = profileDetails.getDefaultCrossProfileIntentFilters().size(); + for (int i = 0; i < size; i++) { + final DefaultCrossProfileIntentFilter filter = + profileDetails.getDefaultCrossProfileIntentFilters().get(i); + if (disallowSharingIntoProfile && filter.letsPersonalDataIntoProfile) { + continue; + } + if (filter.direction == DefaultCrossProfileIntentFilter.Direction.TO_PARENT) { + mPm.addCrossProfileIntentFilter( + filter.filter, mContext.getOpPackageName(), profileUserId, parentUserId, + filter.flags); + } else { + mPm.addCrossProfileIntentFilter( + filter.filter, mContext.getOpPackageName(), parentUserId, profileUserId, + filter.flags); + } + } + } + /** * Finds and converts a previously pre-created user into a regular user, if possible. * @@ -5462,6 +5539,15 @@ public class UserManagerService extends IUserManager.Stub { return allInfos; } } + + @Override + public void setDefaultCrossProfileIntentFilters( + @UserIdInt int parentUserId, @UserIdInt int profileUserId) { + final UserTypeDetails userTypeDetails = getUserTypeDetailsNoChecks(profileUserId); + final Bundle restrictions = getEffectiveUserRestrictions(profileUserId); + UserManagerService.this.setDefaultCrossProfileIntentFilters( + profileUserId, userTypeDetails, restrictions, parentUserId); + } } /** @@ -5726,8 +5812,8 @@ public class UserManagerService extends IUserManager.Stub { // merge existing base restrictions with the new type's default restrictions synchronized (mRestrictionsLock) { - if (!UserRestrictionsUtils.isEmpty(newUserType.getDefaultRestrictions())) { - final Bundle newRestrictions = UserRestrictionsUtils.clone( + if (!BundleUtils.isEmpty(newUserType.getDefaultRestrictions())) { + final Bundle newRestrictions = BundleUtils.clone( mBaseUserRestrictions.getRestrictions(userInfo.id)); UserRestrictionsUtils.merge(newRestrictions, newUserType.getDefaultRestrictions()); diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java index 51dff4063850..ff90043bec94 100644 --- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java +++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java @@ -44,6 +44,7 @@ import android.util.TypedXmlSerializer; import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; +import com.android.server.BundleUtils; import com.android.server.LocalServices; import com.google.android.collect.Sets; @@ -384,10 +385,6 @@ public class UserRestrictionsUtils { return in != null ? in : new Bundle(); } - public static boolean isEmpty(@Nullable Bundle in) { - return (in == null) || (in.size() == 0); - } - /** * Returns {@code true} if given bundle is not null and contains {@code true} for a given * restriction. @@ -396,17 +393,6 @@ public class UserRestrictionsUtils { return in != null && in.getBoolean(restriction); } - /** - * Creates a copy of the {@code in} Bundle. If {@code in} is null, it'll return an empty - * bundle. - * - * <p>The resulting {@link Bundle} is always writable. (i.e. it won't return - * {@link Bundle#EMPTY}) - */ - public static @NonNull Bundle clone(@Nullable Bundle in) { - return (in != null) ? new Bundle(in) : new Bundle(); - } - public static void merge(@NonNull Bundle dest, @Nullable Bundle in) { Objects.requireNonNull(dest); Preconditions.checkArgument(dest != in); @@ -483,10 +469,10 @@ public class UserRestrictionsUtils { if (a == b) { return true; } - if (isEmpty(a)) { - return isEmpty(b); + if (BundleUtils.isEmpty(a)) { + return BundleUtils.isEmpty(b); } - if (isEmpty(b)) { + if (BundleUtils.isEmpty(b)) { return false; } for (String key : a.keySet()) { diff --git a/services/core/java/com/android/server/pm/UserTypeDetails.java b/services/core/java/com/android/server/pm/UserTypeDetails.java index 5fa46b9e4635..17ce386e3770 100644 --- a/services/core/java/com/android/server/pm/UserTypeDetails.java +++ b/services/core/java/com/android/server/pm/UserTypeDetails.java @@ -28,8 +28,12 @@ import android.os.Bundle; import android.os.UserManager; import com.android.internal.util.Preconditions; +import com.android.server.BundleUtils; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; /** * Contains the details about a multiuser "user type", such as a @@ -82,6 +86,24 @@ public final class UserTypeDetails { */ private final @Nullable Bundle mDefaultRestrictions; + /** + * List of {@link android.provider.Settings.System} to apply by default to newly created users + * of this type. + */ + private final @Nullable Bundle mDefaultSystemSettings; + + /** + * List of {@link android.provider.Settings.Secure} to apply by default to newly created users + * of this type. + */ + private final @Nullable Bundle mDefaultSecureSettings; + + /** + * List of {@link DefaultCrossProfileIntentFilter} to allow by default for newly created + * profiles. + */ + private final @Nullable List<DefaultCrossProfileIntentFilter> mDefaultCrossProfileIntentFilters; + // Fields for profiles only, controlling the nature of their badges. // All badge information should be set if {@link #hasBadge()} is true. @@ -133,7 +155,10 @@ public final class UserTypeDetails { int iconBadge, int badgePlain, int badgeNoBackground, @Nullable int[] badgeLabels, @Nullable int[] badgeColors, @Nullable int[] darkThemeBadgeColors, - @Nullable Bundle defaultRestrictions) { + @Nullable Bundle defaultRestrictions, + @Nullable Bundle defaultSystemSettings, + @Nullable Bundle defaultSecureSettings, + @Nullable List<DefaultCrossProfileIntentFilter> defaultCrossProfileIntentFilters) { this.mName = name; this.mEnabled = enabled; this.mMaxAllowed = maxAllowed; @@ -141,6 +166,9 @@ public final class UserTypeDetails { this.mBaseType = baseType; this.mDefaultUserInfoPropertyFlags = defaultUserInfoPropertyFlags; this.mDefaultRestrictions = defaultRestrictions; + this.mDefaultSystemSettings = defaultSystemSettings; + this.mDefaultSecureSettings = defaultSecureSettings; + this.mDefaultCrossProfileIntentFilters = defaultCrossProfileIntentFilters; this.mIconBadge = iconBadge; this.mBadgePlain = badgePlain; @@ -263,9 +291,9 @@ public final class UserTypeDetails { return (mBaseType & UserInfo.FLAG_SYSTEM) != 0; } - /** Returns a Bundle representing the default user restrictions. */ + /** Returns a {@link Bundle} representing the default user restrictions. */ @NonNull Bundle getDefaultRestrictions() { - return UserRestrictionsUtils.clone(mDefaultRestrictions); + return BundleUtils.clone(mDefaultRestrictions); } /** Adds the default user restrictions to the given bundle of restrictions. */ @@ -273,6 +301,24 @@ public final class UserTypeDetails { UserRestrictionsUtils.merge(currentRestrictions, mDefaultRestrictions); } + /** Returns a {@link Bundle} representing the default system settings. */ + @NonNull Bundle getDefaultSystemSettings() { + return BundleUtils.clone(mDefaultSystemSettings); + } + + /** Returns a {@link Bundle} representing the default secure settings. */ + @NonNull Bundle getDefaultSecureSettings() { + return BundleUtils.clone(mDefaultSecureSettings); + } + + /** Returns a list of default cross profile intent filters. */ + @NonNull List<DefaultCrossProfileIntentFilter> getDefaultCrossProfileIntentFilters() { + return mDefaultCrossProfileIntentFilters != null + ? new ArrayList<>(mDefaultCrossProfileIntentFilters) + : Collections.emptyList(); + } + + /** Dumps details of the UserTypeDetails. Do not parse this. */ public void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("mName: "); pw.println(mName); @@ -325,6 +371,10 @@ public final class UserTypeDetails { private int mMaxAllowedPerParent = UNLIMITED_NUMBER_OF_USERS; private int mDefaultUserInfoPropertyFlags = 0; private @Nullable Bundle mDefaultRestrictions = null; + private @Nullable Bundle mDefaultSystemSettings = null; + private @Nullable Bundle mDefaultSecureSettings = null; + private @Nullable List<DefaultCrossProfileIntentFilter> mDefaultCrossProfileIntentFilters = + null; private boolean mEnabled = true; private int mLabel = Resources.ID_NULL; private @Nullable int[] mBadgeLabels = null; @@ -407,6 +457,22 @@ public final class UserTypeDetails { return this; } + public Builder setDefaultSystemSettings(@Nullable Bundle settings) { + mDefaultSystemSettings = settings; + return this; + } + + public Builder setDefaultSecureSettings(@Nullable Bundle settings) { + mDefaultSecureSettings = settings; + return this; + } + + public Builder setDefaultCrossProfileIntentFilters( + @Nullable List<DefaultCrossProfileIntentFilter> intentFilters) { + mDefaultCrossProfileIntentFilters = intentFilters; + return this; + } + @UserInfoFlag int getBaseType() { return mBaseType; } @@ -425,17 +491,28 @@ public final class UserTypeDetails { Preconditions.checkArgument(mBadgeColors != null && mBadgeColors.length != 0, "UserTypeDetails " + mName + " has badge but no badgeColors."); } + if (!isProfile()) { + Preconditions.checkArgument(mDefaultCrossProfileIntentFilters == null + || mDefaultCrossProfileIntentFilters.isEmpty(), + "UserTypeDetails %s has a non empty " + + "defaultCrossProfileIntentFilters", mName); + } return new UserTypeDetails(mName, mEnabled, mMaxAllowed, mBaseType, mDefaultUserInfoPropertyFlags, mLabel, mMaxAllowedPerParent, mIconBadge, mBadgePlain, mBadgeNoBackground, mBadgeLabels, mBadgeColors, mDarkThemeBadgeColors == null ? mBadgeColors : mDarkThemeBadgeColors, - mDefaultRestrictions); + mDefaultRestrictions, mDefaultSystemSettings, mDefaultSecureSettings, + mDefaultCrossProfileIntentFilters); } private boolean hasBadge() { return mIconBadge != Resources.ID_NULL; } + private boolean isProfile() { + return (mBaseType & UserInfo.FLAG_PROFILE) != 0; + } + // TODO(b/143784345): Refactor this when we clean up UserInfo. private boolean hasValidBaseType() { return mBaseType == UserInfo.FLAG_FULL diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java index 1ffbf608c6d7..6aac0b2f3d0f 100644 --- a/services/core/java/com/android/server/pm/UserTypeFactory.java +++ b/services/core/java/com/android/server/pm/UserTypeFactory.java @@ -133,7 +133,9 @@ public final class UserTypeFactory { com.android.internal.R.color.profile_badge_1_dark, com.android.internal.R.color.profile_badge_2_dark, com.android.internal.R.color.profile_badge_3_dark) - .setDefaultRestrictions(null); + .setDefaultRestrictions(getDefaultManagedProfileRestrictions()) + .setDefaultSecureSettings(getDefaultManagedProfileSecureSettings()) + .setDefaultCrossProfileIntentFilters(getDefaultManagedCrossProfileIntentFilter()); } /** @@ -256,12 +258,35 @@ public final class UserTypeFactory { return restrictions; } + private static Bundle getDefaultManagedProfileRestrictions() { + final Bundle restrictions = new Bundle(); + restrictions.putBoolean(UserManager.DISALLOW_WALLPAPER, true); + return restrictions; + } + + private static Bundle getDefaultManagedProfileSecureSettings() { + // Only add String values to the bundle, settings are written as Strings eventually + final Bundle settings = new Bundle(); + settings.putString( + android.provider.Settings.Secure.MANAGED_PROFILE_CONTACT_REMOTE_SEARCH, "1"); + settings.putString( + android.provider.Settings.Secure.CROSS_PROFILE_CALENDAR_ENABLED, "1"); + return settings; + } + + private static List<DefaultCrossProfileIntentFilter> + getDefaultManagedCrossProfileIntentFilter() { + return DefaultCrossProfileIntentFiltersUtils.getDefaultManagedProfileFilters(); + } + /** * Reads the given xml parser to obtain device user-type customization, and updates the given * map of {@link UserTypeDetails.Builder}s accordingly. * <p> * The xml file can specify the attributes according to the set... methods below. */ + // TODO(b/176973369): Add parsing logic to support custom settings/filters + // in config_user_types.xml @VisibleForTesting static void customizeBuilders(ArrayMap<String, UserTypeDetails.Builder> builders, XmlResourceParser parser) { diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index a407e8e1b7df..bc819617332d 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -222,6 +222,7 @@ import com.android.server.wm.DisplayPolicy; import com.android.server.wm.DisplayRotation; import com.android.server.wm.WindowManagerInternal; import com.android.server.wm.WindowManagerInternal.AppTransitionListener; +import com.android.server.wm.WindowManagerService; import java.io.File; import java.io.FileNotFoundException; @@ -1913,6 +1914,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { handleStartTransitionForKeyguardLw(keyguardGoingAway, 0 /* duration */); } }); + mKeyguardDelegate = new KeyguardServiceDelegate(mContext, new StateCallback() { @Override @@ -3136,7 +3138,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { private int handleStartTransitionForKeyguardLw(boolean keyguardGoingAway, long duration) { final int res = applyKeyguardOcclusionChange(); if (res != 0) return res; - if (keyguardGoingAway) { + if (!WindowManagerService.sEnableRemoteKeyguardAnimation && keyguardGoingAway) { if (DEBUG_KEYGUARD) Slog.d(TAG, "Starting keyguard exit animation"); startKeyguardExitAnimation(SystemClock.uptimeMillis(), duration); } diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index e9d64406432a..c77e266ee11a 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -1158,7 +1158,7 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { * @param startTime the start time of the animation in uptime milliseconds * @param fadeoutDuration the duration of the exit animation, in milliseconds */ - public void startKeyguardExitAnimation(long startTime, long fadeoutDuration); + void startKeyguardExitAnimation(long startTime, long fadeoutDuration); /** * Called when System UI has been started. diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java index c2a1c7930b89..a95628f633ad 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java @@ -29,6 +29,7 @@ import com.android.internal.policy.IKeyguardExitCallback; import com.android.internal.policy.IKeyguardService; import com.android.server.UiThread; import com.android.server.policy.WindowManagerPolicy.OnKeyguardExitResult; +import com.android.server.wm.WindowManagerService; import java.io.PrintWriter; @@ -398,7 +399,7 @@ public class KeyguardServiceDelegate { } public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) { - if (mKeyguardService != null) { + if (!WindowManagerService.sEnableRemoteKeyguardAnimation && mKeyguardService != null) { mKeyguardService.startKeyguardExitAnimation(startTime, fadeoutDuration); } } diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java index a82f239948ff..5ec527a7d6c4 100644 --- a/services/core/java/com/android/server/vcn/Vcn.java +++ b/services/core/java/com/android/server/vcn/Vcn.java @@ -29,6 +29,7 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; +import com.android.server.VcnManagementService.VcnSafemodeCallback; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import java.util.Collections; @@ -37,6 +38,7 @@ import java.util.HashSet; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; /** * Represents an single instance of a VCN. @@ -82,10 +84,19 @@ public class Vcn extends Handler { /** Triggers an immediate teardown of the entire Vcn, including GatewayConnections. */ private static final int MSG_CMD_TEARDOWN = MSG_CMD_BASE; + /** + * Causes this VCN to immediately enter Safemode. + * + * <p>Upon entering Safemode, the VCN will unregister its RequestListener, tear down all of its + * VcnGatewayConnections, and notify VcnManagementService that it is in Safemode. + */ + private static final int MSG_CMD_ENTER_SAFEMODE = MSG_CMD_BASE + 1; + @NonNull private final VcnContext mVcnContext; @NonNull private final ParcelUuid mSubscriptionGroup; @NonNull private final Dependencies mDeps; @NonNull private final VcnNetworkRequestListener mRequestListener; + @NonNull private final VcnSafemodeCallback mVcnSafemodeCallback; @NonNull private final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> mVcnGatewayConnections = @@ -94,14 +105,33 @@ public class Vcn extends Handler { @NonNull private VcnConfig mConfig; @NonNull private TelephonySubscriptionSnapshot mLastSnapshot; - private boolean mIsRunning = true; + /** + * Whether this Vcn instance is active and running. + * + * <p>The value will be {@code true} while running. It will be {@code false} if the VCN has been + * shut down or has entered safe mode. + * + * <p>This AtomicBoolean is required in order to ensure consistency and correctness across + * multiple threads. Unlike the rest of the Vcn, this is queried synchronously on Binder threads + * from VcnManagementService, and therefore cannot rely on guarantees of running on the VCN + * Looper. + */ + // TODO(b/179429339): update when exiting safemode (when a new VcnConfig is provided) + private final AtomicBoolean mIsActive = new AtomicBoolean(true); public Vcn( @NonNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config, - @NonNull TelephonySubscriptionSnapshot snapshot) { - this(vcnContext, subscriptionGroup, config, snapshot, new Dependencies()); + @NonNull TelephonySubscriptionSnapshot snapshot, + @NonNull VcnSafemodeCallback vcnSafemodeCallback) { + this( + vcnContext, + subscriptionGroup, + config, + snapshot, + vcnSafemodeCallback, + new Dependencies()); } @VisibleForTesting(visibility = Visibility.PRIVATE) @@ -110,10 +140,13 @@ public class Vcn extends Handler { @NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config, @NonNull TelephonySubscriptionSnapshot snapshot, + @NonNull VcnSafemodeCallback vcnSafemodeCallback, @NonNull Dependencies deps) { super(Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper()); mVcnContext = vcnContext; mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup"); + mVcnSafemodeCallback = + Objects.requireNonNull(vcnSafemodeCallback, "Missing vcnSafemodeCallback"); mDeps = Objects.requireNonNull(deps, "Missing deps"); mRequestListener = new VcnNetworkRequestListener(); @@ -143,6 +176,11 @@ public class Vcn extends Handler { sendMessageAtFrontOfQueue(obtainMessage(MSG_CMD_TEARDOWN)); } + /** Synchronously checks whether this Vcn is active. */ + public boolean isActive() { + return mIsActive.get(); + } + /** Get current Gateways for testing purposes */ @VisibleForTesting(visibility = Visibility.PRIVATE) public Set<VcnGatewayConnection> getVcnGatewayConnections() { @@ -160,7 +198,7 @@ public class Vcn extends Handler { @Override public void handleMessage(@NonNull Message msg) { - if (!mIsRunning) { + if (!isActive()) { return; } @@ -177,6 +215,9 @@ public class Vcn extends Handler { case MSG_CMD_TEARDOWN: handleTeardown(); break; + case MSG_CMD_ENTER_SAFEMODE: + handleEnterSafemode(); + break; default: Slog.wtf(getLogTag(), "Unknown msg.what: " + msg.what); } @@ -198,7 +239,13 @@ public class Vcn extends Handler { gatewayConnection.teardownAsynchronously(); } - mIsRunning = false; + mIsActive.set(false); + } + + private void handleEnterSafemode() { + handleTeardown(); + + mVcnSafemodeCallback.onEnteredSafemode(); } private void handleNetworkRequested( @@ -233,7 +280,8 @@ public class Vcn extends Handler { mVcnContext, mSubscriptionGroup, mLastSnapshot, - gatewayConnectionConfig); + gatewayConnectionConfig, + new VcnGatewayStatusCallbackImpl()); mVcnGatewayConnections.put(gatewayConnectionConfig, vcnGatewayConnection); } } @@ -242,7 +290,7 @@ public class Vcn extends Handler { private void handleSubscriptionsChanged(@NonNull TelephonySubscriptionSnapshot snapshot) { mLastSnapshot = snapshot; - if (mIsRunning) { + if (isActive()) { for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) { gatewayConnection.updateSubscriptionSnapshot(mLastSnapshot); } @@ -271,6 +319,20 @@ public class Vcn extends Handler { return 52; } + /** Callback used for passing status signals from a VcnGatewayConnection to its managing Vcn. */ + @VisibleForTesting(visibility = Visibility.PACKAGE) + public interface VcnGatewayStatusCallback { + /** Called by a VcnGatewayConnection to indicate that it has entered Safemode. */ + void onEnteredSafemode(); + } + + private class VcnGatewayStatusCallbackImpl implements VcnGatewayStatusCallback { + @Override + public void onEnteredSafemode() { + sendMessage(obtainMessage(MSG_CMD_ENTER_SAFEMODE)); + } + } + /** External dependencies used by Vcn, for injection in tests */ @VisibleForTesting(visibility = Visibility.PRIVATE) public static class Dependencies { @@ -279,9 +341,14 @@ public class Vcn extends Handler { VcnContext vcnContext, ParcelUuid subscriptionGroup, TelephonySubscriptionSnapshot snapshot, - VcnGatewayConnectionConfig connectionConfig) { + VcnGatewayConnectionConfig connectionConfig, + VcnGatewayStatusCallback gatewayStatusCallback) { return new VcnGatewayConnection( - vcnContext, subscriptionGroup, snapshot, connectionConfig); + vcnContext, + subscriptionGroup, + snapshot, + connectionConfig, + gatewayStatusCallback); } } } diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java index 853bb4324f90..9ecdf1b48789 100644 --- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java +++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java @@ -61,7 +61,6 @@ import android.os.ParcelUuid; import android.util.ArraySet; import android.util.Slog; -import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; import com.android.internal.util.State; @@ -69,6 +68,7 @@ import com.android.internal.util.StateMachine; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord; import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkTrackerCallback; +import com.android.server.vcn.Vcn.VcnGatewayStatusCallback; import java.io.IOException; import java.net.Inet4Address; @@ -403,15 +403,13 @@ public class VcnGatewayConnection extends StateMachine { @NonNull final RetryTimeoutState mRetryTimeoutState = new RetryTimeoutState(); - @NonNull private final Object mLock = new Object(); - - @GuardedBy("mLock") @NonNull private TelephonySubscriptionSnapshot mLastSnapshot; @NonNull private final VcnContext mVcnContext; @NonNull private final ParcelUuid mSubscriptionGroup; @NonNull private final UnderlyingNetworkTracker mUnderlyingNetworkTracker; @NonNull private final VcnGatewayConnectionConfig mConnectionConfig; + @NonNull private final VcnGatewayStatusCallback mGatewayStatusCallback; @NonNull private final Dependencies mDeps; @NonNull private final VcnUnderlyingNetworkTrackerCallback mUnderlyingNetworkTrackerCallback; @@ -487,8 +485,15 @@ public class VcnGatewayConnection extends StateMachine { @NonNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, @NonNull TelephonySubscriptionSnapshot snapshot, - @NonNull VcnGatewayConnectionConfig connectionConfig) { - this(vcnContext, subscriptionGroup, snapshot, connectionConfig, new Dependencies()); + @NonNull VcnGatewayConnectionConfig connectionConfig, + @NonNull VcnGatewayStatusCallback gatewayStatusCallback) { + this( + vcnContext, + subscriptionGroup, + snapshot, + connectionConfig, + gatewayStatusCallback, + new Dependencies()); } @VisibleForTesting(visibility = Visibility.PRIVATE) @@ -497,16 +502,17 @@ public class VcnGatewayConnection extends StateMachine { @NonNull ParcelUuid subscriptionGroup, @NonNull TelephonySubscriptionSnapshot snapshot, @NonNull VcnGatewayConnectionConfig connectionConfig, + @NonNull VcnGatewayStatusCallback gatewayStatusCallback, @NonNull Dependencies deps) { super(TAG, Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper()); mVcnContext = vcnContext; mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup"); mConnectionConfig = Objects.requireNonNull(connectionConfig, "Missing connectionConfig"); + mGatewayStatusCallback = + Objects.requireNonNull(gatewayStatusCallback, "Missing gatewayStatusCallback"); mDeps = Objects.requireNonNull(deps, "Missing deps"); - synchronized (mLock) { - mLastSnapshot = Objects.requireNonNull(snapshot, "Missing snapshot"); - } + mLastSnapshot = Objects.requireNonNull(snapshot, "Missing snapshot"); mUnderlyingNetworkTrackerCallback = new VcnUnderlyingNetworkTrackerCallback(); @@ -577,13 +583,10 @@ public class VcnGatewayConnection extends StateMachine { */ public void updateSubscriptionSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot) { Objects.requireNonNull(snapshot, "Missing snapshot"); + mVcnContext.ensureRunningOnLooperThread(); - // Vcn is the only user of this method and runs on the same Thread, but lock around - // mLastSnapshot to be technically correct. - synchronized (mLock) { - mLastSnapshot = snapshot; - mUnderlyingNetworkTracker.updateSubscriptionSnapshot(mLastSnapshot); - } + mLastSnapshot = snapshot; + mUnderlyingNetworkTracker.updateSubscriptionSnapshot(mLastSnapshot); sendMessage(EVENT_SUBSCRIPTIONS_CHANGED, TOKEN_ALL); } diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index ce951b85ffe6..771b712cf480 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -975,7 +975,8 @@ class ActivityClientController extends IActivityClientController.Stub { final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token); if (r != null && r.isState(Task.ActivityState.RESUMED, Task.ActivityState.PAUSING)) { r.mDisplayContent.mAppTransition.overridePendingAppTransition( - packageName, enterAnim, exitAnim, null, null); + packageName, enterAnim, exitAnim, null, null, + r.mOverrideTaskTransition); } } Binder.restoreCallingIdentity(origId); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 36c503703b9c..262505f8318a 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -16,7 +16,6 @@ package com.android.server.wm; -import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; import static android.app.ActivityOptions.ANIM_CLIP_REVEAL; @@ -82,7 +81,6 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.pm.ActivityInfo.isFixedOrientationLandscape; import static android.content.pm.ActivityInfo.isFixedOrientationPortrait; -import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.content.res.Configuration.EMPTY; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; @@ -203,7 +201,6 @@ import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; import static com.android.server.wm.WindowContainerChildProto.ACTIVITY; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_CONFIGURATION; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW_VERBOSE; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; @@ -273,7 +270,6 @@ import android.os.SystemClock; import android.os.Trace; import android.os.UserHandle; import android.os.storage.StorageManager; -import android.permission.PermissionManager; import android.service.dreams.DreamActivity; import android.service.dreams.DreamManagerInternal; import android.service.voice.IVoiceInteractionSession; @@ -682,6 +678,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A boolean mRequestForceTransition; boolean mEnteringAnimation; + boolean mOverrideTaskTransition; boolean mAppStopped; // A hint to override the window specified rotation animation, or -1 to use the window specified @@ -1627,6 +1624,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (rotationAnimation >= 0) { mRotationAnimationHint = rotationAnimation; } + + mOverrideTaskTransition = options.getOverrideTaskTransition(); } ColorDisplayService.ColorDisplayServiceInternal cds = LocalServices.getService( @@ -3997,7 +3996,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A pendingOptions.getCustomEnterResId(), pendingOptions.getCustomExitResId(), pendingOptions.getAnimationStartedListener(), - pendingOptions.getAnimationFinishedListener()); + pendingOptions.getAnimationFinishedListener(), + pendingOptions.getOverrideTaskTransition()); break; case ANIM_CLIP_REVEAL: displayContent.mAppTransition.overridePendingAppTransitionClipReveal( @@ -6775,20 +6775,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // layout traversals. mConfigurationSeq = Math.max(++mConfigurationSeq, 1); getResolvedOverrideConfiguration().seq = mConfigurationSeq; - - // Sandbox max bounds by setting it to the app bounds, if activity is letterboxed or in - // size compat mode. - if (providesMaxBounds()) { - if (DEBUG_CONFIGURATION) { - ProtoLog.d(WM_DEBUG_CONFIGURATION, "Sandbox max bounds for uid %s to bounds %s " - + "due to letterboxing? %s mismatch with parent bounds? %s size compat " - + "mode %s", getUid(), - resolvedConfig.windowConfiguration.getBounds(), mLetterbox != null, - !matchParentBounds(), inSizeCompatMode()); - } - resolvedConfig.windowConfiguration - .setMaxBounds(resolvedConfig.windowConfiguration.getBounds()); - } } /** @@ -6972,19 +6958,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return super.getBounds(); } - @Override - public boolean providesMaxBounds() { - // System and SystemUI should always be able to access the physical display bounds, - // so do not provide it with the overridden maximum bounds. - // TODO(b/179179513) check WindowState#mOwnerCanAddInternalSystemWindow instead - if (getUid() == SYSTEM_UID || PermissionManager.checkPermission(INTERNAL_SYSTEM_WINDOW, - getPid(), info.applicationInfo.uid) == PERMISSION_GRANTED) { - return false; - } - // Max bounds should be sandboxed when this is letterboxed or in size compat mode. - return mLetterbox != null || !matchParentBounds() || inSizeCompatMode(); - } - @VisibleForTesting @Override Rect getAnimationBounds(int appRootTaskClipMode) { diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index d846c3a6d36c..79f8229c6162 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1814,8 +1814,7 @@ class ActivityStarter { } private Task computeTargetTask() { - if (mStartActivity.resultTo == null && mInTask == null && !mAddingToTask - && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) { + if (mInTask == null && !mAddingToTask && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) { // A new task should be created instead of using existing one. return null; } else if (mSourceRecord != null) { @@ -2505,7 +2504,9 @@ class ActivityStarter { // If bring to front is requested, and no result is requested and we have not been given // an explicit task to launch in to, and we can find a task that was started with this // same component, then instead of launching bring that one to the front. - putIntoExistingTask &= mInTask == null && mStartActivity.resultTo == null; + putIntoExistingTask &= !isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK) + ? (mInTask == null && mStartActivity.resultTo == null) + : (mInTask == null); ActivityRecord intentActivity = null; if (putIntoExistingTask) { if (LAUNCH_SINGLE_INSTANCE == mLaunchMode) { diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index 90070c8f5068..5b685b4a0499 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -90,6 +90,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerInternal.AppTransitionListener; +import static com.android.server.wm.WindowManagerInternal.KeyguardExitAnimationStartListener; import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_AFTER_ANIM; import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_NONE; @@ -257,6 +258,7 @@ public class AppTransition implements Dump { private long mLastClipRevealTransitionDuration = DEFAULT_APP_TRANSITION_DURATION; private final ArrayList<AppTransitionListener> mListeners = new ArrayList<>(); + private KeyguardExitAnimationStartListener mKeyguardExitAnimationStartListener; private final ExecutorService mDefaultExecutor = Executors.newSingleThreadExecutor(); private int mLastClipRevealMaxTranslation; @@ -266,6 +268,7 @@ public class AppTransition implements Dump { private final boolean mLowRamRecentsEnabled; private final int mDefaultWindowAnimationStyleResId; + private boolean mOverrideTaskTransition; private RemoteAnimationController mRemoteAnimationController; @@ -445,7 +448,7 @@ public class AppTransition implements Dump { AnimationAdapter.STATUS_BAR_TRANSITION_DURATION); if (mRemoteAnimationController != null) { - mRemoteAnimationController.goodToGo(); + mRemoteAnimationController.goodToGo(transit); } return redoLayout; } @@ -508,6 +511,11 @@ public class AppTransition implements Dump { mListeners.remove(listener); } + void registerKeygaurdExitAnimationStartListener( + KeyguardExitAnimationStartListener listener) { + mKeyguardExitAnimationStartListener = listener; + } + public void notifyAppTransitionFinishedLocked(IBinder token) { for (int i = 0; i < mListeners.size(); i++) { mListeners.get(i).onAppTransitionFinishedLocked(token); @@ -971,7 +979,8 @@ public class AppTransition implements Dump { @Nullable Rect surfaceInsets, @Nullable Rect stableInsets, boolean isVoiceInteraction, boolean freeform, WindowContainer container) { - if (mNextAppTransitionOverrideRequested && container.canCustomizeAppTransition()) { + if (mNextAppTransitionOverrideRequested + && (container.canCustomizeAppTransition() || mOverrideTaskTransition)) { mNextAppTransitionType = NEXT_TRANSIT_TYPE_CUSTOM; } @@ -1175,7 +1184,8 @@ public class AppTransition implements Dump { } void overridePendingAppTransition(String packageName, int enterAnim, int exitAnim, - IRemoteCallback startedCallback, IRemoteCallback endedCallback) { + IRemoteCallback startedCallback, IRemoteCallback endedCallback, + boolean overrideTaskTransaction) { if (canOverridePendingAppTransition()) { clear(); mNextAppTransitionOverrideRequested = true; @@ -1185,6 +1195,7 @@ public class AppTransition implements Dump { postAnimationCallback(); mNextAppTransitionCallback = startedCallback; mAnimationFinishedCallback = endedCallback; + mOverrideTaskTransition = overrideTaskTransaction; } } diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index 582aeb36b00b..4575cf7e9060 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -102,6 +102,7 @@ public class AppTransitionController { private final DisplayContent mDisplayContent; private final WallpaperController mWallpaperControllerLocked; private RemoteAnimationDefinition mRemoteAnimationDefinition = null; + private static final int KEYGUARD_GOING_AWAY_ANIMATION_DURATION = 400; private final ArrayMap<WindowContainer, Integer> mTempTransitionReasons = new ArrayMap<>(); @@ -437,10 +438,14 @@ public class AppTransitionController { return adapter; } } - if (mRemoteAnimationDefinition == null) { - return null; + if (mRemoteAnimationDefinition != null) { + final RemoteAnimationAdapter adapter = mRemoteAnimationDefinition.getAdapter( + transit, activityTypes); + if (adapter != null) { + return adapter; + } } - return mRemoteAnimationDefinition.getAdapter(transit, activityTypes); + return null; } /** @@ -709,6 +714,13 @@ public class AppTransitionController { applyAnimations(closingWcs, closingApps, transit, false /* visible */, animLp, voiceInteraction); + for (int i = 0; i < openingApps.size(); ++i) { + openingApps.valueAtUnchecked(i).mOverrideTaskTransition = false; + } + for (int i = 0; i < closingApps.size(); ++i) { + closingApps.valueAtUnchecked(i).mOverrideTaskTransition = false; + } + final AccessibilityController accessibilityController = mDisplayContent.mWmService.mAccessibilityController; if (accessibilityController != null) { diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 02e281f512a2..61fe023ae1b9 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -25,7 +25,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECOND import static android.content.res.Configuration.UI_MODE_TYPE_CAR; import static android.content.res.Configuration.UI_MODE_TYPE_MASK; import static android.view.Display.TYPE_INTERNAL; -import static android.view.InsetsState.ITYPE_BOTTOM_GESTURES; +import static android.view.InsetsState.ITYPE_BOTTOM_MANDATORY_GESTURES; import static android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT; import static android.view.InsetsState.ITYPE_CAPTION_BAR; import static android.view.InsetsState.ITYPE_CLIMATE_BAR; @@ -35,7 +35,7 @@ import static android.view.InsetsState.ITYPE_LEFT_GESTURES; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_RIGHT_GESTURES; import static android.view.InsetsState.ITYPE_STATUS_BAR; -import static android.view.InsetsState.ITYPE_TOP_GESTURES; +import static android.view.InsetsState.ITYPE_TOP_MANDATORY_GESTURES; import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT; import static android.view.ViewRootImpl.computeWindowBounds; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; @@ -1074,7 +1074,7 @@ public class DisplayPolicy { rect.bottom = rect.top + getStatusBarHeight(displayFrames); }; mDisplayContent.setInsetProvider(ITYPE_STATUS_BAR, win, frameProvider); - mDisplayContent.setInsetProvider(ITYPE_TOP_GESTURES, win, frameProvider); + mDisplayContent.setInsetProvider(ITYPE_TOP_MANDATORY_GESTURES, win, frameProvider); mDisplayContent.setInsetProvider(ITYPE_TOP_TAPPABLE_ELEMENT, win, frameProvider); break; case TYPE_NAVIGATION_BAR: @@ -1101,7 +1101,7 @@ public class DisplayPolicy { (displayFrames, windowState, inOutFrame) -> inOutFrame.set(windowState.getFrame())); - mDisplayContent.setInsetProvider(ITYPE_BOTTOM_GESTURES, win, + mDisplayContent.setInsetProvider(ITYPE_BOTTOM_MANDATORY_GESTURES, win, (displayFrames, windowState, inOutFrame) -> { inOutFrame.top -= mBottomGestureAdditionalInset; }); diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java index 2274a4a91595..99c9e798c605 100644 --- a/services/core/java/com/android/server/wm/Letterbox.java +++ b/services/core/java/com/android/server/wm/Letterbox.java @@ -340,7 +340,7 @@ public class Letterbox { } public void applySurfaceChanges(SurfaceControl.Transaction t) { - if (mSurfaceFrameRelative.equals(mLayoutFrameRelative)) { + if (!needsApplySurfaceChanges()) { // Nothing changed. return; } diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java index a1d2072c0de7..392f27ead2bb 100644 --- a/services/core/java/com/android/server/wm/RemoteAnimationController.java +++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java @@ -36,6 +36,7 @@ import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; +import android.view.WindowManager; import com.android.internal.protolog.ProtoLogImpl; import com.android.internal.protolog.common.ProtoLog; @@ -98,7 +99,7 @@ class RemoteAnimationController implements DeathRecipient { /** * Called when the transition is ready to be started, and all leashes have been set up. */ - void goodToGo() { + void goodToGo(@WindowManager.TransitionOldType int transit) { ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "goodToGo()"); if (mPendingAnimations.isEmpty() || mCanceled) { ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, @@ -123,11 +124,15 @@ class RemoteAnimationController implements DeathRecipient { // Create the remote wallpaper animation targets (if any) final RemoteAnimationTarget[] wallpaperTargets = createWallpaperAnimations(); + + // TODO(bc-unlock): Create the remote non app animation targets (if any) + final RemoteAnimationTarget[] nonAppTargets = new RemoteAnimationTarget[0]; + mService.mAnimator.addAfterPrepareSurfacesRunnable(() -> { try { linkToDeathOfRunner(); - mRemoteAnimationAdapter.getRunner().onAnimationStart(appTargets, wallpaperTargets, - mFinishedCallback); + mRemoteAnimationAdapter.getRunner().onAnimationStart(transit, appTargets, + wallpaperTargets, nonAppTargets, mFinishedCallback); } catch (RemoteException e) { Slog.e(TAG, "Failed to start remote animation", e); onAnimationFinished(); @@ -274,6 +279,7 @@ class RemoteAnimationController implements DeathRecipient { private void setRunningRemoteAnimation(boolean running) { final int pid = mRemoteAnimationAdapter.getCallingPid(); final int uid = mRemoteAnimationAdapter.getCallingUid(); + if (pid == 0) { throw new RuntimeException("Calling pid of remote animation was null"); } diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java index 6df453684734..65671959e884 100644 --- a/services/core/java/com/android/server/wm/SafeActivityOptions.java +++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java @@ -253,6 +253,20 @@ public class SafeActivityOptions { throw new SecurityException(msg); } + // Check if the caller is allowed to override any app transition animation. + final boolean overrideTaskTransition = options.getOverrideTaskTransition(); + if (aInfo != null && overrideTaskTransition) { + final int startTasksFromRecentsPerm = ActivityTaskManagerService.checkPermission( + START_TASKS_FROM_RECENTS, callingPid, callingUid); + if (startTasksFromRecentsPerm != PERMISSION_GRANTED) { + final String msg = "Permission Denial: starting " + getIntentString(intent) + + " from " + callerApp + " (pid=" + callingPid + + ", uid=" + callingUid + ") with overrideTaskTransition=true"; + Slog.w(TAG, msg); + throw new SecurityException(msg); + } + } + // Check permission for remote animations final RemoteAnimationAdapter adapter = options.getRemoteAnimationAdapter(); if (adapter != null && supervisor.mService.checkPermission( diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 30af0eda17fc..e44a028c897f 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -77,7 +77,6 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT; import static com.android.internal.policy.DecorView.DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP; import static com.android.internal.policy.DecorView.DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE; -import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_LOCKTASK; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES; @@ -145,7 +144,6 @@ import static com.android.server.wm.TaskProto.WINDOW_CONTAINER; import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; import static com.android.server.wm.WindowContainerChildProto.TASK; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_CONFIGURATION; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ROOT_TASK; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; @@ -921,10 +919,8 @@ class Task extends WindowContainer<WindowContainer> { return; } - if (isLeafTask()) { - // This task is going away, so save the last state if necessary. - saveLaunchingStateIfNeeded(((WindowContainer) oldParent).getDisplayContent()); - } + // This task is going away, so save the last state if necessary. + saveLaunchingStateIfNeeded(((WindowContainer) oldParent).getDisplayContent()); // TODO: VI what about activity? final boolean isVoiceSession = voiceSession != null; @@ -2459,14 +2455,16 @@ class Task extends WindowContainer<WindowContainer> { /** * Saves launching state if necessary so that we can launch the activity to its latest state. - * It only saves state if this task has been shown to user and it's in fullscreen or freeform - * mode on freeform displays. */ private void saveLaunchingStateIfNeeded() { saveLaunchingStateIfNeeded(getDisplayContent()); } private void saveLaunchingStateIfNeeded(DisplayContent display) { + if (!isLeafTask()) { + return; + } + if (!getHasBeenVisible()) { // Not ever visible to user. return; @@ -2867,16 +2865,6 @@ class Task extends WindowContainer<WindowContainer> { // In FULLSCREEN mode, always start with empty bounds to indicate "fill parent". outBounds.setEmpty(); computeLetterboxBounds(outBounds, newParentConfig); - // Since the task is letterboxed due to mismatched orientation against its parent, - // sandbox max bounds to the app bounds. - if (!outBounds.isEmpty()) { - if (DEBUG_CONFIGURATION) { - ProtoLog.d(WM_DEBUG_CONFIGURATION, "Sandbox max bounds due to mismatched " - + "orientation with parent, to %s vs DisplayArea %s", outBounds, - getDisplayArea() != null ? getDisplayArea().getBounds() : "null"); - } - getResolvedOverrideConfiguration().windowConfiguration.setMaxBounds(outBounds); - } } /** Computes bounds for {@link WindowConfiguration#WINDOWING_MODE_FREEFORM}. */ diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 41854072985c..63732d8d4bdc 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -47,6 +47,7 @@ import android.content.Intent; import android.os.UserHandle; import android.util.IntArray; import android.util.Slog; +import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; import android.window.WindowContainerTransaction; @@ -905,6 +906,13 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { } } + @Override + RemoteAnimationTarget createRemoteAnimationTarget( + RemoteAnimationController.RemoteAnimationRecord record) { + final ActivityRecord activity = getTopMostActivity(); + return activity != null ? activity.createRemoteAnimationTarget(record) : null; + } + SurfaceControl getSplitScreenDividerAnchor() { return mSplitScreenDividerAnchor; } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 46aea23beaf6..98eb11f8a970 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -372,8 +372,6 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe dc.mWallpaperController.startWallpaperAnimation(anim); } } - } - if (transit == TRANSIT_KEYGUARD_GOING_AWAY) { dc.startKeyguardExitOnNonAppWindows( (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0, (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0, diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index a3a9eb773abf..eed3299cc93d 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -26,9 +26,11 @@ import android.hardware.display.DisplayManagerInternal; import android.os.IBinder; import android.view.Display; import android.view.IInputFilter; +import android.view.IRemoteAnimationFinishedCallback; import android.view.IWindow; import android.view.InputChannel; import android.view.MagnificationSpec; +import android.view.RemoteAnimationTarget; import android.view.WindowInfo; import android.view.WindowManager.DisplayImePolicy; @@ -154,6 +156,21 @@ public abstract class WindowManagerInternal { } /** + * An interface to be notified when keyguard exit animation should start. + */ + public interface KeyguardExitAnimationStartListener { + /** + * Called when keyguard exit animation should start. + * @param apps The list of apps to animate. + * @param wallpapers The list of wallpapers to animate. + * @param finishedCallback The callback to invoke when the animation is finished. + */ + void onAnimationStart(RemoteAnimationTarget[] apps, + RemoteAnimationTarget[] wallpapers, + IRemoteAnimationFinishedCallback finishedCallback); + } + + /** * An interface to be notified about hardware keyboard status. */ public interface OnHardKeyboardStatusChangeListener { @@ -372,6 +389,14 @@ public abstract class WindowManagerInternal { public abstract void registerAppTransitionListener(AppTransitionListener listener); /** + * Registers a listener to be notified to start the keyguard exit animation. + * + * @param listener The listener to register. + */ + public abstract void registerKeyguardExitAnimationStartListener( + KeyguardExitAnimationStartListener listener); + + /** * Reports that the password for the given user has changed. */ public abstract void reportPasswordChanged(int userId); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 673c6a5fb470..8438118f6ee8 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -405,6 +405,8 @@ public class WindowManagerService extends IWindowManager.Stub // trying to apply a new one. private static final boolean ALWAYS_KEEP_CURRENT = true; + static final int LOGTAG_INPUT_FOCUS = 62001; + /** * Restrict ability of activities overriding transition animation in a way such that * an activity can do it only when the transition happens within a same task. @@ -413,7 +415,6 @@ public class WindowManagerService extends IWindowManager.Stub */ private static final String DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY = "persist.wm.disable_custom_task_animation"; - static final int LOGTAG_INPUT_FOCUS = 62001; /** * @see #DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY @@ -421,6 +422,19 @@ public class WindowManagerService extends IWindowManager.Stub static boolean sDisableCustomTaskAnimationProperty = SystemProperties.getBoolean(DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY, true); + /** + * Run Keyguard animation as remote animation in System UI instead of local animation in + * the server process. + */ + private static final String ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY = + "persist.wm.enable_remote_keyguard_animation"; + + /** + * @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY + */ + public static boolean sEnableRemoteKeyguardAnimation = + SystemProperties.getBoolean(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, false); + private static final String DISABLE_TRIPLE_BUFFERING_PROPERTY = "ro.sf.disable_triple_buffer"; @@ -770,7 +784,8 @@ public class WindowManagerService extends IWindowManager.Stub final AnrController mAnrController; private final ScreenshotHashController mScreenshotHashController; - private final WindowContextListenerController mWindowContextListenerController = + @VisibleForTesting + final WindowContextListenerController mWindowContextListenerController = new WindowContextListenerController(); @VisibleForTesting @@ -2794,6 +2809,17 @@ public class WindowManagerService extends IWindowManager.Stub return WindowManagerGlobal.ADD_OKAY; } + /** + * Registers a listener for a {@link android.app.WindowContext} to subscribe to configuration + * changes of a {@link DisplayArea}. + * + * @param clientToken the window context's token + * @param type Window type of the window context + * @param displayId The display associated with the window context + * @param options A bundle used to pass window-related options and choose the right DisplayArea + * + * @return {@code true} if the listener was registered successfully. + */ @Override public boolean registerWindowContextListener(IBinder clientToken, int type, int displayId, Bundle options) { @@ -2849,6 +2875,7 @@ public class WindowManagerService extends IWindowManager.Stub } } + /** Returns {@code true} if this binder is a registered window token. */ @Override public boolean isWindowToken(IBinder binder) { synchronized (mGlobalLock) { @@ -7750,6 +7777,15 @@ public class WindowManagerService extends IWindowManager.Stub } @Override + public void registerKeyguardExitAnimationStartListener( + KeyguardExitAnimationStartListener listener) { + synchronized (mGlobalLock) { + getDefaultDisplayContentLocked().mAppTransition + .registerKeygaurdExitAnimationStartListener(listener); + } + } + + @Override public void reportPasswordChanged(int userId) { mKeyguardDisableHandler.updateKeyguardEnabled(userId); } diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index 8a512bc8834b..cd18311d7d54 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -17,7 +17,6 @@ package com.android.server.wm; import static android.os.Process.INVALID_UID; -import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; @@ -26,7 +25,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_MOVEMENT; -import static com.android.internal.protolog.ProtoLogGroup.WM_ERROR; import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN; import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; @@ -41,7 +39,6 @@ import static com.android.server.wm.WindowTokenProto.WINDOW_CONTAINER; import android.annotation.CallSuper; import android.annotation.Nullable; -import android.app.IWindowToken; import android.app.servertransaction.FixedRotationAdjustmentsItem; import android.content.res.Configuration; import android.graphics.Rect; @@ -58,7 +55,6 @@ import android.view.InsetsState; import android.view.SurfaceControl; import android.view.WindowManager; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import com.android.server.policy.WindowManagerPolicy; @@ -112,17 +108,10 @@ class WindowToken extends WindowContainer<WindowState> { private FixedRotationTransformState mFixedRotationTransformState; - private Configuration mLastReportedConfig; - private int mLastReportedDisplay = INVALID_DISPLAY; - /** * When set to {@code true}, this window token is created from {@link android.app.WindowContext} */ - @VisibleForTesting - final boolean mFromClientToken; - - private DeathRecipient mDeathRecipient; - private boolean mBinderDied = false; + private final boolean mFromClientToken; private final int mOwnerUid; @@ -188,30 +177,6 @@ class WindowToken extends WindowContainer<WindowState> { } } - private class DeathRecipient implements IBinder.DeathRecipient { - private boolean mHasUnlinkToDeath = false; - - @Override - public void binderDied() { - synchronized (mWmService.mGlobalLock) { - mBinderDied = true; - removeImmediately(); - } - } - - void linkToDeath() throws RemoteException { - token.linkToDeath(DeathRecipient.this, 0); - } - - void unlinkToDeath() { - if (mHasUnlinkToDeath) { - return; - } - token.unlinkToDeath(DeathRecipient.this, 0); - mHasUnlinkToDeath = true; - } - } - /** * Compares two child window of this token and returns -1 if the first is lesser than the * second in terms of z-order and 1 otherwise. @@ -266,17 +231,6 @@ class WindowToken extends WindowContainer<WindowState> { if (dc != null) { dc.addWindowToken(token, this); } - if (shouldReportToClient()) { - try { - mDeathRecipient = new DeathRecipient(); - mDeathRecipient.linkToDeath(); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to add window token with type " + windowType + " on " - + "display " + dc.getDisplayId(), e); - mDeathRecipient = null; - return; - } - } } void removeAllWindowsIfPossible() { @@ -414,22 +368,6 @@ class WindowToken extends WindowContainer<WindowState> { // Needs to occur after the token is removed from the display above to avoid attempt at // duplicate removal of this window container from it's parent. super.removeImmediately(); - - reportWindowTokenRemovedToClient(); - } - - // TODO(b/159767464): Remove after we migrate to listener approach. - private void reportWindowTokenRemovedToClient() { - if (!shouldReportToClient()) { - return; - } - mDeathRecipient.unlinkToDeath(); - IWindowToken windowTokenClient = IWindowToken.Stub.asInterface(token); - try { - windowTokenClient.onWindowTokenRemoved(); - } catch (RemoteException e) { - ProtoLog.w(WM_ERROR, "Could not report token removal to the window token client."); - } } @Override @@ -441,51 +379,11 @@ class WindowToken extends WindowContainer<WindowState> { // to another display before the window behind // it is ready. super.onDisplayChanged(dc); - reportConfigToWindowTokenClient(); } @Override public void onConfigurationChanged(Configuration newParentConfig) { super.onConfigurationChanged(newParentConfig); - reportConfigToWindowTokenClient(); - } - - void reportConfigToWindowTokenClient() { - if (!shouldReportToClient()) { - return; - } - if (mLastReportedConfig == null) { - mLastReportedConfig = new Configuration(); - } - final Configuration config = getConfiguration(); - final int displayId = getDisplayContent().getDisplayId(); - if (config.diff(mLastReportedConfig) == 0 && displayId == mLastReportedDisplay) { - // No changes since last reported time. - return; - } - - mLastReportedConfig.setTo(config); - mLastReportedDisplay = displayId; - - IWindowToken windowTokenClient = IWindowToken.Stub.asInterface(token); - try { - windowTokenClient.onConfigurationChanged(config, displayId); - } catch (RemoteException e) { - ProtoLog.w(WM_ERROR, - "Could not report config changes to the window token client."); - } - } - - /** - * @return {@code true} if this {@link WindowToken} is not an {@link ActivityRecord} and - * registered from client side. - */ - private boolean shouldReportToClient() { - // Only report to client for WindowToken because Activities are updated through ATM - // callbacks. - return asActivityRecord() == null - // Report to {@link android.view.WindowTokenClient} if this token was registered from it. - && mFromClientToken && !mBinderDied; } @Override diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java index 11e4db503ec5..160033edb093 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java @@ -16,6 +16,7 @@ package com.android.server.devicepolicy; import android.annotation.NonNull; +import android.annotation.UserIdInt; import android.app.admin.DevicePolicySafetyChecker; import android.app.admin.FullyManagedDeviceProvisioningParams; import android.app.admin.IDevicePolicyManager; @@ -127,4 +128,7 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { public void provisionFullyManagedDevice( FullyManagedDeviceProvisioningParams provisioningParams, String callerPackage) { } + + public void resetDefaultCrossProfileIntentFilters(@UserIdInt int userId) { + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 3d2e5de0c19b..717e77b22bdc 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -910,12 +910,20 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { }; protected static class RestrictionsListener implements UserRestrictionsListener { - private Context mContext; - - public RestrictionsListener(Context context) { + private final Context mContext; + private final UserManagerInternal mUserManagerInternal; + private final DevicePolicyManagerService mDpms; + + public RestrictionsListener( + Context context, + UserManagerInternal userManagerInternal, + DevicePolicyManagerService dpms) { mContext = context; + mUserManagerInternal = userManagerInternal; + mDpms = dpms; } + @Override public void onUserRestrictionsChanged(int userId, Bundle newRestrictions, Bundle prevRestrictions) { final boolean newlyDisallowed = @@ -925,13 +933,19 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final boolean restrictionChanged = (newlyDisallowed != previouslyDisallowed); if (restrictionChanged) { - // Notify ManagedProvisioning to update the built-in cross profile intent filters. - Intent intent = new Intent( - DevicePolicyManager.ACTION_DATA_SHARING_RESTRICTION_CHANGED); - intent.setPackage(getManagedProvisioningPackage(mContext)); - intent.putExtra(Intent.EXTRA_USER_ID, userId); - intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); - mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM); + final int parentId = mUserManagerInternal.getProfileParentId(userId); + if (parentId == userId) { + return; + } + + // Always reset filters on the parent user, which handles cross profile intent + // filters between the parent and its profiles. + Slog.i(LOG_TAG, "Resetting cross-profile intent filters on restriction " + + "change"); + mDpms.resetDefaultCrossProfileIntentFilters(parentId); + mContext.sendBroadcastAsUser(new Intent( + DevicePolicyManager.ACTION_DATA_SHARING_RESTRICTION_APPLIED), + UserHandle.of(userId)); } } } @@ -1621,7 +1635,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mSetupContentObserver = new SetupContentObserver(mHandler); - mUserManagerInternal.addUserRestrictionsListener(new RestrictionsListener(mContext)); + mUserManagerInternal.addUserRestrictionsListener( + new RestrictionsListener(mContext, mUserManagerInternal, this)); mUserManagerInternal.addUserLifecycleListener(new UserLifecycleListener()); loadOwners(); @@ -16008,19 +16023,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { provisioningParams.isKeepAccountMigrated(), callerPackage); if (provisioningParams.isOrganizationOwnedProvisioning()) { - markIsProfileOwnerOnOrganizationOwnedDevice(admin, userInfo.id); - restrictRemovalOfManagedProfile(admin, userInfo.id); + setProfileOwnerOnOrgOwnedDeviceState(admin, userInfo.id, caller.getUserId()); } - final Intent intent = new Intent(DevicePolicyManager.ACTION_MANAGED_PROFILE_CREATED) - .putExtra(Intent.EXTRA_USER_HANDLE, userInfo.id) - .putExtra( - DevicePolicyManager.EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED, - provisioningParams.isLeaveAllSystemAppsEnabled()) - .setPackage(getManagedProvisioningPackage(mContext)) - .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); - mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM); - return userInfo.getUserHandle(); } catch (Exception e) { DevicePolicyEventLogger @@ -16250,21 +16255,20 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } - private void markIsProfileOwnerOnOrganizationOwnedDevice( - ComponentName admin, @UserIdInt int profileId) { - getDpmForProfile(profileId).markProfileOwnerOnOrganizationOwnedDevice(admin); - } - - private void restrictRemovalOfManagedProfile( - ComponentName admin, @UserIdInt int profileId) { - getDpmForProfile(profileId).addUserRestriction( - admin, UserManager.DISALLOW_REMOVE_MANAGED_PROFILE); + private void setProfileOwnerOnOrgOwnedDeviceState( + ComponentName admin, @UserIdInt int profileId, @UserIdInt int parentUserId) { + synchronized (getLockObject()) { + markProfileOwnerOnOrganizationOwnedDeviceUncheckedLocked(admin, profileId); + } + restrictRemovalOfManagedProfile(parentUserId); } - private DevicePolicyManager getDpmForProfile(@UserIdInt int profileId) { - final Context profileContext = mContext.createContextAsUser( - UserHandle.of(profileId), /* flags= */ 0); - return profileContext.getSystemService(DevicePolicyManager.class); + private void restrictRemovalOfManagedProfile(@UserIdInt int parentUserId) { + final UserHandle parentUserHandle = UserHandle.of(parentUserId); + mUserManager.setUserRestriction( + UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, + /* value= */ true, + parentUserHandle); } @Override @@ -16313,15 +16317,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } disallowAddUser(); - - final Intent intent = new Intent(DevicePolicyManager.ACTION_PROVISIONED_MANAGED_DEVICE) - .putExtra(Intent.EXTRA_USER_HANDLE, caller.getUserId()) - .putExtra( - DevicePolicyManager.EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED, - provisioningParams.isLeaveAllSystemAppsEnabled()) - .setPackage(getManagedProvisioningPackage(mContext)) - .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); - mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM); } catch (Exception e) { DevicePolicyEventLogger .createEvent(DevicePolicyEnums.PLATFORM_PROVISIONING_ERROR) @@ -16420,4 +16415,45 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { .setStrings(callerPackage) .write(); } + + @Override + public void resetDefaultCrossProfileIntentFilters(@UserIdInt int userId) { + Preconditions.checkCallAuthorization( + hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); + + mInjector.binderWithCleanCallingIdentity(() -> { + try { + final List<UserInfo> profiles = mUserManager.getProfiles(userId); + final int numOfProfiles = profiles.size(); + if (numOfProfiles <= 1) { + return; + } + + final String managedProvisioningPackageName = getManagedProvisioningPackage( + mContext); + // Removes cross profile intent filters from the parent to all the profiles. + mIPackageManager.clearCrossProfileIntentFilters( + userId, mContext.getOpPackageName()); + // Setting and resetting default cross profile intent filters was previously handled + // by Managed Provisioning. For backwards compatibility, clear any intent filters + // that were set by ManagedProvisioning. + mIPackageManager.clearCrossProfileIntentFilters( + userId, managedProvisioningPackageName); + + // For each profile reset cross profile intent filters + for (int i = 0; i < numOfProfiles; i++) { + UserInfo profile = profiles.get(i); + mIPackageManager.clearCrossProfileIntentFilters( + profile.id, mContext.getOpPackageName()); + // Clear any intent filters that were set by ManagedProvisioning. + mIPackageManager.clearCrossProfileIntentFilters( + profile.id, managedProvisioningPackageName); + + mUserManagerInternal.setDefaultCrossProfileIntentFilters(userId, profile.id); + } + } catch (RemoteException e) { + // Shouldn't happen. + } + }); + } } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 8c853ccdd55b..7597cbf322f5 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -5616,43 +5616,52 @@ public class DevicePolicyManagerTest extends DpmTestBase { public void testDisallowSharingIntoProfileSetRestriction() { when(mServiceContext.resources.getString(R.string.config_managed_provisioning_package)) .thenReturn("com.android.managedprovisioning"); + when(getServices().userManagerInternal.getProfileParentId(anyInt())) + .thenReturn(UserHandle.USER_SYSTEM); + mServiceContext.binder.callingPid = DpmMockContext.SYSTEM_PID; + mServiceContext.binder.callingUid = DpmMockContext.SYSTEM_UID; Bundle restriction = new Bundle(); restriction.putBoolean(UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, true); - mContext.binder.callingUid = DpmMockContext.SYSTEM_UID; - RestrictionsListener listener = new RestrictionsListener(mContext); + RestrictionsListener listener = new RestrictionsListener( + mServiceContext, getServices().userManagerInternal, dpms); listener.onUserRestrictionsChanged(CALLER_USER_HANDLE, restriction, new Bundle()); - verifyDataSharingChangedBroadcast(); + + verifyDataSharingAppliedBroadcast(); } @Test public void testDisallowSharingIntoProfileClearRestriction() { when(mServiceContext.resources.getString(R.string.config_managed_provisioning_package)) .thenReturn("com.android.managedprovisioning"); + when(getServices().userManagerInternal.getProfileParentId(anyInt())) + .thenReturn(UserHandle.USER_SYSTEM); + mServiceContext.binder.callingPid = DpmMockContext.SYSTEM_PID; + mServiceContext.binder.callingUid = DpmMockContext.SYSTEM_UID; Bundle restriction = new Bundle(); restriction.putBoolean(UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, true); - mContext.binder.callingUid = DpmMockContext.SYSTEM_UID; - RestrictionsListener listener = new RestrictionsListener(mContext); + RestrictionsListener listener = new RestrictionsListener( + mServiceContext, getServices().userManagerInternal, dpms); listener.onUserRestrictionsChanged(CALLER_USER_HANDLE, new Bundle(), restriction); - verifyDataSharingChangedBroadcast(); + + verifyDataSharingAppliedBroadcast(); } @Test public void testDisallowSharingIntoProfileUnchanged() { - RestrictionsListener listener = new RestrictionsListener(mContext); + RestrictionsListener listener = new RestrictionsListener( + mContext, getServices().userManagerInternal, dpms); listener.onUserRestrictionsChanged(CALLER_USER_HANDLE, new Bundle(), new Bundle()); verify(mContext.spiedContext, never()).sendBroadcastAsUser(any(), any()); } - private void verifyDataSharingChangedBroadcast() { + private void verifyDataSharingAppliedBroadcast() { Intent expectedIntent = new Intent( - DevicePolicyManager.ACTION_DATA_SHARING_RESTRICTION_CHANGED); - expectedIntent.setPackage("com.android.managedprovisioning"); - expectedIntent.putExtra(Intent.EXTRA_USER_ID, CALLER_USER_HANDLE); + DevicePolicyManager.ACTION_DATA_SHARING_RESTRICTION_APPLIED); verify(mContext.spiedContext, times(1)).sendBroadcastAsUser( MockUtils.checkIntent(expectedIntent), - MockUtils.checkUserHandle(UserHandle.USER_SYSTEM)); + MockUtils.checkUserHandle(CALLER_USER_HANDLE)); } @Test diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java index df19aeb13707..58ba90726b80 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java @@ -1829,11 +1829,11 @@ public class NetworkPolicyManagerServiceTest { } /** - * Exhaustively test isUidNetworkingBlocked to output the expected results based on external + * Exhaustively test checkUidNetworkingBlocked to output the expected results based on external * conditions. */ @Test - public void testIsUidNetworkingBlocked() { + public void testCheckUidNetworkingBlocked() { final ArrayList<Pair<Boolean, Integer>> expectedBlockedStates = new ArrayList<>(); // Metered network. Data saver on. @@ -1877,17 +1877,16 @@ public class NetworkPolicyManagerServiceTest { private void verifyNetworkBlockedState(boolean metered, boolean backgroundRestricted, ArrayList<Pair<Boolean, Integer>> expectedBlockedStateForRules) { - final NetworkPolicyManagerInternal npmi = LocalServices - .getService(NetworkPolicyManagerInternal.class); for (Pair<Boolean, Integer> pair : expectedBlockedStateForRules) { final boolean expectedResult = pair.first; final int rule = pair.second; assertEquals(formatBlockedStateError(UID_A, rule, metered, backgroundRestricted), - expectedResult, - npmi.isUidNetworkingBlocked(UID_A, rule, metered, backgroundRestricted)); + expectedResult, mService.checkUidNetworkingBlocked(UID_A, rule, + metered, backgroundRestricted)); assertFalse(formatBlockedStateError(SYSTEM_UID, rule, metered, backgroundRestricted), - npmi.isUidNetworkingBlocked(SYSTEM_UID, rule, metered, backgroundRestricted)); + mService.checkUidNetworkingBlocked(SYSTEM_UID, rule, metered, + backgroundRestricted)); } } diff --git a/services/tests/servicestests/src/com/android/server/pm/BundleUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/BundleUtilsTest.java new file mode 100644 index 000000000000..764c504eeede --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/pm/BundleUtilsTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import static com.android.server.devicepolicy.DpmTestUtils.assertRestrictions; +import static com.android.server.devicepolicy.DpmTestUtils.newRestrictions; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.Bundle; +import android.test.suitebuilder.annotation.SmallTest; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.BundleUtils; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Build/Install/Run: + * atest com.android.server.pm.BundleUtilsTest + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class BundleUtilsTest { + + @Test + public void testIsEmpty() { + assertThat(BundleUtils.isEmpty(null)).isTrue(); + assertThat(BundleUtils.isEmpty(new Bundle())).isTrue(); + assertThat(BundleUtils.isEmpty(newRestrictions("a"))).isFalse(); + } + + @Test + public void testClone() { + Bundle in = new Bundle(); + Bundle out = BundleUtils.clone(in); + assertThat(in).isNotSameInstanceAs(out); + assertRestrictions(out, new Bundle()); + + out = BundleUtils.clone(null); + assertThat(out).isNotNull(); + out.putBoolean("a", true); // Should not be Bundle.EMPTY. + } +} diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java index ee30f68de7a0..cd98d44075ca 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java @@ -72,11 +72,18 @@ public class UserManagerServiceUserTypeTest { @Test public void testUserTypeBuilder_createUserType() { final Bundle restrictions = makeRestrictionsBundle("r1", "r2"); + final Bundle systemSettings = makeSettingsBundle("s1", "s2"); + final Bundle secureSettings = makeSettingsBundle("secure_s1", "secure_s2"); + final List<DefaultCrossProfileIntentFilter> filters = List.of( + new DefaultCrossProfileIntentFilter.Builder( + DefaultCrossProfileIntentFilter.Direction.TO_PARENT, + /* flags= */0, + /* letsPersonalDataIntoProfile= */false).build()); final UserTypeDetails type = new UserTypeDetails.Builder() .setName("a.name") .setEnabled(true) .setMaxAllowed(21) - .setBaseType(FLAG_FULL) + .setBaseType(FLAG_PROFILE) .setDefaultUserInfoPropertyFlags(FLAG_EPHEMERAL) .setBadgeLabels(23, 24, 25) .setBadgeColors(26, 27) @@ -86,20 +93,45 @@ public class UserManagerServiceUserTypeTest { .setLabel(31) .setMaxAllowedPerParent(32) .setDefaultRestrictions(restrictions) + .setDefaultSystemSettings(systemSettings) + .setDefaultSecureSettings(secureSettings) + .setDefaultCrossProfileIntentFilters(filters) .createUserTypeDetails(); assertEquals("a.name", type.getName()); assertTrue(type.isEnabled()); assertEquals(21, type.getMaxAllowed()); - assertEquals(FLAG_FULL | FLAG_EPHEMERAL, type.getDefaultUserInfoFlags()); + assertEquals(FLAG_PROFILE | FLAG_EPHEMERAL, type.getDefaultUserInfoFlags()); assertEquals(28, type.getIconBadge()); assertEquals(29, type.getBadgePlain()); assertEquals(30, type.getBadgeNoBackground()); assertEquals(31, type.getLabel()); assertEquals(32, type.getMaxAllowedPerParent()); + assertTrue(UserRestrictionsUtils.areEqual(restrictions, type.getDefaultRestrictions())); assertNotSame(restrictions, type.getDefaultRestrictions()); + assertNotSame(systemSettings, type.getDefaultSystemSettings()); + assertEquals(systemSettings.size(), type.getDefaultSystemSettings().size()); + for (String key : systemSettings.keySet()) { + assertEquals( + systemSettings.getString(key), + type.getDefaultSystemSettings().getString(key)); + } + + assertNotSame(secureSettings, type.getDefaultSecureSettings()); + assertEquals(secureSettings.size(), type.getDefaultSecureSettings().size()); + for (String key : secureSettings.keySet()) { + assertEquals( + secureSettings.getString(key), + type.getDefaultSecureSettings().getString(key)); + } + + assertNotSame(filters, type.getDefaultCrossProfileIntentFilters()); + assertEquals(filters.size(), type.getDefaultCrossProfileIntentFilters().size()); + for (int i = 0; i < filters.size(); i++) { + assertEquals(filters.get(i), type.getDefaultCrossProfileIntentFilters().get(i)); + } assertEquals(23, type.getBadgeLabel(0)); assertEquals(24, type.getBadgeLabel(1)); @@ -135,6 +167,9 @@ public class UserManagerServiceUserTypeTest { assertEquals(Resources.ID_NULL, type.getBadgeColor(0)); assertEquals(Resources.ID_NULL, type.getLabel()); assertTrue(type.getDefaultRestrictions().isEmpty()); + assertTrue(type.getDefaultSystemSettings().isEmpty()); + assertTrue(type.getDefaultSecureSettings().isEmpty()); + assertTrue(type.getDefaultCrossProfileIntentFilters().isEmpty()); assertFalse(type.hasBadge()); } @@ -416,4 +451,13 @@ public class UserManagerServiceUserTypeTest { } return bundle; } + + /** Creates a Bundle of the given settings keys and puts true for the value. */ + private static Bundle makeSettingsBundle(String ... settings) { + final Bundle bundle = new Bundle(); + for (String setting : settings) { + bundle.putBoolean(setting, true); + } + return bundle; + } } diff --git a/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java index dc181a959d83..ddf0cd0e9235 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java @@ -48,23 +48,6 @@ public class UserRestrictionsUtilsTest extends AndroidTestCase { assertSame(in, UserRestrictionsUtils.nonNull(in)); } - public void testIsEmpty() { - assertTrue(UserRestrictionsUtils.isEmpty(null)); - assertTrue(UserRestrictionsUtils.isEmpty(new Bundle())); - assertFalse(UserRestrictionsUtils.isEmpty(newRestrictions("a"))); - } - - public void testClone() { - Bundle in = new Bundle(); - Bundle out = UserRestrictionsUtils.clone(in); - assertNotSame(in, out); - assertRestrictions(out, new Bundle()); - - out = UserRestrictionsUtils.clone(null); - assertNotNull(out); - out.putBoolean("a", true); // Should not be Bundle.EMPTY. - } - public void testMerge() { Bundle a = newRestrictions("a", "d"); Bundle b = newRestrictions("b", "d", "e"); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index fc1cb70cf746..acda4d5661e4 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -29,8 +29,8 @@ import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_MAX; import static android.app.NotificationManager.IMPORTANCE_NONE; import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; +import static android.util.StatsLog.ANNOTATION_ID_IS_UID; -import static com.android.internal.util.FrameworkStatsLog.ANNOTATION_ID_IS_UID; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES; import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.CHANNEL_ID_FIELD_NUMBER; import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.CHANNEL_NAME_FIELD_NUMBER; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index 57d5323fe330..72c6028e5ad3 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -33,8 +33,8 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCRE import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; +import static android.util.StatsLog.ANNOTATION_ID_IS_UID; -import static com.android.internal.util.FrameworkStatsLog.ANNOTATION_ID_IS_UID; import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE; import static com.android.os.AtomsProto.DNDModeProto.CHANNELS_BYPASSING_FIELD_NUMBER; import static com.android.os.AtomsProto.DNDModeProto.ENABLED_FIELD_NUMBER; diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 42b080e69211..93851109200b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -679,8 +679,10 @@ public class ActivityRecordTests extends WindowTestsBase { new RemoteAnimationAdapter(new Stub() { @Override - public void onAnimationStart(RemoteAnimationTarget[] apps, + public void onAnimationStart(@WindowManager.TransitionOldType int transit, + RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, + RemoteAnimationTarget[] nonApps, IRemoteAnimationFinishedCallback finishedCallback) { } diff --git a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java index 91b9449eddb0..71f19148d616 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java @@ -36,6 +36,7 @@ import android.view.IRemoteAnimationRunner; import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationDefinition; import android.view.RemoteAnimationTarget; +import android.view.WindowManager; import androidx.test.filters.SmallTest; @@ -70,8 +71,10 @@ public class AppChangeTransitionTests extends WindowTestsBase { class TestRemoteAnimationRunner implements IRemoteAnimationRunner { @Override - public void onAnimationStart(RemoteAnimationTarget[] apps, + public void onAnimationStart(@WindowManager.TransitionOldType int transit, + RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, + RemoteAnimationTarget[] nonApps, IRemoteAnimationFinishedCallback finishedCallback) { for (RemoteAnimationTarget target : apps) { assertNotNull(target.startBounds); diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java index f1e36098d84e..83aca5e2d482 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java @@ -265,8 +265,10 @@ public class AppTransitionTests extends WindowTestsBase { private class TestRemoteAnimationRunner implements IRemoteAnimationRunner { boolean mCancelled = false; @Override - public void onAnimationStart(RemoteAnimationTarget[] apps, + public void onAnimationStart(@WindowManager.TransitionOldType int transit, + RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, + RemoteAnimationTarget[] nonApps, IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException { } diff --git a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java index f75c98f39323..78074d6e1492 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java @@ -17,6 +17,7 @@ package com.android.server.wm; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -26,7 +27,6 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -70,13 +70,15 @@ public class InputMethodMenuControllerTest extends WindowTestsBase { spyOn(wms); doAnswer(invocation -> { Object[] args = invocation.getArguments(); - final IBinder token = (IBinder) args[0]; - final int windowType = (int) args[1]; - new WindowToken(mWm, token, windowType, true /* persistOnEmpty */, - mDefaultDisplay, true /* ownerCanManageAppTokens */, 1000 /* ownerUid */, - false /* roundedCornerOverlay */, true /* fromClientToken */); - return WindowManagerGlobal.ADD_OKAY; - }).when(wms).addWindowTokenWithOptions(any(), anyInt(), anyInt(), any(), anyString()); + IBinder clientToken = (IBinder) args[0]; + int displayId = (int) args[2]; + DisplayContent dc = mWm.mRoot.getDisplayContent(displayId); + mWm.mWindowContextListenerController.registerWindowContainerListener(clientToken, + dc.getImeContainer(), 1000 /* ownerUid */, TYPE_INPUT_METHOD_DIALOG, + null /* options */); + return true; + }).when(wms).registerWindowContextListener(any(), eq(TYPE_INPUT_METHOD_DIALOG), + anyInt(), any()); mSecondaryDisplay = new TestDisplayContent.Builder(mAtm, 1000, 1000).build(); @@ -95,14 +97,12 @@ public class InputMethodMenuControllerTest extends WindowTestsBase { assertImeSwitchContextMetricsValidity(contextOnDefaultDisplay, mDefaultDisplay); - // Obtain the context again and check they are the same instance and match the display - // metrics of the secondary display. + // Obtain the context again and check if the window metrics match the IME container bounds + // of the secondary display. final Context contextOnSecondaryDisplay = mController.getSettingsContext( mSecondaryDisplay.getDisplayId()); assertImeSwitchContextMetricsValidity(contextOnSecondaryDisplay, mSecondaryDisplay); - assertThat(contextOnDefaultDisplay.getWindowContextToken()) - .isEqualTo(contextOnSecondaryDisplay.getWindowContextToken()); } private void assertImeSwitchContextMetricsValidity(Context context, DisplayContent dc) { diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java index fa3e3aefea3b..7714a6cead1d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java @@ -183,6 +183,10 @@ public class LetterboxTest { mColor = Color.GREEN; assertTrue(mLetterbox.needsApplySurfaceChanges()); + + mLetterbox.applySurfaceChanges(mTransaction); + + verify(mTransaction).setColor(mSurfaces.top, new float[]{0, 1, 0}); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java index 2efd4b53efcc..15e045c85c29 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java @@ -17,6 +17,9 @@ package com.android.server.wm; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; +import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN; +import static android.view.WindowManager.TRANSIT_OLD_NONE; +import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE; import static com.android.dx.mockito.inline.extended.ExtendedMockito.atLeast; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -100,15 +103,18 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { new Point(50, 100), null, new Rect(50, 100, 150, 150), null).mAdapter; adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION, mFinishedCallback); - mController.goodToGo(); + mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN); mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = ArgumentCaptor.forClass(RemoteAnimationTarget[].class); final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor = ArgumentCaptor.forClass(RemoteAnimationTarget[].class); + final ArgumentCaptor<RemoteAnimationTarget[]> nonApsCaptor = + ArgumentCaptor.forClass(RemoteAnimationTarget[].class); final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor = ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class); - verify(mMockRunner).onAnimationStart(appsCaptor.capture(), wallpapersCaptor.capture(), + verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_ACTIVITY_OPEN), + appsCaptor.capture(), wallpapersCaptor.capture(), nonApsCaptor.capture(), finishedCaptor.capture()); assertEquals(1, appsCaptor.getValue().length); final RemoteAnimationTarget app = appsCaptor.getValue()[0]; @@ -136,7 +142,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { new Point(50, 100), null, new Rect(50, 100, 150, 150), null).mAdapter; adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION, mFinishedCallback); - mController.goodToGo(); + mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN); adapter.onAnimationCancelled(mMockLeash); verify(mMockRunner).onAnimationCancelled(); @@ -149,7 +155,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { new Point(50, 100), null, new Rect(50, 100, 150, 150), null).mAdapter; adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION, mFinishedCallback); - mController.goodToGo(); + mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN); mClock.fastForward(2500); mHandler.timeAdvance(); @@ -170,7 +176,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { null).mAdapter; adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION, mFinishedCallback); - mController.goodToGo(); + mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN); mClock.fastForward(2500); mHandler.timeAdvance(); @@ -190,7 +196,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { @Test public void testZeroAnimations() { - mController.goodToGo(); + mController.goodToGo(TRANSIT_OLD_NONE); verifyNoMoreInteractionsExceptAsBinder(mMockRunner); } @@ -199,7 +205,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); mController.createRemoteAnimationRecord(win.mActivityRecord, new Point(50, 100), null, new Rect(50, 100, 150, 150), null); - mController.goodToGo(); + mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN); verifyNoMoreInteractionsExceptAsBinder(mMockRunner); } @@ -213,15 +219,18 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { new Point(50, 100), null, new Rect(50, 100, 150, 150), null).mAdapter; adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION, mFinishedCallback); - mController.goodToGo(); + mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN); mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = ArgumentCaptor.forClass(RemoteAnimationTarget[].class); final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor = ArgumentCaptor.forClass(RemoteAnimationTarget[].class); + final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor = + ArgumentCaptor.forClass(RemoteAnimationTarget[].class); final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor = ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class); - verify(mMockRunner).onAnimationStart(appsCaptor.capture(), wallpapersCaptor.capture(), + verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_ACTIVITY_OPEN), + appsCaptor.capture(), wallpapersCaptor.capture(), nonAppsCaptor.capture(), finishedCaptor.capture()); assertEquals(1, appsCaptor.getValue().length); assertEquals(mMockLeash, appsCaptor.getValue()[0].leash); @@ -235,7 +244,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION, mFinishedCallback); win.mActivityRecord.removeImmediately(); - mController.goodToGo(); + mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN); verifyNoMoreInteractionsExceptAsBinder(mMockRunner); verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION), eq(adapter)); @@ -255,15 +264,18 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { mFinishedCallback); ((AnimationAdapter) record.mThumbnailAdapter).startAnimation(mMockThumbnailLeash, mMockTransaction, ANIMATION_TYPE_WINDOW_ANIMATION, mThumbnailFinishedCallback); - mController.goodToGo(); + mController.goodToGo(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE); mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = ArgumentCaptor.forClass(RemoteAnimationTarget[].class); final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor = ArgumentCaptor.forClass(RemoteAnimationTarget[].class); + final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor = + ArgumentCaptor.forClass(RemoteAnimationTarget[].class); final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor = ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class); - verify(mMockRunner).onAnimationStart(appsCaptor.capture(), wallpapersCaptor.capture(), + verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE), + appsCaptor.capture(), wallpapersCaptor.capture(), nonAppsCaptor.capture(), finishedCaptor.capture()); assertEquals(1, appsCaptor.getValue().length); final RemoteAnimationTarget app = appsCaptor.getValue()[0]; @@ -305,15 +317,18 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { mFinishedCallback); ((AnimationAdapter) record.mThumbnailAdapter).startAnimation(mMockThumbnailLeash, mMockTransaction, ANIMATION_TYPE_WINDOW_ANIMATION, mThumbnailFinishedCallback); - mController.goodToGo(); + mController.goodToGo(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE); mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = ArgumentCaptor.forClass(RemoteAnimationTarget[].class); final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor = ArgumentCaptor.forClass(RemoteAnimationTarget[].class); + final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor = + ArgumentCaptor.forClass(RemoteAnimationTarget[].class); final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor = ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class); - verify(mMockRunner).onAnimationStart(appsCaptor.capture(), wallpapersCaptor.capture(), + verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE), + appsCaptor.capture(), wallpapersCaptor.capture(), nonAppsCaptor.capture(), finishedCaptor.capture()); assertEquals(1, appsCaptor.getValue().length); final RemoteAnimationTarget app = appsCaptor.getValue()[0]; @@ -354,15 +369,18 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { new Point(50, 100), null, new Rect(50, 100, 150, 150), null).mAdapter; adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION, mFinishedCallback); - mController.goodToGo(); + mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN); mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = ArgumentCaptor.forClass(RemoteAnimationTarget[].class); final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor = ArgumentCaptor.forClass(RemoteAnimationTarget[].class); + final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor = + ArgumentCaptor.forClass(RemoteAnimationTarget[].class); final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor = ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class); - verify(mMockRunner).onAnimationStart(appsCaptor.capture(), wallpapersCaptor.capture(), + verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_ACTIVITY_OPEN), + appsCaptor.capture(), wallpapersCaptor.capture(), nonAppsCaptor.capture(), finishedCaptor.capture()); assertEquals(1, wallpapersCaptor.getValue().length); } finally { @@ -383,15 +401,18 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { new Point(50, 100), null, new Rect(50, 100, 150, 150), null).mAdapter; adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION, mFinishedCallback); - mController.goodToGo(); + mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN); mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = ArgumentCaptor.forClass(RemoteAnimationTarget[].class); final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor = ArgumentCaptor.forClass(RemoteAnimationTarget[].class); + final ArgumentCaptor<RemoteAnimationTarget[]> nonAPpsCaptor = + ArgumentCaptor.forClass(RemoteAnimationTarget[].class); final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor = ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class); - verify(mMockRunner).onAnimationStart(appsCaptor.capture(), wallpapersCaptor.capture(), + verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_ACTIVITY_OPEN), + appsCaptor.capture(), wallpapersCaptor.capture(), nonAPpsCaptor.capture(), finishedCaptor.capture()); assertEquals(1, wallpapersCaptor.getValue().length); diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 6f775cf301b5..cc4d4eaa9e8b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -39,8 +39,6 @@ import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING; import static com.android.server.wm.Task.ActivityState.STOPPED; import static com.android.server.wm.WindowContainer.POSITION_TOP; -import static com.google.common.truth.Truth.assertThat; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; @@ -119,13 +117,13 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testKeepBoundsWhenChangingFromFreeformToFullscreen() { removeGlobalMinSizeRestriction(); - // Create landscape freeform display and a freeform app. + // create freeform display and a freeform app DisplayContent display = new TestDisplayContent.Builder(mAtm, 2000, 1000) .setCanRotate(false) .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM).build(); setUpApp(display); - // Put app window into portrait freeform and then make it a compat app. + // Put app window into freeform and then make it a compat app. final Rect bounds = new Rect(100, 100, 400, 600); mTask.setBounds(bounds); prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); @@ -138,7 +136,7 @@ public class SizeCompatTests extends WindowTestsBase { final int density = mActivity.getConfiguration().densityDpi; - // Change display configuration to fullscreen. + // change display configuration to fullscreen Configuration c = new Configuration(display.getRequestedOverrideConfiguration()); c.windowConfiguration.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN); display.onRequestedOverrideConfigurationChanged(c); @@ -148,8 +146,6 @@ public class SizeCompatTests extends WindowTestsBase { assertEquals(bounds.width(), mActivity.getBounds().width()); assertEquals(bounds.height(), mActivity.getBounds().height()); assertEquals(density, mActivity.getConfiguration().densityDpi); - // Size compat mode is sandboxed at the activity level. - assertActivityMaxBoundsSandboxedForSizeCompat(); } @Test @@ -175,12 +171,6 @@ public class SizeCompatTests extends WindowTestsBase { assertEquals(appBounds.height(), appBounds.width() * aspectRatio, 0.5f /* delta */); // The decor height should be a part of the effective bounds. assertEquals(mActivity.getBounds().height(), appBounds.height() + notchHeight); - // Activity max bounds should be sandboxed; activity is letterboxed due to aspect ratio. - assertActivityMaxBoundsSandboxedForLetterbox(); - // Activity max bounds ignore notch, since an app can be shown past the notch (although app - // is currently limited by the notch). - assertThat(mActivity.getWindowConfiguration().getMaxBounds().height()) - .isEqualTo(displayBounds.height()); mActivity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE); assertFitted(); @@ -190,17 +180,9 @@ public class SizeCompatTests extends WindowTestsBase { assertEquals(appBounds.width(), appBounds.height() * aspectRatio, 0.5f /* delta */); // The notch is no longer on top. assertEquals(appBounds, mActivity.getBounds()); - // Activity max bounds are sandboxed. - assertActivityMaxBoundsSandboxedForLetterbox(); mActivity.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT); assertFitted(); - // Activity max bounds should be sandboxed; activity is letterboxed due to aspect ratio. - assertActivityMaxBoundsSandboxedForLetterbox(); - // Activity max bounds ignore notch, since an app can be shown past the notch (although app - // is currently limited by the notch). - assertThat(mActivity.getWindowConfiguration().getMaxBounds().height()) - .isEqualTo(displayBounds.height()); } @Test @@ -228,9 +210,6 @@ public class SizeCompatTests extends WindowTestsBase { assertEquals(originalBounds.width(), mActivity.getBounds().width()); assertEquals(originalBounds.height(), mActivity.getBounds().height()); assertEquals(originalDpi, mActivity.getConfiguration().densityDpi); - // Activity is sandboxed; it is in size compat mode since it is not resizable and has a - // max aspect ratio. - assertActivityMaxBoundsSandboxedForSizeCompat(); assertScaled(); } @@ -238,13 +217,11 @@ public class SizeCompatTests extends WindowTestsBase { public void testFixedScreenBoundsWhenDisplaySizeChanged() { setUpDisplaySizeWithApp(1000, 2500); prepareUnresizable(mActivity, -1f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); - final DisplayContent display = mActivity.mDisplayContent; assertFitted(); - // Activity and task inherit bounds from TaskDisplayArea, since not sandboxed. - assertMaxBoundsInheritDisplayAreaBounds(); final Rect origBounds = new Rect(mActivity.getBounds()); final Rect currentBounds = mActivity.getWindowConfiguration().getBounds(); + final DisplayContent display = mActivity.mDisplayContent; // Change the size of current display. resizeDisplay(display, 1000, 2000); @@ -261,8 +238,6 @@ public class SizeCompatTests extends WindowTestsBase { // The position of configuration bounds should be the same as compat bounds. assertEquals(mActivity.getBounds().left, currentBounds.left); assertEquals(mActivity.getBounds().top, currentBounds.top); - // Activity is sandboxed to the offset size compat bounds. - assertActivityMaxBoundsSandboxedForSizeCompat(); // Change display size to a different orientation resizeDisplay(display, 2000, 1000); @@ -271,8 +246,6 @@ public class SizeCompatTests extends WindowTestsBase { assertEquals(origBounds.height(), currentBounds.height()); assertEquals(ORIENTATION_LANDSCAPE, display.getConfiguration().orientation); assertEquals(Configuration.ORIENTATION_PORTRAIT, mActivity.getConfiguration().orientation); - // Activity is sandboxed to the offset size compat bounds. - assertActivityMaxBoundsSandboxedForSizeCompat(); // The previous resize operation doesn't consider the rotation change after size changed. // These setups apply the requested orientation to rotation as real case that the top fixed @@ -292,8 +265,6 @@ public class SizeCompatTests extends WindowTestsBase { assertEquals(origBounds.height(), currentBounds.height()); assertEquals(offsetX, currentBounds.left); assertScaled(); - // Activity is sandboxed due to size compat mode. - assertActivityMaxBoundsSandboxedForSizeCompat(); } @Test @@ -309,8 +280,6 @@ public class SizeCompatTests extends WindowTestsBase { assertEquals(bounds.width(), bounds.height() * maxAspect, 0.0001f /* delta */); // The position should be horizontal centered. assertEquals((displayWidth - bounds.width()) / 2, bounds.left); - // Activity max bounds should be sandboxed since it is letterboxed. - assertActivityMaxBoundsSandboxedForLetterbox(); mActivity.mDisplayContent.setImeLayeringTarget(addWindowToActivity(mActivity)); // Make sure IME cannot attach to the app, otherwise IME window will also be shifted. @@ -322,8 +291,6 @@ public class SizeCompatTests extends WindowTestsBase { // It should keep non-attachable because the resolved bounds will be computed according to // the aspect ratio that won't match its parent bounds. assertFalse(mActivity.mDisplayContent.isImeAttachedToApp()); - // Activity max bounds should be sandboxed since it is letterboxed. - assertActivityMaxBoundsSandboxedForLetterbox(); } @Test @@ -349,13 +316,14 @@ public class SizeCompatTests extends WindowTestsBase { } @Test - public void testMoveToDifferentOrientationDisplay() { + public void testMoveToDifferentOrientDisplay() { setUpDisplaySizeWithApp(1000, 2500); prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); assertFitted(); - final Rect currentBounds = mActivity.getWindowConfiguration().getBounds(); - final Rect originalBounds = new Rect(mActivity.getWindowConfiguration().getBounds()); + final Rect configBounds = mActivity.getWindowConfiguration().getBounds(); + final int origWidth = configBounds.width(); + final int origHeight = configBounds.height(); final int notchHeight = 100; final DisplayContent newDisplay = new TestDisplayContent.Builder(mAtm, 2000, 1000) @@ -364,44 +332,37 @@ public class SizeCompatTests extends WindowTestsBase { // Move the non-resizable activity to the new display. mTask.reparent(newDisplay.getDefaultTaskDisplayArea(), true /* onTop */); // The configuration bounds [820, 0 - 1820, 2500] should keep the same. - assertEquals(originalBounds.width(), currentBounds.width()); - assertEquals(originalBounds.height(), currentBounds.height()); + assertEquals(origWidth, configBounds.width()); + assertEquals(origHeight, configBounds.height()); assertScaled(); - // Activity max bounds are sandboxed due to size compat mode on the new display. - assertActivityMaxBoundsSandboxedForSizeCompat(); final Rect newDisplayBounds = newDisplay.getWindowConfiguration().getBounds(); // The scaled bounds should exclude notch area (1000 - 100 == 360 * 2500 / 1000 = 900). assertEquals(newDisplayBounds.height() - notchHeight, - (int) ((float) mActivity.getBounds().width() * originalBounds.height() - / originalBounds.width())); + (int) ((float) mActivity.getBounds().width() * origHeight / origWidth)); // Recompute the natural configuration in the new display. mActivity.clearSizeCompatMode(); mActivity.ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */); // Because the display cannot rotate, the portrait activity will fit the short side of // display with keeping portrait bounds [200, 0 - 700, 1000] in center. - assertEquals(newDisplayBounds.height(), currentBounds.height()); - assertEquals(currentBounds.height() * newDisplayBounds.height() / newDisplayBounds.width(), - currentBounds.width()); + assertEquals(newDisplayBounds.height(), configBounds.height()); + assertEquals(configBounds.height() * newDisplayBounds.height() / newDisplayBounds.width(), + configBounds.width()); assertFitted(); // The appBounds should be [200, 100 - 700, 1000]. final Rect appBounds = mActivity.getWindowConfiguration().getAppBounds(); - assertEquals(currentBounds.width(), appBounds.width()); - assertEquals(currentBounds.height() - notchHeight, appBounds.height()); - // Task max bounds are sandboxed due to letterboxing from orientation mismatch with display. - assertTaskMaxBoundsSandboxed(); + assertEquals(configBounds.width(), appBounds.width()); + assertEquals(configBounds.height() - notchHeight, appBounds.height()); } @Test - public void testFixedOrientationRotateCutoutDisplay() { + public void testFixedOrientRotateCutoutDisplay() { // Create a display with a notch/cutout final int notchHeight = 60; - final int width = 1000; - setUpApp(new TestDisplayContent.Builder(mAtm, width, 2500) + setUpApp(new TestDisplayContent.Builder(mAtm, 1000, 2500) .setNotch(notchHeight).build()); - // Bounds=[0, 0 - 1000, 1400], AppBounds=[0, 60 - 1000, 1460]. - final float maxAspect = 1.4f; + // Bounds=[0, 0 - 1000, 1460], AppBounds=[0, 60 - 1000, 1460]. prepareUnresizable(mActivity, 1.4f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); final Rect currentBounds = mActivity.getWindowConfiguration().getBounds(); @@ -409,11 +370,6 @@ public class SizeCompatTests extends WindowTestsBase { final Rect origBounds = new Rect(currentBounds); final Rect origAppBounds = new Rect(appBounds); - // Activity is sandboxed, and bounds include the area consumed by the notch. - assertActivityMaxBoundsSandboxedForLetterbox(); - assertThat(mActivity.getConfiguration().windowConfiguration.getMaxBounds().height()) - .isEqualTo(Math.round(width * maxAspect) + notchHeight); - // Although the activity is fixed orientation, force rotate the display. rotateDisplay(mActivity.mDisplayContent, ROTATION_270); assertEquals(ROTATION_270, mTask.getWindowConfiguration().getRotation()); @@ -429,13 +385,10 @@ public class SizeCompatTests extends WindowTestsBase { // The position in configuration should be global coordinates. assertEquals(mActivity.getBounds().left, currentBounds.left); assertEquals(mActivity.getBounds().top, currentBounds.top); - - // Activity max bounds are sandboxed due to size compat mode. - assertActivityMaxBoundsSandboxedForSizeCompat(); } @Test - public void testFixedAspectRatioOrientationChangeOrientation() { + public void testFixedAspOrientChangeOrient() { setUpDisplaySizeWithApp(1000, 2500); final float maxAspect = 1.4f; @@ -447,8 +400,6 @@ public class SizeCompatTests extends WindowTestsBase { final Rect originalAppBounds = new Rect(mActivity.getWindowConfiguration().getAppBounds()); assertEquals((int) (originalBounds.width() * maxAspect), originalBounds.height()); - // Activity is sandboxed due to fixed aspect ratio. - assertActivityMaxBoundsSandboxedForLetterbox(); // Change the fixed orientation. mActivity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE); @@ -460,8 +411,6 @@ public class SizeCompatTests extends WindowTestsBase { mActivity.getWindowConfiguration().getAppBounds().height()); assertEquals(originalAppBounds.height(), mActivity.getWindowConfiguration().getAppBounds().width()); - // Activity is sandboxed due to fixed aspect ratio. - assertActivityMaxBoundsSandboxedForLetterbox(); } @Test @@ -510,8 +459,6 @@ public class SizeCompatTests extends WindowTestsBase { // restarted and the override configuration won't be cleared. verify(mActivity, never()).restartProcessIfVisible(); assertScaled(); - // Activity max bounds are sandboxed due to size compat mode, even if is not visible. - assertActivityMaxBoundsSandboxedForSizeCompat(); // Change display density display.mBaseDisplayDensity = (int) (0.7f * display.mBaseDisplayDensity); @@ -586,16 +533,12 @@ public class SizeCompatTests extends WindowTestsBase { // in multi-window mode. mTask.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM); assertFalse(activity.shouldUseSizeCompatMode()); - // Activity and task should not be sandboxed. - assertMaxBoundsInheritDisplayAreaBounds(); // The non-resizable activity should not be size compat because the display support // changing windowing mode from fullscreen to freeform. mTask.mDisplayContent.setDisplayWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM); mTask.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN); assertFalse(activity.shouldUseSizeCompatMode()); - // Activity and task should not be sandboxed. - assertMaxBoundsInheritDisplayAreaBounds(); } @Test @@ -659,9 +602,6 @@ public class SizeCompatTests extends WindowTestsBase { // be transparent. assertFalse(displayPolicy.isFullyTransparentAllowed(w, TYPE_STATUS_BAR)); - // Activity is sandboxed. - assertActivityMaxBoundsSandboxedForLetterbox(); - // Make the activity fill the display. prepareUnresizable(mActivity, 10 /* maxAspect */, SCREEN_ORIENTATION_LANDSCAPE); w.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN; @@ -671,7 +611,6 @@ public class SizeCompatTests extends WindowTestsBase { // The letterbox should only cover the notch area, so status bar can be transparent. assertEquals(new Rect(notchHeight, 0, 0, 0), mActivity.getLetterboxInsets()); assertTrue(displayPolicy.isFullyTransparentAllowed(w, TYPE_STATUS_BAR)); - assertActivityMaxBoundsSandboxedForLetterbox(); } @Test @@ -696,8 +635,6 @@ public class SizeCompatTests extends WindowTestsBase { assertTrue(mTask.isTaskLetterboxed()); assertFalse(mActivity.inSizeCompatMode()); assertEquals(taskBounds, activityBounds); - // Activity inherits max bounds from task, since sandboxing applied to task. - assertTaskMaxBoundsSandboxed(); // Task bounds should be 700x1400 with the ratio as the display. assertEquals(displayBounds.height(), taskBounds.height()); @@ -728,8 +665,6 @@ public class SizeCompatTests extends WindowTestsBase { assertScaled(); assertEquals(activityBounds.width(), newActivityBounds.width()); assertEquals(activityBounds.height(), newActivityBounds.height()); - // Activity max bounds are sandboxed due to size compat mode. - assertActivityMaxBoundsSandboxedForSizeCompat(); } @Test @@ -741,30 +676,29 @@ public class SizeCompatTests extends WindowTestsBase { // Portrait fixed app without max aspect. prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT); + Rect displayBounds = new Rect(mActivity.mDisplayContent.getBounds()); + Rect activityBounds = new Rect(mActivity.getBounds()); + // App should launch in fullscreen. assertFalse(mTask.isTaskLetterboxed()); assertFalse(mActivity.inSizeCompatMode()); - // Activity and task inherit max bounds from TaskDisplayArea. - assertMaxBoundsInheritDisplayAreaBounds(); + assertEquals(displayBounds, activityBounds); // Rotate display to landscape. rotateDisplay(mActivity.mDisplayContent, ROTATION_90); - final Rect rotatedDisplayBounds = new Rect(mActivity.mDisplayContent.getBounds()); - final Rect rotatedActivityBounds = new Rect(mActivity.getBounds()); - assertTrue(rotatedDisplayBounds.width() > rotatedDisplayBounds.height()); + displayBounds = new Rect(mActivity.mDisplayContent.getBounds()); + activityBounds = new Rect(mActivity.getBounds()); + assertTrue(displayBounds.width() > displayBounds.height()); // App should be in size compat. assertFalse(mTask.isTaskLetterboxed()); assertScaled(); - assertThat(mActivity.inSizeCompatMode()).isTrue(); - // Activity max bounds are sandboxed due to size compat mode. - assertActivityMaxBoundsSandboxedForSizeCompat(); // App bounds should be 700x1400 with the ratio as the display. - assertEquals(rotatedDisplayBounds.height(), rotatedActivityBounds.height()); - assertEquals(rotatedDisplayBounds.height() * rotatedDisplayBounds.height() - / rotatedDisplayBounds.width(), rotatedActivityBounds.width()); + assertEquals(displayBounds.height(), activityBounds.height()); + assertEquals(displayBounds.height() * displayBounds.height() / displayBounds.width(), + activityBounds.width()); } @Test @@ -797,17 +731,14 @@ public class SizeCompatTests extends WindowTestsBase { final Rect displayBounds = new Rect(display.getBounds()); final Rect taskBounds = new Rect(mTask.getBounds()); final Rect newActivityBounds = new Rect(newActivity.getBounds()); - final float displayAspectRatio = (float) displayBounds.height() / displayBounds.width(); // Task and app bounds should be 700x1400 with the ratio as the display. assertTrue(mTask.isTaskLetterboxed()); assertFalse(newActivity.inSizeCompatMode()); assertEquals(taskBounds, newActivityBounds); assertEquals(displayBounds.height(), taskBounds.height()); - assertThat(taskBounds.width()) - .isEqualTo(Math.round(displayBounds.height() * displayAspectRatio)); - // Task max bounds are sandboxed due to letterbox, with the ratio of the display. - assertTaskMaxBoundsSandboxed(); + assertEquals(displayBounds.height() * displayBounds.height() / displayBounds.width(), + taskBounds.width()); } @Test @@ -847,14 +778,6 @@ public class SizeCompatTests extends WindowTestsBase { assertEquals(displayBounds.height(), taskBounds.height()); assertEquals((long) Math.rint(taskBounds.height() / newActivity.info.maxAspectRatio), taskBounds.width()); - // New activity max bounds are sandboxed due to letterbox. - assertThat(newActivity.getConfiguration().windowConfiguration.getMaxBounds()) - .isEqualTo(taskBounds); - // Task max bounds are sandboxed due to letterbox, with the ratio of the display. - assertThat(mTask.getConfiguration().windowConfiguration.getMaxBounds().height()) - .isEqualTo(displayBounds.height()); - assertThat(mTask.getConfiguration().windowConfiguration.getMaxBounds().width()) - .isEqualTo(Math.round(displayBounds.height() / newActivity.info.maxAspectRatio)); // App bounds should be fullscreen in Task bounds. assertFalse(newActivity.inSizeCompatMode()); @@ -883,9 +806,6 @@ public class SizeCompatTests extends WindowTestsBase { assertFalse(mTask.isTaskLetterboxed()); assertScaled(); assertEquals(mTask.getLastTaskBoundsComputeActivity(), mActivity); - assertThat(mActivity.inSizeCompatMode()).isTrue(); - // Activity max bounds are sandboxed due to size compat mode. - assertActivityMaxBoundsSandboxedForSizeCompat(); final Rect activityBounds = new Rect(mActivity.getBounds()); mTask.resumeTopActivityUncheckedLocked(null /* prev */, null /* options */); @@ -896,8 +816,6 @@ public class SizeCompatTests extends WindowTestsBase { assertScaled(); assertEquals(mTask.getLastTaskBoundsComputeActivity(), mActivity); assertEquals(activityBounds, mActivity.getBounds()); - // Activity max bounds are sandboxed due to size compat. - assertActivityMaxBoundsSandboxedForSizeCompat(); } @Test @@ -913,7 +831,6 @@ public class SizeCompatTests extends WindowTestsBase { // In Task letterbox assertTrue(mTask.isTaskLetterboxed()); assertFalse(mActivity.inSizeCompatMode()); - assertTaskMaxBoundsSandboxed(); // Rotate display to portrait. rotateDisplay(display, ROTATION_90); @@ -921,7 +838,6 @@ public class SizeCompatTests extends WindowTestsBase { // App should be in size compat. assertFalse(mTask.isTaskLetterboxed()); assertScaled(); - assertActivityMaxBoundsSandboxedForSizeCompat(); // Rotate display to landscape. rotateDisplay(display, ROTATION_180); @@ -929,7 +845,6 @@ public class SizeCompatTests extends WindowTestsBase { // In Task letterbox assertTrue(mTask.isTaskLetterboxed()); assertFalse(mActivity.inSizeCompatMode()); - assertTaskMaxBoundsSandboxed(); } @Test @@ -947,26 +862,20 @@ public class SizeCompatTests extends WindowTestsBase { // In Task letterbox assertTrue(mTask.isTaskLetterboxed()); assertFalse(mActivity.inSizeCompatMode()); - // Task is letterboxed due to mismatched orientation request. - assertTaskMaxBoundsSandboxed(); - // Rotate display to landscape. + // Rotate display to portrait. rotateDisplay(display, ROTATION_90); // App should be in size compat. assertFalse(mTask.isTaskLetterboxed()); assertScaled(); - // Activity max bounds are sandboxed due to unresizable app. - assertActivityMaxBoundsSandboxedForSizeCompat(); - // Rotate display to portrait. + // Rotate display to landscape. rotateDisplay(display, ROTATION_180); // In Task letterbox assertTrue(mTask.isTaskLetterboxed()); assertFalse(mActivity.inSizeCompatMode()); - // Task is letterboxed, as in first case. - assertTaskMaxBoundsSandboxed(); } @Test @@ -983,18 +892,12 @@ public class SizeCompatTests extends WindowTestsBase { assertEquals(ORIENTATION_LANDSCAPE, display.getConfiguration().orientation); assertEquals(2800, displayBounds.width()); assertEquals(1400, displayBounds.height()); - Rect displayAreaBounds = new Rect(0, 0, 2400, 1000); - taskDisplayArea.setBounds(displayAreaBounds); + taskDisplayArea.setBounds(0, 0, 2400, 1000); final Rect activityBounds = new Rect(mActivity.getBounds()); assertFalse(mActivity.inSizeCompatMode()); assertEquals(2400, activityBounds.width()); assertEquals(1000, activityBounds.height()); - // Task and activity maximum bounds inherit from TaskDisplayArea bounds. - assertThat(mActivity.getConfiguration().windowConfiguration.getMaxBounds()) - .isEqualTo(displayAreaBounds); - assertThat(mTask.getConfiguration().windowConfiguration.getMaxBounds()) - .isEqualTo(displayAreaBounds); } @Test @@ -1142,48 +1045,6 @@ public class SizeCompatTests extends WindowTestsBase { assertFalse(mActivity.hasSizeCompatBounds()); } - /** Asserts both the activity and task max bounds inherit from the TaskDisplayArea. */ - private void assertMaxBoundsInheritDisplayAreaBounds() { - final Rect taskDisplayAreaBounds = mTask.getDisplayArea().getBounds(); - assertThat(mActivity.getConfiguration().windowConfiguration.getMaxBounds()) - .isEqualTo(taskDisplayAreaBounds); - assertThat(mTask.getConfiguration().windowConfiguration.getMaxBounds()) - .isEqualTo(taskDisplayAreaBounds); - } - - /** - * Asserts task-level letterboxing, so both activity and task max bounds - * are sandboxed to the letterbox bounds. - */ - private void assertTaskMaxBoundsSandboxed() { - // Activity inherits max bounds from task, since sandboxing applied to task. - assertThat(mActivity.getConfiguration().windowConfiguration.getMaxBounds()) - .isEqualTo(mTask.getBounds()); - // Task max bounds are sandboxed due to letterbox. - assertThat(mTask.getConfiguration().windowConfiguration.getMaxBounds()) - .isEqualTo(mTask.getBounds()); - } - - /** Asserts activity-level size compat mode, so only activity max bounds are sandboxed. */ - private void assertActivityMaxBoundsSandboxedForSizeCompat() { - // Activity max bounds are sandboxed due to size compat mode. - assertThat(mActivity.getConfiguration().windowConfiguration.getMaxBounds()) - .isEqualTo(mActivity.getWindowConfiguration().getBounds()); - // Task inherits max bounds from display. - assertThat(mTask.getConfiguration().windowConfiguration.getMaxBounds()) - .isEqualTo(mTask.getDisplayContent().getBounds()); - } - - /** Asserts activity-level letterboxing, so only activity max bounds are sandboxed. */ - private void assertActivityMaxBoundsSandboxedForLetterbox() { - // Activity is sandboxed due to fixed aspect ratio. - assertThat(mActivity.getConfiguration().windowConfiguration.getMaxBounds()) - .isEqualTo(mActivity.getBounds()); - // Task inherits bounds from display. - assertThat(mTask.getConfiguration().windowConfiguration.getMaxBounds()) - .isEqualTo(mTask.getDisplayContent().getBounds()); - } - static Configuration rotateDisplay(DisplayContent display, int rotation) { final Configuration c = new Configuration(); display.getDisplayRotation().setRotation(rotation); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java index f20513dd369f..0eb8c8d2e58a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java @@ -1004,6 +1004,26 @@ public class TaskRecordTests extends WindowTestsBase { } @Test + public void testNotSaveLaunchingStateForNonLeafTask() { + LaunchParamsPersister persister = mAtm.mTaskSupervisor.mLaunchParamsPersister; + spyOn(persister); + + final Task task = getTestTask(); + task.setHasBeenVisible(false); + task.getDisplayContent().setDisplayWindowingMode(WINDOWING_MODE_FREEFORM); + task.getRootTask().setWindowingMode(WINDOWING_MODE_FULLSCREEN); + + final Task leafTask = createTaskInStack(task, 0 /* userId */); + + leafTask.setHasBeenVisible(true); + task.setHasBeenVisible(true); + task.onConfigurationChanged(task.getParent().getConfiguration()); + + verify(persister, never()).saveTask(same(task), any()); + verify(persister).saveTask(same(leafTask), any()); + } + + @Test public void testNotSpecifyOrientationByFloatingTask() { final Task task = new TaskBuilder(mSupervisor) .setCreateActivity(true).setCreateParentTask(true).build(); diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java index d71993df8602..ae85ceb72958 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java @@ -136,11 +136,6 @@ class TestDisplayContent extends DisplayContent { final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId, mInfo, DEFAULT_DISPLAY_ADJUSTMENTS); final TestDisplayContent newDisplay = createInternal(display); - // Ensure letterbox aspect ratio is not overridden on any device target. - // {@link com.android.internal.R.dimen.config_taskLetterboxAspectRatio}, provided by - // the below method, is set on some device form factors. - mService.mWindowManager.setTaskLetterboxAspectRatio(0); - // disable the normal system decorations final DisplayPolicy displayPolicy = newDisplay.getDisplayPolicy(); spyOn(displayPolicy); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java index df5b48a038f3..99c96bd0de1b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java @@ -66,6 +66,7 @@ import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; import android.view.SurfaceSession; +import android.view.WindowManager; import androidx.test.filters.SmallTest; @@ -905,8 +906,10 @@ public class WindowContainerTests extends WindowTestsBase { final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter( new IRemoteAnimationRunner.Stub() { @Override - public void onAnimationStart(RemoteAnimationTarget[] apps, + public void onAnimationStart(@WindowManager.TransitionOldType int transit, + RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, + RemoteAnimationTarget[] nonApps, IRemoteAnimationFinishedCallback finishedCallback) { try { finishedCallback.onAnimationFinished(); diff --git a/startop/view_compiler/Android.bp b/startop/view_compiler/Android.bp index 7cc233b2439e..cac7b82adc2c 100644 --- a/startop/view_compiler/Android.bp +++ b/startop/view_compiler/Android.bp @@ -84,7 +84,6 @@ cc_test_host { static_libs: [ "libviewcompiler", ], - test_suites: ["general-tests"], } cc_binary_host { diff --git a/startop/view_compiler/TEST_MAPPING b/startop/view_compiler/TEST_MAPPING index 5f7d3f99ae81..791e47105ff9 100644 --- a/startop/view_compiler/TEST_MAPPING +++ b/startop/view_compiler/TEST_MAPPING @@ -10,10 +10,6 @@ "include-filter": "android.view.cts.PrecompiledLayoutTest" } ] - }, - { - "name": "view-compiler-tests", - "host": true } ] } diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 7215cd5e5559..ac584c175c59 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -4038,6 +4038,20 @@ public class CarrierConfigManager { public static final String KEY_ENABLE_PRESENCE_GROUP_SUBSCRIBE_BOOL = KEY_PREFIX + "enable_presence_group_subscribe_bool"; + /** + * An integer key associated with the period of time in seconds the non-rcs capability + * information of each contact is cached on the device. + * <p> + * The rcs capability cache expiration sec is managed by + * {@code android.telephony.ims.ProvisioningManager} but non-rcs capability is managed by + * {@link CarrierConfigManager} since non-rcs capability will be provided via ACS or carrier + * config. + * <p> + * The default value is 2592000 secs (30 days), see RCC.07 Annex A.1.9. + */ + public static final String KEY_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC_INT = + KEY_PREFIX + "non_rcs_capabilities_cache_expiration_sec_int"; + private Ims() {} private static PersistableBundle getDefaults() { @@ -4048,6 +4062,7 @@ public class CarrierConfigManager { defaults.putBoolean(KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL, false); defaults.putBoolean(KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL, false); defaults.putBoolean(KEY_ENABLE_PRESENCE_GROUP_SUBSCRIBE_BOOL, true); + defaults.putInt(KEY_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC_INT, 30 * 24 * 60 * 60); return defaults; } } diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 2734ad1194d3..1473b7a8873d 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -47,6 +47,7 @@ import android.net.NetworkPolicyManager; import android.net.Uri; import android.os.Binder; import android.os.Build; +import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.ParcelUuid; @@ -150,6 +151,22 @@ public class SubscriptionManager { public static final String CACHE_KEY_SLOT_INDEX_PROPERTY = "cache_key.telephony.get_slot_index"; + /** @hide */ + public static final String GET_SIM_SPECIFIC_SETTINGS_METHOD_NAME = "getSimSpecificSettings"; + + /** @hide */ + public static final String RESTORE_SIM_SPECIFIC_SETTINGS_METHOD_NAME = + "restoreSimSpecificSettings"; + + /** + * Key to the backup & restore data byte array in the Bundle that is returned by {@link + * #getAllSimSpecificSettingsForBackup()} or to be pass in to {@link + * #restoreAllSimSpecificSettings()}. + * + * @hide + */ + public static final String KEY_SIM_SPECIFIC_SETTINGS_DATA = "KEY_SIM_SPECIFIC_SETTINGS_DATA"; + private static final int MAX_CACHE_SIZE = 4; private static class VoidPropertyInvalidatedCache<T> @@ -372,6 +389,28 @@ public class SubscriptionManager { public static final Uri WFC_ROAMING_ENABLED_CONTENT_URI = Uri.withAppendedPath( CONTENT_URI, "wfc_roaming_enabled"); + + /** + * A content {@link uri} used to call the appropriate backup or restore method for sim-specific + * settings + * <p> + * See {@link #GET_SIM_SPECIFIC_SETTINGS_METHOD_NAME} and {@link + * #RESTORE_SIM_SPECIFIC_SETTINGS_METHOD_NAME} for information on what method to call. + * @hide + */ + @NonNull + public static final Uri SIM_INFO_BACKUP_AND_RESTORE_CONTENT_URI = Uri.withAppendedPath( + CONTENT_URI, "backup_and_restore"); + + /** + * A content {@link uri} used to notify contentobservers listening to siminfo restore during + * SuW. + * @hide + */ + @NonNull + public static final Uri SIM_INFO_SUW_RESTORE_CONTENT_URI = Uri.withAppendedPath( + SIM_INFO_BACKUP_AND_RESTORE_CONTENT_URI, "suw_restore"); + /** * A content {@link Uri} used to receive updates on cross sim enabled user setting. * <p> @@ -3455,4 +3494,71 @@ public class SubscriptionManager { sSlotIndexCache.clear(); sPhoneIdCache.clear(); } + + /** + * Called to retrieve SIM-specific settings data to be backed up. + * + * @return data in byte[] to be backed up. + * + * @hide + */ + @NonNull + @SystemApi + @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public byte[] getAllSimSpecificSettingsForBackup() { + Bundle bundle = mContext.getContentResolver().call( + SIM_INFO_BACKUP_AND_RESTORE_CONTENT_URI, + GET_SIM_SPECIFIC_SETTINGS_METHOD_NAME, null, null); + return bundle.getByteArray(SubscriptionManager.KEY_SIM_SPECIFIC_SETTINGS_DATA); + } + + /** + * Called to attempt to restore the backed up sim-specific configs to device for specific sim. + * This will try to restore the data that was stored internally when {@link + * #restoreAllSimSpecificSettingsFromBackup(byte[] data)} was called during setup wizard. + * End result is SimInfoDB is modified to match any backed up configs for the requested + * inserted sim. + * + * <p> + * The {@link Uri} {@link #SIM_INFO_BACKUP_AND_RESTORE_CONTENT_URI} is notified if any SimInfoDB + * entry is updated as the result of this method call. + * + * @param iccId of the sim that a restore is requested for. + * + * @hide + */ + @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) + public void restoreSimSpecificSettingsForIccIdFromBackup(@NonNull String iccId) { + mContext.getContentResolver().call( + SIM_INFO_BACKUP_AND_RESTORE_CONTENT_URI, + RESTORE_SIM_SPECIFIC_SETTINGS_METHOD_NAME, + iccId, null); + } + + /** + * Called during setup wizard restore flow to attempt to restore the backed up sim-specific + * configs to device for all existing SIMs in SimInfoDB. Internally, it will store the backup + * data in an internal file. This file will persist on device for device's lifetime and will be + * used later on when a SIM is inserted to restore that specific SIM's settings by calling + * {@link #restoreSimSpecificSettingsForIccIdFromBackup(String iccId)}. End result is + * SimInfoDB is modified to match any backed up configs for the appropriate inserted SIMs. + * + * <p> + * The {@link Uri} {@link #SIM_INFO_BACKUP_AND_RESTORE_CONTENT_URI} is notified if any SimInfoDB + * entry is updated as the result of this method call. + * + * @param data with the sim specific configs to be backed up. + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) + public void restoreAllSimSpecificSettingsFromBackup(@NonNull byte[] data) { + Bundle bundle = new Bundle(); + bundle.putByteArray(KEY_SIM_SPECIFIC_SETTINGS_DATA, data); + mContext.getContentResolver().call( + SIM_INFO_BACKUP_AND_RESTORE_CONTENT_URI, + RESTORE_SIM_SPECIFIC_SETTINGS_METHOD_NAME, + null, bundle); + } } diff --git a/telephony/java/android/telephony/ims/stub/ImsEcbmImplBase.java b/telephony/java/android/telephony/ims/stub/ImsEcbmImplBase.java index 2e35d27614d1..5f8e93d02a00 100644 --- a/telephony/java/android/telephony/ims/stub/ImsEcbmImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsEcbmImplBase.java @@ -44,8 +44,12 @@ public class ImsEcbmImplBase { @Override public void setListener(IImsEcbmListener listener) { synchronized (mLock) { - if (mImsEcbm != null && listener != null && Objects.equals( - mImsEcbm.asBinder(), listener.asBinder())) { + if (mListener != null && !mListener.asBinder().isBinderAlive()) { + Log.w(TAG, "setListener: discarding dead Binder"); + mListener = null; + } + if (mListener != null && listener != null && Objects.equals( + mListener.asBinder(), listener.asBinder())) { return; } if (listener == null) { diff --git a/telephony/java/android/telephony/ims/stub/ImsMultiEndpointImplBase.java b/telephony/java/android/telephony/ims/stub/ImsMultiEndpointImplBase.java index 555a47eb8200..8e961acc7b36 100644 --- a/telephony/java/android/telephony/ims/stub/ImsMultiEndpointImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsMultiEndpointImplBase.java @@ -48,6 +48,10 @@ public class ImsMultiEndpointImplBase { @Override public void setListener(IImsExternalCallStateListener listener) throws RemoteException { synchronized (mLock) { + if (mListener != null && !mListener.asBinder().isBinderAlive()) { + Log.w(TAG, "setListener: discarding dead Binder"); + mListener = null; + } if (mListener != null && listener != null && Objects.equals( mListener.asBinder(), listener.asBinder())) { return; diff --git a/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java b/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java index eef4fcaceeaf..83b89aa8e814 100644 --- a/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java @@ -23,6 +23,7 @@ import android.annotation.SystemApi; import android.os.Bundle; import android.os.RemoteException; import android.telephony.ims.ImsUtListener; +import android.util.Log; import com.android.ims.internal.IImsUt; import com.android.ims.internal.IImsUtListener; @@ -41,6 +42,7 @@ import java.util.Objects; // will break other implementations of ImsUt maintained by other ImsServices. @SystemApi public class ImsUtImplBase { + private static final String TAG = "ImsUtImplBase"; /** * Bar all incoming calls. (See 3GPP TS 24.611) * @hide @@ -207,6 +209,11 @@ public class ImsUtImplBase { @Override public void setListener(IImsUtListener listener) throws RemoteException { synchronized (mLock) { + if (mUtListener != null + && !mUtListener.getListenerInterface().asBinder().isBinderAlive()) { + Log.w(TAG, "setListener: discarding dead Binder"); + mUtListener = null; + } if (mUtListener != null && listener != null && Objects.equals( mUtListener.getListenerInterface().asBinder(), listener.asBinder())) { return; diff --git a/tests/UpdatableSystemFontTest/TEST_MAPPING b/tests/UpdatableSystemFontTest/TEST_MAPPING new file mode 100644 index 000000000000..a5c447934f43 --- /dev/null +++ b/tests/UpdatableSystemFontTest/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "postsubmit": [ + { + "name": "UpdatableSystemFontTest" + } + ] +} diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index c7554f6b79b6..0674138044ff 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -1262,22 +1262,28 @@ public class ConnectivityServiceTest { } } - private void updateUidNetworkingBlocked() { - doAnswer(i -> NetworkPolicyManagerInternal.isUidNetworkingBlocked( - i.getArgument(0) /* uid */, mUidRules, i.getArgument(1) /* metered */, - mRestrictBackground) + private void mockUidNetworkingBlocked() { + doAnswer(i -> mContext.getSystemService(NetworkPolicyManager.class) + .checkUidNetworkingBlocked(i.getArgument(0) /* uid */, mUidRules, + i.getArgument(1) /* metered */, mRestrictBackground) ).when(mNetworkPolicyManager).isUidNetworkingBlocked(anyInt(), anyBoolean()); + + doAnswer(inv -> mContext.getSystemService(NetworkPolicyManager.class) + .checkUidNetworkingBlocked(inv.getArgument(0) /* uid */, + inv.getArgument(1) /* uidRules */, + inv.getArgument(2) /* isNetworkMetered */, + inv.getArgument(3) /* isBackgroundRestricted */) + ).when(mNetworkPolicyManager).checkUidNetworkingBlocked( + anyInt(), anyInt(), anyBoolean(), anyBoolean()); } private void setUidRulesChanged(int uidRules) throws RemoteException { mUidRules = uidRules; - updateUidNetworkingBlocked(); mPolicyListener.onUidRulesChanged(Process.myUid(), mUidRules); } private void setRestrictBackgroundChanged(boolean restrictBackground) throws RemoteException { mRestrictBackground = restrictBackground; - updateUidNetworkingBlocked(); mPolicyListener.onRestrictBackgroundChanged(mRestrictBackground); } @@ -6809,6 +6815,7 @@ public class ConnectivityServiceTest { .addTransportType(TRANSPORT_CELLULAR) .build(); mCm.registerNetworkCallback(cellRequest, cellNetworkCallback); + mockUidNetworkingBlocked(); mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); @@ -6891,6 +6898,7 @@ public class ConnectivityServiceTest { public void testNetworkBlockedStatusBeforeAndAfterConnect() throws Exception { final TestNetworkCallback defaultCallback = new TestNetworkCallback(); mCm.registerDefaultNetworkCallback(defaultCallback); + mockUidNetworkingBlocked(); // No Networkcallbacks invoked before any network is active. setUidRulesChanged(RULE_REJECT_ALL); @@ -7160,6 +7168,13 @@ public class ConnectivityServiceTest { when(mKeyStore.get(Credentials.VPN + profileName)).thenReturn(encodedProfile); } + private void establishLegacyLockdownVpn() throws Exception { + // The legacy lockdown VPN only supports userId 0. + final Set<UidRange> ranges = Collections.singleton(UidRange.createForUser(PRIMARY_USER)); + mMockVpn.registerAgent(ranges); + mMockVpn.connect(true); + } + @Test public void testLegacyLockdownVpn() throws Exception { mServiceContext.setPermission( @@ -7254,22 +7269,30 @@ public class ConnectivityServiceTest { mMockVpn.expectStartLegacyVpnRunner(); b1 = expectConnectivityAction(TYPE_VPN, DetailedState.CONNECTED); ExpectedBroadcast b2 = expectConnectivityAction(TYPE_MOBILE, DetailedState.CONNECTED); - mMockVpn.establishForMyUid(); + establishLegacyLockdownVpn(); callback.expectAvailableThenValidatedCallbacks(mMockVpn); defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn); + NetworkCapabilities vpnNc = mCm.getNetworkCapabilities(mMockVpn.getNetwork()); b1.expectBroadcast(); b2.expectBroadcast(); assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); assertNetworkInfo(TYPE_WIFI, DetailedState.DISCONNECTED); assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED); + assertTrue(vpnNc.hasTransport(TRANSPORT_VPN)); + assertTrue(vpnNc.hasTransport(TRANSPORT_CELLULAR)); + assertFalse(vpnNc.hasTransport(TRANSPORT_WIFI)); + assertFalse(vpnNc.hasCapability(NET_CAPABILITY_NOT_METERED)); // Switch default network from cell to wifi. Expect VPN to disconnect and reconnect. final LinkProperties wifiLp = new LinkProperties(); wifiLp.setInterfaceName("wlan0"); wifiLp.addLinkAddress(new LinkAddress("192.0.2.163/25")); wifiLp.addRoute(new RouteInfo(new IpPrefix("0.0.0.0/0"), null, "wlan0")); - mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp); + final NetworkCapabilities wifiNc = new NetworkCapabilities(); + wifiNc.addTransportType(TRANSPORT_WIFI); + wifiNc.addCapability(NET_CAPABILITY_NOT_METERED); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp, wifiNc); b1 = expectConnectivityAction(TYPE_MOBILE, DetailedState.DISCONNECTED); // Wifi is CONNECTING because the VPN isn't up yet. @@ -7302,16 +7325,20 @@ public class ConnectivityServiceTest { // The VPN comes up again on wifi. b1 = expectConnectivityAction(TYPE_VPN, DetailedState.CONNECTED); b2 = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED); - mMockVpn.establishForMyUid(); + establishLegacyLockdownVpn(); callback.expectAvailableThenValidatedCallbacks(mMockVpn); defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn); b1.expectBroadcast(); b2.expectBroadcast(); - assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED); assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED); + vpnNc = mCm.getNetworkCapabilities(mMockVpn.getNetwork()); + assertTrue(vpnNc.hasTransport(TRANSPORT_VPN)); + assertTrue(vpnNc.hasTransport(TRANSPORT_WIFI)); + assertFalse(vpnNc.hasTransport(TRANSPORT_CELLULAR)); + assertTrue(vpnNc.hasCapability(NET_CAPABILITY_NOT_METERED)); // Disconnect cell. Nothing much happens since it's not the default network. // Whenever LockdownVpnTracker is connected, it will send a connected broadcast any time any diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java index f4782829cff7..32c6a75bd904 100644 --- a/tests/net/java/com/android/server/connectivity/VpnTest.java +++ b/tests/net/java/com/android/server/connectivity/VpnTest.java @@ -49,6 +49,7 @@ import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.AppOpsManager; import android.app.NotificationManager; +import android.app.PendingIntent; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -119,6 +120,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; @@ -213,6 +215,8 @@ public class VpnTest { when(mContext.getPackageName()).thenReturn(TEST_VPN_PKG); when(mContext.getOpPackageName()).thenReturn(TEST_VPN_PKG); + when(mContext.getSystemServiceName(UserManager.class)) + .thenReturn(Context.USER_SERVICE); when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager); when(mContext.getSystemService(eq(Context.APP_OPS_SERVICE))).thenReturn(mAppOps); when(mContext.getSystemServiceName(NotificationManager.class)) @@ -253,12 +257,14 @@ public class VpnTest { @Test public void testRestrictedProfilesAreAddedToVpn() { - if (true) return; // TODO(b/175883995): Test disabled until updated for new UserManager API. setMockedUsers(primaryUser, secondaryUser, restrictedProfileA, restrictedProfileB); final Vpn vpn = createVpn(primaryUser.id); - final Set<UidRange> ranges = vpn.createUserAndRestrictedProfilesRanges(primaryUser.id, - null, null); + + // Assume the user can have restricted profiles. + doReturn(true).when(mUserManager).canHaveRestrictedProfile(); + final Set<UidRange> ranges = + vpn.createUserAndRestrictedProfilesRanges(primaryUser.id, null, null); assertEquals(new ArraySet<>(Arrays.asList(new UidRange[] { PRI_USER_RANGE, UidRange.createForUser(restrictedProfileA.id) @@ -267,7 +273,6 @@ public class VpnTest { @Test public void testManagedProfilesAreNotAddedToVpn() { - if (true) return; // TODO(b/175883995): Test disabled until updated for new UserManager API. setMockedUsers(primaryUser, managedProfileA); final Vpn vpn = createVpn(primaryUser.id); @@ -290,7 +295,6 @@ public class VpnTest { @Test public void testUidAllowAndDenylist() throws Exception { - if (true) return; // TODO(b/175883995): Test disabled until updated for new UserManager API. final Vpn vpn = createVpn(primaryUser.id); final UidRange user = PRI_USER_RANGE; final String[] packages = {PKGS[0], PKGS[1], PKGS[2]}; @@ -316,7 +320,6 @@ public class VpnTest { @Test public void testGetAlwaysAndOnGetLockDown() throws Exception { - if (true) return; // TODO(b/175883995): Test disabled until updated for new UserManager API. final Vpn vpn = createVpn(primaryUser.id); // Default state. @@ -341,7 +344,6 @@ public class VpnTest { @Test public void testLockdownChangingPackage() throws Exception { - if (true) return; // TODO(b/175883995): Test disabled until updated for new UserManager API. final Vpn vpn = createVpn(primaryUser.id); final UidRange user = PRI_USER_RANGE; @@ -369,7 +371,6 @@ public class VpnTest { @Test public void testLockdownAllowlist() throws Exception { - if (true) return; // TODO(b/175883995): Test disabled until updated for new UserManager API. final Vpn vpn = createVpn(primaryUser.id); final UidRange user = PRI_USER_RANGE; @@ -444,7 +445,6 @@ public class VpnTest { @Test public void testLockdownRuleRepeatability() throws Exception { - if (true) return; // TODO(b/175883995): Test disabled until updated for new UserManager API. final Vpn vpn = createVpn(primaryUser.id); final UidRangeParcel[] primaryUserRangeParcel = new UidRangeParcel[] { new UidRangeParcel(PRI_USER_RANGE.start, PRI_USER_RANGE.stop)}; @@ -477,7 +477,6 @@ public class VpnTest { @Test public void testLockdownRuleReversibility() throws Exception { - if (true) return; // TODO(b/175883995): Test disabled until updated for new UserManager API. final Vpn vpn = createVpn(primaryUser.id); final UidRangeParcel[] entireUser = { new UidRangeParcel(PRI_USER_RANGE.start, PRI_USER_RANGE.stop) @@ -954,7 +953,14 @@ public class VpnTest { } private Vpn startLegacyVpn(final Vpn vpn, final VpnProfile vpnProfile) throws Exception { - setMockedUsers(primaryUser); + // TODO(b/175883995): once these tests have been updated for the changes to the UserManager + // API, remove this ad-hoc setup code and use setMockedUsers(primaryUser) again. + // setMockedUsers(primaryUser); + final ArrayList<UserInfo> users = new ArrayList<>(); + users.add(primaryUser); + when(mUserManager.getAliveUsers()).thenReturn(users); + when(mUserManager.getUserInfo(primaryUser.id)).thenReturn(primaryUser); + when(mUserManager.canHaveRestrictedProfile()).thenReturn(false); // Dummy egress interface final LinkProperties lp = new LinkProperties(); @@ -997,14 +1003,12 @@ public class VpnTest { profile.ipsecIdentifier = "id"; profile.ipsecSecret = "secret"; profile.l2tpSecret = "l2tpsecret"; + when(mConnectivityManager.getAllNetworks()) .thenReturn(new Network[] { new Network(101) }); + when(mConnectivityManager.registerNetworkAgent(any(), any(), any(), any(), - anyInt(), any(), anyInt())).thenAnswer(invocation -> { - // The runner has registered an agent and is now ready. - legacyRunnerReady.open(); - return new Network(102); - }); + anyInt(), any(), anyInt())).thenReturn(new Network(102)); final Vpn vpn = startLegacyVpn(createVpn(primaryUser.id), profile); final TestDeps deps = (TestDeps) vpn.mDeps; try { @@ -1020,14 +1024,20 @@ public class VpnTest { "linkname", "vpn", "refuse-eap", "nodefaultroute", "usepeerdns", "idle", "1800", "mtu", "1270", "mru", "1270" }, deps.mtpdArgs.get(10, TimeUnit.SECONDS)); + // Now wait for the runner to be ready before testing for the route. - legacyRunnerReady.block(10_000); - // In this test the expected address is always v4 so /32 + ArgumentCaptor<LinkProperties> lpCaptor = ArgumentCaptor.forClass(LinkProperties.class); + verify(mConnectivityManager, timeout(10_000)).registerNetworkAgent(any(), any(), + lpCaptor.capture(), any(), anyInt(), any(), anyInt()); + + // In this test the expected address is always v4 so /32. + // Note that the interface needs to be specified because RouteInfo objects stored in + // LinkProperties objects always acquire the LinkProperties' interface. final RouteInfo expectedRoute = new RouteInfo(new IpPrefix(expectedAddr + "/32"), - RouteInfo.RTN_THROW); - assertTrue("Routes lack the expected throw route (" + expectedRoute + ") : " - + vpn.mConfig.routes, - vpn.mConfig.routes.contains(expectedRoute)); + null, EGRESS_IFACE, RouteInfo.RTN_THROW); + final List<RouteInfo> actualRoutes = lpCaptor.getValue().getRoutes(); + assertTrue("Expected throw route (" + expectedRoute + ") not found in " + actualRoutes, + actualRoutes.contains(expectedRoute)); } finally { // Now interrupt the thread, unblock the runner and clean up. vpn.mVpnRunner.exitVpnRunner(); @@ -1083,6 +1093,11 @@ public class VpnTest { } @Override + public PendingIntent getIntentForStatusPanel(Context context) { + return null; + } + + @Override public void sendArgumentsToDaemon( final String daemon, final LocalSocket socket, final String[] arguments, final Vpn.RetryScheduler interruptChecker) throws IOException { @@ -1144,6 +1159,10 @@ public class VpnTest { doReturn(UserHandle.of(userId)).when(asUserContext).getUser(); when(mContext.createContextAsUser(eq(UserHandle.of(userId)), anyInt())) .thenReturn(asUserContext); + when(asUserContext.getSystemServiceName(UserManager.class)) + .thenReturn(Context.USER_SERVICE); + when(asUserContext.getSystemService(UserManager.class)) + .thenReturn(mUserManager); final TestLooper testLooper = new TestLooper(); final Vpn vpn = new Vpn(testLooper.getLooper(), mContext, new TestDeps(), mNetService, mNetd, userId, mKeyStore, mSystemServices, mIkev2SessionCreator); @@ -1179,11 +1198,6 @@ public class VpnTest { final int id = (int) invocation.getArguments()[0]; return userMap.get(id); }).when(mUserManager).getUserInfo(anyInt()); - - doAnswer(invocation -> { - final int id = (int) invocation.getArguments()[0]; - return (userMap.get(id).flags & UserInfo.FLAG_ADMIN) != 0; - }).when(mUserManager).canHaveRestrictedProfile(); } /** diff --git a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java index a7d0860210b4..a07bce34c737 100644 --- a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java +++ b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java @@ -46,6 +46,8 @@ public final class FrameworksTestsFilter extends SelectTest { "android.view.InsetsSourceTest", "android.view.InsetsSourceConsumerTest", "android.view.InsetsStateTest", + "android.view.RoundedCornerTest", + "android.view.RoundedCornersTest", "android.view.WindowMetricsTest", "android.view.PendingInsetsControllerTest", "android.app.WindowContextTest", diff --git a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java index 86a15912b6b4..3e659d0bc128 100644 --- a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java +++ b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java @@ -59,12 +59,17 @@ public class VcnGatewayConnectionConfigTest { // Public for use in VcnGatewayConnectionTest public static VcnGatewayConnectionConfig buildTestConfig() { + return buildTestConfigWithExposedCaps(EXPOSED_CAPS); + } + + // Public for use in VcnGatewayConnectionTest + public static VcnGatewayConnectionConfig buildTestConfigWithExposedCaps(int... exposedCaps) { final VcnGatewayConnectionConfig.Builder builder = new VcnGatewayConnectionConfig.Builder() .setRetryInterval(RETRY_INTERVALS_MS) .setMaxMtu(MAX_MTU); - for (int caps : EXPOSED_CAPS) { + for (int caps : exposedCaps) { builder.addExposedCapability(caps); } diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java index e32e1e831f83..485964487fda 100644 --- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java +++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java @@ -66,6 +66,7 @@ import android.telephony.TelephonyManager; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.server.VcnManagementService.VcnSafemodeCallback; import com.android.server.vcn.TelephonySubscriptionTracker; import com.android.server.vcn.Vcn; import com.android.server.vcn.VcnContext; @@ -142,6 +143,9 @@ public class VcnManagementServiceTest { private final TelephonySubscriptionTracker mSubscriptionTracker = mock(TelephonySubscriptionTracker.class); + private final ArgumentCaptor<VcnSafemodeCallback> mSafemodeCallbackCaptor = + ArgumentCaptor.forClass(VcnSafemodeCallback.class); + private final VcnManagementService mVcnMgmtSvc; private final IVcnUnderlyingNetworkPolicyListener mMockPolicyListener = @@ -184,7 +188,7 @@ public class VcnManagementServiceTest { doAnswer((invocation) -> { // Mock-within a doAnswer is safe, because it doesn't actually run nested. return mock(Vcn.class); - }).when(mMockDeps).newVcn(any(), any(), any(), any()); + }).when(mMockDeps).newVcn(any(), any(), any(), any(), any()); final PersistableBundle bundle = PersistableBundleUtils.fromMap( @@ -307,7 +311,7 @@ public class VcnManagementServiceTest { TelephonySubscriptionSnapshot snapshot = triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_1)); verify(mMockDeps) - .newVcn(eq(mVcnContext), eq(TEST_UUID_1), eq(TEST_VCN_CONFIG), eq(snapshot)); + .newVcn(eq(mVcnContext), eq(TEST_UUID_1), eq(TEST_VCN_CONFIG), eq(snapshot), any()); } @Test @@ -485,7 +489,8 @@ public class VcnManagementServiceTest { eq(mVcnContext), eq(TEST_UUID_2), eq(TEST_VCN_CONFIG), - eq(TelephonySubscriptionSnapshot.EMPTY_SNAPSHOT)); + eq(TelephonySubscriptionSnapshot.EMPTY_SNAPSHOT), + any()); // Verify Vcn is updated if it was previously started mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); @@ -634,4 +639,25 @@ public class VcnManagementServiceTest { verify(mMockPolicyListener).onPolicyChanged(); } + + @Test + public void testVcnSafemodeCallbackOnEnteredSafemode() throws Exception { + TelephonySubscriptionSnapshot snapshot = + triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_1)); + verify(mMockDeps) + .newVcn( + eq(mVcnContext), + eq(TEST_UUID_1), + eq(TEST_VCN_CONFIG), + eq(snapshot), + mSafemodeCallbackCaptor.capture()); + + mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); + + VcnSafemodeCallback safemodeCallback = mSafemodeCallbackCaptor.getValue(); + safemodeCallback.onEnteredSafemode(); + + assertFalse(mVcnMgmtSvc.getAllVcns().get(TEST_UUID_1).isActive()); + verify(mMockPolicyListener).onPolicyChanged(); + } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java index fbaae6f534a9..8643d8a2ea8a 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java @@ -45,7 +45,12 @@ public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnect public void testEnterWhileNotRunningTriggersQuit() throws Exception { final VcnGatewayConnection vgc = new VcnGatewayConnection( - mVcnContext, TEST_SUB_GRP, TEST_SUBSCRIPTION_SNAPSHOT, mConfig, mDeps); + mVcnContext, + TEST_SUB_GRP, + TEST_SUBSCRIPTION_SNAPSHOT, + mConfig, + mGatewayStatusCallback, + mDeps); vgc.setIsRunning(false); vgc.transitionTo(vgc.mDisconnectedState); diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java index df1341cce20f..333b5b990dde 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java @@ -43,6 +43,7 @@ import android.os.test.TestLooper; import com.android.server.IpSecService; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; +import com.android.server.vcn.Vcn.VcnGatewayStatusCallback; import org.junit.Before; import org.mockito.ArgumentCaptor; @@ -80,6 +81,7 @@ public class VcnGatewayConnectionTestBase { @NonNull protected final VcnNetworkProvider mVcnNetworkProvider; @NonNull protected final VcnContext mVcnContext; @NonNull protected final VcnGatewayConnectionConfig mConfig; + @NonNull protected final VcnGatewayStatusCallback mGatewayStatusCallback; @NonNull protected final VcnGatewayConnection.Dependencies mDeps; @NonNull protected final UnderlyingNetworkTracker mUnderlyingNetworkTracker; @@ -94,6 +96,7 @@ public class VcnGatewayConnectionTestBase { mVcnNetworkProvider = mock(VcnNetworkProvider.class); mVcnContext = mock(VcnContext.class); mConfig = VcnGatewayConnectionConfigTest.buildTestConfig(); + mGatewayStatusCallback = mock(VcnGatewayStatusCallback.class); mDeps = mock(VcnGatewayConnection.Dependencies.class); mUnderlyingNetworkTracker = mock(UnderlyingNetworkTracker.class); @@ -123,7 +126,12 @@ public class VcnGatewayConnectionTestBase { mGatewayConnection = new VcnGatewayConnection( - mVcnContext, TEST_SUB_GRP, TEST_SUBSCRIPTION_SNAPSHOT, mConfig, mDeps); + mVcnContext, + TEST_SUB_GRP, + TEST_SUBSCRIPTION_SNAPSHOT, + mConfig, + mGatewayStatusCallback, + mDeps); } protected IpSecTransform makeDummyIpSecTransform() throws Exception { diff --git a/tests/vcn/java/com/android/server/vcn/VcnTest.java b/tests/vcn/java/com/android/server/vcn/VcnTest.java index 0c1df763a08e..66cbf84619ab 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnTest.java @@ -16,22 +16,27 @@ package com.android.server.vcn; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.content.Context; import android.net.NetworkRequest; import android.net.vcn.VcnConfig; +import android.net.vcn.VcnGatewayConnectionConfig; import android.net.vcn.VcnGatewayConnectionConfigTest; import android.os.ParcelUuid; import android.os.test.TestLooper; +import com.android.server.VcnManagementService.VcnSafemodeCallback; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; +import com.android.server.vcn.Vcn.VcnGatewayStatusCallback; import com.android.server.vcn.VcnNetworkProvider.NetworkRequestListener; import org.junit.Before; @@ -51,9 +56,13 @@ public class VcnTest { private VcnContext mVcnContext; private TelephonySubscriptionSnapshot mSubscriptionSnapshot; private VcnNetworkProvider mVcnNetworkProvider; + private VcnSafemodeCallback mVcnSafemodeCallback; private Vcn.Dependencies mDeps; + private ArgumentCaptor<VcnGatewayStatusCallback> mGatewayStatusCallbackCaptor; + private TestLooper mTestLooper; + private VcnGatewayConnectionConfig mGatewayConnectionConfig; private VcnConfig mConfig; private Vcn mVcn; @@ -63,6 +72,7 @@ public class VcnTest { mVcnContext = mock(VcnContext.class); mSubscriptionSnapshot = mock(TelephonySubscriptionSnapshot.class); mVcnNetworkProvider = mock(VcnNetworkProvider.class); + mVcnSafemodeCallback = mock(VcnSafemodeCallback.class); mDeps = mock(Vcn.Dependencies.class); mTestLooper = new TestLooper(); @@ -76,15 +86,26 @@ public class VcnTest { doAnswer((invocation) -> { // Mock-within a doAnswer is safe, because it doesn't actually run nested. return mock(VcnGatewayConnection.class); - }).when(mDeps).newVcnGatewayConnection(any(), any(), any(), any()); + }).when(mDeps).newVcnGatewayConnection(any(), any(), any(), any(), any()); - mConfig = - new VcnConfig.Builder(mContext) - .addGatewayConnectionConfig( - VcnGatewayConnectionConfigTest.buildTestConfig()) - .build(); + mGatewayStatusCallbackCaptor = ArgumentCaptor.forClass(VcnGatewayStatusCallback.class); - mVcn = new Vcn(mVcnContext, TEST_SUB_GROUP, mConfig, mSubscriptionSnapshot, mDeps); + final VcnConfig.Builder configBuilder = new VcnConfig.Builder(mContext); + for (final int capability : VcnGatewayConnectionConfigTest.EXPOSED_CAPS) { + configBuilder.addGatewayConnectionConfig( + VcnGatewayConnectionConfigTest.buildTestConfigWithExposedCaps(capability)); + } + configBuilder.addGatewayConnectionConfig(VcnGatewayConnectionConfigTest.buildTestConfig()); + mConfig = configBuilder.build(); + + mVcn = + new Vcn( + mVcnContext, + TEST_SUB_GROUP, + mConfig, + mSubscriptionSnapshot, + mVcnSafemodeCallback, + mDeps); } private NetworkRequestListener verifyAndGetRequestListener() { @@ -95,23 +116,22 @@ public class VcnTest { return mNetworkRequestListenerCaptor.getValue(); } - private NetworkRequest getNetworkRequestWithCapabilities(int[] networkCapabilities) { - final NetworkRequest.Builder builder = new NetworkRequest.Builder(); - for (final int netCapability : networkCapabilities) { - builder.addCapability(netCapability); + private void startVcnGatewayWithCapabilities( + NetworkRequestListener requestListener, int... netCapabilities) { + final NetworkRequest.Builder requestBuilder = new NetworkRequest.Builder(); + for (final int netCapability : netCapabilities) { + requestBuilder.addCapability(netCapability); } - return builder.build(); + + requestListener.onNetworkRequested(requestBuilder.build(), NETWORK_SCORE, PROVIDER_ID); + mTestLooper.dispatchAll(); } @Test public void testSubscriptionSnapshotUpdatesVcnGatewayConnections() { final NetworkRequestListener requestListener = verifyAndGetRequestListener(); - - requestListener.onNetworkRequested( - getNetworkRequestWithCapabilities(VcnGatewayConnectionConfigTest.EXPOSED_CAPS), - NETWORK_SCORE, - PROVIDER_ID); - mTestLooper.dispatchAll(); + startVcnGatewayWithCapabilities( + requestListener, VcnGatewayConnectionConfigTest.EXPOSED_CAPS); final Set<VcnGatewayConnection> gatewayConnections = mVcn.getVcnGatewayConnections(); assertFalse(gatewayConnections.isEmpty()); @@ -126,4 +146,38 @@ public class VcnTest { verify(gateway).updateSubscriptionSnapshot(eq(updatedSnapshot)); } } + + @Test + public void testGatewayEnteringSafemodeNotifiesVcn() { + final NetworkRequestListener requestListener = verifyAndGetRequestListener(); + for (final int capability : VcnGatewayConnectionConfigTest.EXPOSED_CAPS) { + startVcnGatewayWithCapabilities(requestListener, capability); + } + + // Each Capability in EXPOSED_CAPS was split into a separate VcnGatewayConnection in #setUp. + // Expect one VcnGatewayConnection per capability. + final int numExpectedGateways = VcnGatewayConnectionConfigTest.EXPOSED_CAPS.length; + + final Set<VcnGatewayConnection> gatewayConnections = mVcn.getVcnGatewayConnections(); + assertEquals(numExpectedGateways, gatewayConnections.size()); + verify(mDeps, times(numExpectedGateways)) + .newVcnGatewayConnection( + eq(mVcnContext), + eq(TEST_SUB_GROUP), + eq(mSubscriptionSnapshot), + any(), + mGatewayStatusCallbackCaptor.capture()); + + // Doesn't matter which callback this gets - any Gateway entering Safemode should shut down + // all Gateways + final VcnGatewayStatusCallback statusCallback = mGatewayStatusCallbackCaptor.getValue(); + statusCallback.onEnteredSafemode(); + mTestLooper.dispatchAll(); + + for (final VcnGatewayConnection gatewayConnection : gatewayConnections) { + verify(gatewayConnection).teardownAsynchronously(); + } + verify(mVcnNetworkProvider).unregisterListener(requestListener); + verify(mVcnSafemodeCallback).onEnteredSafemode(); + } } |