diff options
244 files changed, 7604 insertions, 3139 deletions
diff --git a/Android.bp b/Android.bp index 202e548d56c8..9655daf649c7 100644 --- a/Android.bp +++ b/Android.bp @@ -536,7 +536,7 @@ java_library { "android.hardware.vibrator-V1.3-java", "android.security.apc-java", "android.security.authorization-java", - "android.system.keystore2-java", + "android.system.keystore2-V1-java", "android.system.suspend.control.internal-java", "cameraprotosnano", "devicepolicyprotosnano", @@ -760,6 +760,7 @@ gensrcs { srcs: [ ":ipconnectivity-proto-src", ":libstats_atom_enum_protos", + ":libtombstone_proto-src", "core/proto/**/*.proto", "libs/incident/**/*.proto", ":service-permission-protos", 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/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java index e1a859648ee6..aefeab621778 100644 --- a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java +++ b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java @@ -36,30 +36,46 @@ import java.util.Map; import java.util.Set; /** - * ApplicationMediaCapabilities is an immutable class that encapsulates an application's - * capabilities for handling newer video codec format and media features. - * - * The ApplicationMediaCapabilities class is used by the platform to represent an application's - * media capabilities as defined in their manifest(TODO: Add link) in order to determine - * whether modern media files need to be transcoded for that application (TODO: Add link). - * - * ApplicationMediaCapabilities objects can also be built by applications at runtime for use with - * {@link ContentResolver#openTypedAssetFileDescriptor(Uri, String, Bundle)} to provide more - * control over the transcoding that is built into the platform. ApplicationMediaCapabilities - * provided by applications at runtime like this override the default manifest capabilities for that - * media access. - * - * <h3> Video Codec Support</h3> - * Newer video codes include HEVC, VP9 and AV1. Application only needs to indicate their support - * for newer format with this class as they are assumed to support older format like h.264. - * - * <h4>Capability of handling HDR(high dynamic range) video</h4> - * There are four types of HDR video(Dolby-Vision, HDR10, HDR10+, HLG) supported by the platform, - * application will only need to specify individual types they supported. + ApplicationMediaCapabilities is an immutable class that encapsulates an application's capabilities + for handling newer video codec format and media features. + + <p> + Android 12 introduces seamless media transcoding feature. By default, Android assumes apps can + support playback of all media formats. Apps that would like to request that media be transcoded + into a more compatible format should declare their media capabilities in a media_capabilities + .xml resource file and add it as a property tag in the AndroidManifest.xml file. Here is a example: + <pre> + {@code + <media-capabilities xmlns:android="http://schemas.android.com/apk/res/android"> + <format android:name="HEVC" supported="true"/> + <format android:name="HDR10" supported="false"/> + <format android:name="HDR10Plus" supported="false"/> + </media-capabilities> + } + </pre> + The ApplicationMediaCapabilities class is generated from this xml and used by the platform to + represent an application's media capabilities in order to determine whether modern media files need + to be transcoded for that application. + </p> + + <p> + ApplicationMediaCapabilities objects can also be built by applications at runtime for use with + {@link ContentResolver#openTypedAssetFileDescriptor(Uri, String, Bundle)} to provide more + control over the transcoding that is built into the platform. ApplicationMediaCapabilities + provided by applications at runtime like this override the default manifest capabilities for that + media access.The object could be build either through {@link #createFromXml(XmlPullParser)} or + through the builder class {@link ApplicationMediaCapabilities.Builder} + + <h3> Video Codec Support</h3> + <p> + Newer video codes include HEVC, VP9 and AV1. Application only needs to indicate their support + for newer format with this class as they are assumed to support older format like h.264. + + <h3>Capability of handling HDR(high dynamic range) video</h3> + <p> + There are four types of HDR video(Dolby-Vision, HDR10, HDR10+, HLG) supported by the platform, + application will only need to specify individual types they supported. */ -// TODO(huang): Correct openTypedAssetFileDescriptor with the new API after it is added. -// TODO(hkuang): Add a link to seamless transcoding detail when it is published -// TODO(hkuang): Add code sample on how to build a capability object with MediaCodecList public final class ApplicationMediaCapabilities implements Parcelable { private static final String TAG = "ApplicationMediaCapabilities"; @@ -105,9 +121,9 @@ public final class ApplicationMediaCapabilities implements Parcelable { */ public boolean isVideoMimeTypeSupported( @NonNull String videoMime) throws FormatNotFoundException { - if (mUnsupportedVideoMimeTypes.contains(videoMime)) { + if (mUnsupportedVideoMimeTypes.contains(videoMime.toLowerCase())) { return false; - } else if (mSupportedVideoMimeTypes.contains(videoMime)) { + } else if (mSupportedVideoMimeTypes.contains(videoMime.toLowerCase())) { return true; } else { throw new FormatNotFoundException(videoMime); @@ -262,11 +278,27 @@ public final class ApplicationMediaCapabilities implements Parcelable { /** * Creates {@link ApplicationMediaCapabilities} from an xml. + * + * The xml's syntax is the same as the media_capabilities.xml used by the AndroidManifest.xml. + * <p> Here is an example: + * + * <pre> + * {@code + * <media-capabilities xmlns:android="http://schemas.android.com/apk/res/android"> + * <format android:name="HEVC" supported="true"/> + * <format android:name="HDR10" supported="false"/> + * <format android:name="HDR10Plus" supported="false"/> + * </media-capabilities> + * } + * </pre> + * <p> + * * @param xmlParser The underlying {@link XmlPullParser} that will read the xml. * @return An ApplicationMediaCapabilities object. * @throws UnsupportedOperationException if the capabilities in xml config are invalid or * incompatible. */ + // TODO: Add developer.android.com link for the format of the xml. @NonNull public static ApplicationMediaCapabilities createFromXml(@NonNull XmlPullParser xmlParser) { ApplicationMediaCapabilities.Builder builder = new ApplicationMediaCapabilities.Builder(); @@ -430,7 +462,7 @@ public final class ApplicationMediaCapabilities implements Parcelable { mIsSlowMotionSupported = isSupported; break; default: - throw new UnsupportedOperationException("Invalid format name " + name); + Log.w(TAG, "Invalid format name " + name); } // Save the name and isSupported into the map for validate later. mFormatSupportedMap.put(name, isSupported); diff --git a/core/api/current.txt b/core/api/current.txt index e511de780ac8..bfdfb4060078 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(); @@ -31445,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(); @@ -53345,7 +53349,7 @@ package android.widget { method public void onSelectedDayChange(@NonNull android.widget.CalendarView, int, int, int); } - public class CheckBox extends android.widget.CompoundButton { + @android.widget.RemoteViews.RemoteView public class CheckBox extends android.widget.CompoundButton { ctor public CheckBox(android.content.Context); ctor public CheckBox(android.content.Context, android.util.AttributeSet); ctor public CheckBox(android.content.Context, android.util.AttributeSet, int); @@ -53411,6 +53415,7 @@ package android.widget { method public boolean isChecked(); method public void setButtonDrawable(@DrawableRes int); method public void setButtonDrawable(@Nullable android.graphics.drawable.Drawable); + method public void setButtonIcon(@Nullable android.graphics.drawable.Icon); method public void setButtonTintBlendMode(@Nullable android.graphics.BlendMode); method public void setButtonTintList(@Nullable android.content.res.ColorStateList); method public void setButtonTintMode(@Nullable android.graphics.PorterDuff.Mode); @@ -54451,14 +54456,14 @@ package android.widget { field protected String[] mExcludeMimes; } - public class RadioButton extends android.widget.CompoundButton { + @android.widget.RemoteViews.RemoteView public class RadioButton extends android.widget.CompoundButton { ctor public RadioButton(android.content.Context); ctor public RadioButton(android.content.Context, android.util.AttributeSet); ctor public RadioButton(android.content.Context, android.util.AttributeSet, int); ctor public RadioButton(android.content.Context, android.util.AttributeSet, int, int); } - public class RadioGroup extends android.widget.LinearLayout { + @android.widget.RemoteViews.RemoteView public class RadioGroup extends android.widget.LinearLayout { ctor public RadioGroup(android.content.Context); ctor public RadioGroup(android.content.Context, android.util.AttributeSet); method public void check(@IdRes int); @@ -54580,6 +54585,7 @@ package android.widget { method public void setChronometerCountDown(@IdRes int, boolean); method public void setColor(@IdRes int, @NonNull String, @ColorRes int); method public void setColorStateList(@IdRes int, @NonNull String, @ColorRes int); + method public void setCompoundButtonChecked(@IdRes int, boolean); method public void setContentDescription(@IdRes int, CharSequence); method public void setDisplayedChild(@IdRes int, int); method public void setDouble(@IdRes int, String, double); @@ -54604,6 +54610,7 @@ package android.widget { method public void setOnClickResponse(@IdRes int, @NonNull android.widget.RemoteViews.RemoteResponse); method public void setPendingIntentTemplate(@IdRes int, android.app.PendingIntent); method public void setProgressBar(@IdRes int, int, int, boolean); + method public void setRadioGroupChecked(@IdRes int, @IdRes int); method public void setRelativeScrollPosition(@IdRes int, int); method @Deprecated public void setRemoteAdapter(int, @IdRes int, android.content.Intent); method public void setRemoteAdapter(@IdRes int, android.content.Intent); @@ -54970,7 +54977,7 @@ package android.widget { ctor public StackView(android.content.Context, android.util.AttributeSet, int, int); } - public class Switch extends android.widget.CompoundButton { + @android.widget.RemoteViews.RemoteView public class Switch extends android.widget.CompoundButton { ctor public Switch(android.content.Context); ctor public Switch(android.content.Context, android.util.AttributeSet); ctor public Switch(android.content.Context, android.util.AttributeSet, int); @@ -55001,12 +55008,14 @@ package android.widget { method public void setTextOff(CharSequence); method public void setTextOn(CharSequence); method public void setThumbDrawable(android.graphics.drawable.Drawable); + method public void setThumbIcon(@Nullable android.graphics.drawable.Icon); method public void setThumbResource(@DrawableRes int); method public void setThumbTextPadding(int); method public void setThumbTintBlendMode(@Nullable android.graphics.BlendMode); method public void setThumbTintList(@Nullable android.content.res.ColorStateList); method public void setThumbTintMode(@Nullable android.graphics.PorterDuff.Mode); method public void setTrackDrawable(android.graphics.drawable.Drawable); + method public void setTrackIcon(@Nullable android.graphics.drawable.Icon); method public void setTrackResource(@DrawableRes int); method public void setTrackTintBlendMode(@Nullable android.graphics.BlendMode); method public void setTrackTintList(@Nullable android.content.res.ColorStateList); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 1977babe0bfb..c91b50b55c51 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -290,6 +290,7 @@ package android { public static final class R.attr { field public static final int allowClearUserDataOnFailedRestore = 16844288; // 0x1010600 + field public static final int hotwordDetectionService = 16844326; // 0x1010626 field public static final int isVrOnly = 16844152; // 0x1010578 field public static final int minExtensionVersion = 16844305; // 0x1010611 field public static final int requiredSystemPropertyName = 16844133; // 0x1010565 @@ -9509,6 +9510,11 @@ package android.security.keystore { method @Deprecated @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUid(int); } + public abstract class KeyProperties { + field public static final int NAMESPACE_APPLICATION = -1; // 0xffffffff + field public static final int NAMESPACE_WIFI = 102; // 0x66 + } + } package android.security.keystore.recovery { @@ -10331,7 +10337,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 +11710,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 +11718,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..7674bfd8827d 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -12,6 +12,7 @@ package android { field public static final String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA"; field public static final String CONFIGURE_DISPLAY_BRIGHTNESS = "android.permission.CONFIGURE_DISPLAY_BRIGHTNESS"; field public static final String CONTROL_DEVICE_LIGHTS = "android.permission.CONTROL_DEVICE_LIGHTS"; + field public static final String CONTROL_DEVICE_STATE = "android.permission.CONTROL_DEVICE_STATE"; field public static final String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES"; field @Deprecated public static final String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS"; field public static final String MANAGE_ACTIVITY_TASKS = "android.permission.MANAGE_ACTIVITY_TASKS"; @@ -28,6 +29,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 +121,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 +393,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 @@ -899,6 +901,40 @@ package android.hardware.camera2 { } +package android.hardware.devicestate { + + public final class DeviceStateManager { + method public void addDeviceStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateListener); + method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void cancelRequest(@NonNull android.hardware.devicestate.DeviceStateRequest); + method @NonNull public int[] getSupportedStates(); + method public void removeDeviceStateListener(@NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateListener); + method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void requestState(@NonNull android.hardware.devicestate.DeviceStateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.devicestate.DeviceStateRequest.Callback); + } + + public static interface DeviceStateManager.DeviceStateListener { + method public void onDeviceStateChanged(int); + } + + public final class DeviceStateRequest { + method public int getFlags(); + method public int getState(); + method @NonNull public static android.hardware.devicestate.DeviceStateRequest.Builder newBuilder(int); + field public static final int FLAG_CANCEL_WHEN_BASE_CHANGES = 1; // 0x1 + } + + public static final class DeviceStateRequest.Builder { + method @NonNull public android.hardware.devicestate.DeviceStateRequest build(); + method @NonNull public android.hardware.devicestate.DeviceStateRequest.Builder setFlags(int); + } + + public static interface DeviceStateRequest.Callback { + method public default void onRequestActivated(@NonNull android.hardware.devicestate.DeviceStateRequest); + method public default void onRequestCanceled(@NonNull android.hardware.devicestate.DeviceStateRequest); + method public default void onRequestSuspended(@NonNull android.hardware.devicestate.DeviceStateRequest); + } + +} + package android.hardware.display { public class AmbientDisplayConfiguration { 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/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/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/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index 62925757cde1..0c0e4020d04a 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -450,6 +450,7 @@ public class LauncherApps { FLAG_MATCH_PINNED, FLAG_MATCH_MANIFEST, FLAG_MATCH_CACHED, + FLAG_MATCH_PINNED_BY_ANY_LAUNCHER, FLAG_GET_KEY_FIELDS_ONLY, FLAG_GET_PERSONS_DATA, }) 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/content/pm/parsing/component/ParsedPermissionUtils.java b/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java index cbd2c550a797..9012b5ce2b1e 100644 --- a/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java +++ b/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java @@ -108,17 +108,14 @@ public class ParsedPermissionUtils { permission.protectionLevel = PermissionInfo.fixProtectionLevel(permission.protectionLevel); - if (permission.getProtectionFlags() != 0) { - if ((permission.protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTANT) == 0 - && (permission.protectionLevel & PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY) - == 0 - && (permission.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE) - != PermissionInfo.PROTECTION_SIGNATURE - && (permission.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE) - != PermissionInfo.PROTECTION_INTERNAL) { - return input.error("<permission> protectionLevel specifies a non-instant flag " - + "but is not based on signature or internal type"); - } + final int otherProtectionFlags = permission.getProtectionFlags() + & ~(PermissionInfo.PROTECTION_FLAG_APPOP | PermissionInfo.PROTECTION_FLAG_INSTANT + | PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY); + if (otherProtectionFlags != 0 + && permission.getProtection() != PermissionInfo.PROTECTION_SIGNATURE + && permission.getProtection() != PermissionInfo.PROTECTION_INTERNAL) { + return input.error("<permission> protectionLevel specifies a non-instant, non-appop," + + " non-runtimeOnly flag but is not based on signature or internal type"); } return ComponentParseUtils.parseAllMetaData(pkg, res, parser, tag, permission, input); diff --git a/core/java/android/hardware/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java index 29a6ee278d9c..f175e7b00b7e 100644 --- a/core/java/android/hardware/devicestate/DeviceStateManager.java +++ b/core/java/android/hardware/devicestate/DeviceStateManager.java @@ -16,8 +16,12 @@ package android.hardware.devicestate; +import android.annotation.CallbackExecutor; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.SystemService; +import android.annotation.TestApi; import android.content.Context; import java.util.concurrent.Executor; @@ -28,13 +32,19 @@ import java.util.concurrent.Executor; * * @hide */ +@TestApi @SystemService(Context.DEVICE_STATE_SERVICE) public final class DeviceStateManager { - /** Invalid device state. */ + /** + * Invalid device state. + * + * @hide + */ public static final int INVALID_DEVICE_STATE = -1; - private DeviceStateManagerGlobal mGlobal; + private final DeviceStateManagerGlobal mGlobal; + /** @hide */ public DeviceStateManager() { DeviceStateManagerGlobal global = DeviceStateManagerGlobal.getInstance(); if (global == null) { @@ -45,23 +55,73 @@ public final class DeviceStateManager { } /** + * Returns the list of device states that are supported and can be requested with + * {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}. + */ + @NonNull + public int[] getSupportedStates() { + return mGlobal.getSupportedStates(); + } + + /** + * Submits a {@link DeviceStateRequest request} to modify the device state. + * <p> + * By default, the request is kept active until a call to + * {@link #cancelRequest(DeviceStateRequest)} or until one of the following occurs: + * <ul> + * <li>Another processes submits a request succeeding this request in which case the request + * will be suspended until the interrupting request is canceled. + * <li>The requested state has become unsupported. + * <li>The process submitting the request dies. + * </ul> + * However, this behavior can be changed by setting flags on the {@link DeviceStateRequest}. + * + * @throws IllegalArgumentException if the requested state is unsupported. + * @throws SecurityException if the {@link android.Manifest.permission#CONTROL_DEVICE_STATE} + * permission is not held. + * + * @see DeviceStateRequest + */ + @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) + public void requestState(@NonNull DeviceStateRequest request, + @Nullable @CallbackExecutor Executor executor, + @Nullable DeviceStateRequest.Callback callback) { + mGlobal.requestState(request, callback, executor); + } + + /** + * Cancels a {@link DeviceStateRequest request} previously submitted with a call to + * {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}. + * <p> + * This method is noop if the {@code request} has not been submitted with a call to + * {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}. + * + * @throws SecurityException if the {@link android.Manifest.permission#CONTROL_DEVICE_STATE} + * permission is not held. + */ + @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) + public void cancelRequest(@NonNull DeviceStateRequest request) { + mGlobal.cancelRequest(request); + } + + /** * Registers a listener to receive notifications about changes in device state. * - * @param listener the listener to register. * @param executor the executor to process notifications. + * @param listener the listener to register. * * @see DeviceStateListener */ - public void registerDeviceStateListener(@NonNull DeviceStateListener listener, - @NonNull Executor executor) { + public void addDeviceStateListener(@NonNull @CallbackExecutor Executor executor, + @NonNull DeviceStateListener listener) { mGlobal.registerDeviceStateListener(listener, executor); } /** * Unregisters a listener previously registered with - * {@link #registerDeviceStateListener(DeviceStateListener, Executor)}. + * {@link #addDeviceStateListener(Executor, DeviceStateListener)}. */ - public void unregisterDeviceStateListener(@NonNull DeviceStateListener listener) { + public void removeDeviceStateListener(@NonNull DeviceStateListener listener) { mGlobal.unregisterDeviceStateListener(listener); } diff --git a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java index c8905038d056..b9ae88ea840f 100644 --- a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java +++ b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java @@ -20,9 +20,11 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.hardware.devicestate.DeviceStateManager.DeviceStateListener; +import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; +import android.util.ArrayMap; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -67,6 +69,9 @@ public final class DeviceStateManagerGlobal { @GuardedBy("mLock") private final ArrayList<DeviceStateListenerWrapper> mListeners = new ArrayList<>(); + @GuardedBy("mLock") + private final ArrayMap<IBinder, DeviceStateRequestWrapper> mRequests = new ArrayMap<>(); + @Nullable @GuardedBy("mLock") private Integer mLastReceivedState; @@ -77,9 +82,84 @@ public final class DeviceStateManagerGlobal { } /** + * Returns the set of supported device states. + * + * @see DeviceStateManager#getSupportedStates() + */ + public int[] getSupportedStates() { + try { + return mDeviceStateManager.getSupportedDeviceStates(); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + /** + * Submits a {@link DeviceStateRequest request} to modify the device state. + * + * @see DeviceStateManager#requestState(DeviceStateRequest, + * Executor, DeviceStateRequest.Callback) + * @see DeviceStateRequest + */ + public void requestState(@NonNull DeviceStateRequest request, + @Nullable DeviceStateRequest.Callback callback, @Nullable Executor executor) { + if (callback == null && executor != null) { + throw new IllegalArgumentException("Callback must be supplied with executor."); + } else if (executor == null && callback != null) { + throw new IllegalArgumentException("Executor must be supplied with callback."); + } + + synchronized (mLock) { + registerCallbackIfNeededLocked(); + + if (findRequestTokenLocked(request) != null) { + // This request has already been submitted. + return; + } + + // Add the request wrapper to the mRequests array before requesting the state as the + // callback could be triggered immediately if the mDeviceStateManager IBinder is in the + // same process as this instance. + IBinder token = new Binder(); + mRequests.put(token, new DeviceStateRequestWrapper(request, callback, executor)); + + try { + mDeviceStateManager.requestState(token, request.getState(), request.getFlags()); + } catch (RemoteException ex) { + mRequests.remove(token); + throw ex.rethrowFromSystemServer(); + } + } + } + + /** + * Cancels a {@link DeviceStateRequest request} previously submitted with a call to + * {@link #requestState(DeviceStateRequest, DeviceStateRequest.Callback, Executor)}. + * + * @see DeviceStateManager#cancelRequest(DeviceStateRequest) + */ + public void cancelRequest(@NonNull DeviceStateRequest request) { + synchronized (mLock) { + registerCallbackIfNeededLocked(); + + final IBinder token = findRequestTokenLocked(request); + if (token == null) { + // This request has not been submitted. + return; + } + + try { + mDeviceStateManager.cancelRequest(token); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + } + + /** * Registers a listener to receive notifications about changes in device state. * - * @see DeviceStateManager#registerDeviceStateListener(DeviceStateListener, Executor) + * @see DeviceStateManager#addDeviceStateListener(Executor, DeviceStateListener) */ @VisibleForTesting(visibility = Visibility.PACKAGE) public void registerDeviceStateListener(@NonNull DeviceStateListener listener, @@ -112,7 +192,7 @@ public final class DeviceStateManagerGlobal { * Unregisters a listener previously registered with * {@link #registerDeviceStateListener(DeviceStateListener, Executor)}. * - * @see DeviceStateManager#registerDeviceStateListener(DeviceStateListener, Executor) + * @see DeviceStateManager#addDeviceStateListener(Executor, DeviceStateListener) */ @VisibleForTesting(visibility = Visibility.PACKAGE) public void unregisterDeviceStateListener(DeviceStateListener listener) { @@ -144,6 +224,17 @@ public final class DeviceStateManagerGlobal { return -1; } + @Nullable + private IBinder findRequestTokenLocked(@NonNull DeviceStateRequest request) { + for (int i = 0; i < mRequests.size(); i++) { + if (mRequests.valueAt(i).mRequest.equals(request)) { + return mRequests.keyAt(i); + } + } + return null; + } + + /** Handles a call from the server that the device state has changed. */ private void handleDeviceStateChanged(int newDeviceState) { ArrayList<DeviceStateListenerWrapper> listeners; synchronized (mLock) { @@ -156,11 +247,68 @@ public final class DeviceStateManagerGlobal { } } + /** + * Handles a call from the server that a request for the supplied {@code token} has become + * active. + */ + private void handleRequestActive(IBinder token) { + DeviceStateRequestWrapper request; + synchronized (mLock) { + request = mRequests.get(token); + } + if (request != null) { + request.notifyRequestActive(); + } + } + + /** + * Handles a call from the server that a request for the supplied {@code token} has become + * suspended. + */ + private void handleRequestSuspended(IBinder token) { + DeviceStateRequestWrapper request; + synchronized (mLock) { + request = mRequests.get(token); + } + if (request != null) { + request.notifyRequestSuspended(); + } + } + + /** + * Handles a call from the server that a request for the supplied {@code token} has become + * canceled. + */ + private void handleRequestCanceled(IBinder token) { + DeviceStateRequestWrapper request; + synchronized (mLock) { + request = mRequests.remove(token); + } + if (request != null) { + request.notifyRequestCanceled(); + } + } + private final class DeviceStateManagerCallback extends IDeviceStateManagerCallback.Stub { @Override public void onDeviceStateChanged(int deviceState) { handleDeviceStateChanged(deviceState); } + + @Override + public void onRequestActive(IBinder token) { + handleRequestActive(token); + } + + @Override + public void onRequestSuspended(IBinder token) { + handleRequestSuspended(token); + } + + @Override + public void onRequestCanceled(IBinder token) { + handleRequestCanceled(token); + } } private static final class DeviceStateListenerWrapper { @@ -176,4 +324,43 @@ public final class DeviceStateManagerGlobal { mExecutor.execute(() -> mDeviceStateListener.onDeviceStateChanged(newDeviceState)); } } + + private static final class DeviceStateRequestWrapper { + private final DeviceStateRequest mRequest; + @Nullable + private final DeviceStateRequest.Callback mCallback; + @Nullable + private final Executor mExecutor; + + DeviceStateRequestWrapper(@NonNull DeviceStateRequest request, + @Nullable DeviceStateRequest.Callback callback, @Nullable Executor executor) { + mRequest = request; + mCallback = callback; + mExecutor = executor; + } + + void notifyRequestActive() { + if (mCallback == null) { + return; + } + + mExecutor.execute(() -> mCallback.onRequestActivated(mRequest)); + } + + void notifyRequestSuspended() { + if (mCallback == null) { + return; + } + + mExecutor.execute(() -> mCallback.onRequestSuspended(mRequest)); + } + + void notifyRequestCanceled() { + if (mCallback == null) { + return; + } + + mExecutor.execute(() -> mCallback.onRequestSuspended(mRequest)); + } + } } diff --git a/core/java/android/hardware/devicestate/DeviceStateRequest.java b/core/java/android/hardware/devicestate/DeviceStateRequest.java new file mode 100644 index 000000000000..70f7002597ed --- /dev/null +++ b/core/java/android/hardware/devicestate/DeviceStateRequest.java @@ -0,0 +1,163 @@ +/* + * 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.hardware.devicestate; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.TestApi; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.Executor; + +/** + * A request to alter the state of the device managed by {@link DeviceStateManager}. + * <p> + * Once constructed, a {@link DeviceStateRequest request} can be submitted with a call to + * {@link DeviceStateManager#requestState(DeviceStateRequest, Executor, + * DeviceStateRequest.Callback)}. + * <p> + * By default, the request is kept active until a call to + * {@link DeviceStateManager#cancelRequest(DeviceStateRequest)} or until one of the following + * occurs: + * <ul> + * <li>Another processes submits a request succeeding this request in which case the request + * will be suspended until the interrupting request is canceled. + * <li>The requested state has become unsupported. + * <li>The process submitting the request dies. + * </ul> + * However, this behavior can be changed by setting flags on the request. For example, the + * {@link #FLAG_CANCEL_WHEN_BASE_CHANGES} flag will extend this behavior to also cancel the + * request whenever the base (non-override) device state changes. + * + * @see DeviceStateManager + * + * @hide + */ +@TestApi +public final class DeviceStateRequest { + /** + * Flag that indicates the request should be canceled automatically when the base + * (non-override) device state changes. Useful when the requestor only wants the request to + * remain active while the base state remains constant and automatically cancel when the user + * manipulates the device into a different state. + */ + public static final int FLAG_CANCEL_WHEN_BASE_CHANGES = 1 << 0; + + /** @hide */ + @IntDef(prefix = {"FLAG_"}, flag = true, value = { + FLAG_CANCEL_WHEN_BASE_CHANGES, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface RequestFlags {} + + /** + * Creates a new {@link Builder} for a {@link DeviceStateRequest}. Must be one of the supported + * states for the device which can be queried with a call to + * {@link DeviceStateManager#getSupportedStates()}. + * + * @param requestedState the device state being requested. + */ + @NonNull + public static Builder newBuilder(int requestedState) { + return new Builder(requestedState); + } + + /** + * Builder for {@link DeviceStateRequest}. An instance can be obtained through + * {@link #newBuilder(int)}. + */ + public static final class Builder { + private final int mRequestedState; + private int mFlags; + + private Builder(int requestedState) { + mRequestedState = requestedState; + } + + /** + * Sets the flag bits provided within {@code flags} with all other bits remaining + * unchanged. + */ + @NonNull + public Builder setFlags(@RequestFlags int flags) { + mFlags |= flags; + return this; + } + + /** + * Returns a new {@link DeviceStateRequest} object whose state matches the state set on the + * builder. + */ + @NonNull + public DeviceStateRequest build() { + return new DeviceStateRequest(mRequestedState, mFlags); + } + } + + /** Callback to track the status of a request. */ + public interface Callback { + /** + * Called to indicate the request has become active and the device state will match the + * requested state. + * <p> + * Guaranteed to be called after a call to + * {@link DeviceStateManager.DeviceStateListener#onDeviceStateChanged(int)} with a state + * matching the requested state. + */ + default void onRequestActivated(@NonNull DeviceStateRequest request) {} + + /** + * Called to indicate the request has been temporarily suspended. + * <p> + * Guaranteed to be called before a call to + * {@link DeviceStateManager.DeviceStateListener#onDeviceStateChanged(int)}. + */ + default void onRequestSuspended(@NonNull DeviceStateRequest request) {} + + /** + * Called to indicate the request has been canceled. The request can be resubmitted with + * another call to {@link DeviceStateManager#requestState(DeviceStateRequest, Executor, + * DeviceStateRequest.Callback)}. + * <p> + * Guaranteed to be called before a call to + * {@link DeviceStateManager.DeviceStateListener#onDeviceStateChanged(int)}. + * <p> + * Note: A call to {@link #onRequestSuspended(DeviceStateRequest)} is not guaranteed to + * occur before this method. + */ + default void onRequestCanceled(@NonNull DeviceStateRequest request) {} + } + + private final int mRequestedState; + @RequestFlags + private final int mFlags; + + private DeviceStateRequest(int requestedState, @RequestFlags int flags) { + mRequestedState = requestedState; + mFlags = flags; + } + + public int getState() { + return mRequestedState; + } + + @RequestFlags + public int getFlags() { + return mFlags; + } +} diff --git a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl index a157b3311ca5..323ad21e4884 100644 --- a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl +++ b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl @@ -20,5 +20,45 @@ import android.hardware.devicestate.IDeviceStateManagerCallback; /** @hide */ interface IDeviceStateManager { + /** + * Registers a callback to receive notifications from the device state manager. Only one + * callback can be registered per-process. + * <p> + * As the callback mechanism is used to alert the caller of changes to request status a callback + * <b>MUST</b> be registered before calling {@link #requestState(IBinder, int, int)} or + * {@link #cancelRequest(IBinder)}. Otherwise an exception will be thrown. + * + * @throws SecurityException if a callback is already registered for the calling process. + */ void registerCallback(in IDeviceStateManagerCallback callback); + + /** Returns the array of supported device state identifiers. */ + int[] getSupportedDeviceStates(); + + /** + * Requests that the device enter the supplied {@code state}. A callback <b>MUST</b> have been + * previously registered with {@link #registerCallback(IDeviceStateManagerCallback)} before a + * call to this method. + * + * @param token the request token previously registered with + * {@link #requestState(IBinder, int, int)} + * + * @throws IllegalStateException if a callback has not yet been registered for the calling + * process. + * @throws IllegalStateException if the supplied {@code token} has already been registered. + * @throws IllegalArgumentException if the supplied {@code state} is not supported. + */ + void requestState(IBinder token, int state, int flags); + + /** + * Cancels a request previously submitted with a call to + * {@link #requestState(IBinder, int, int)}. + * + * @param token the request token previously registered with + * {@link #requestState(IBinder, int, int)} + * + * @throws IllegalStateException if the supplied {@code token} has not been previously + * requested. + */ + void cancelRequest(IBinder token); } diff --git a/core/java/android/hardware/devicestate/IDeviceStateManagerCallback.aidl b/core/java/android/hardware/devicestate/IDeviceStateManagerCallback.aidl index d1c581361b62..ee2a071741ef 100644 --- a/core/java/android/hardware/devicestate/IDeviceStateManagerCallback.aidl +++ b/core/java/android/hardware/devicestate/IDeviceStateManagerCallback.aidl @@ -18,5 +18,42 @@ package android.hardware.devicestate; /** @hide */ interface IDeviceStateManagerCallback { + /** + * Called in response to a change in device state. Guaranteed to be called once with the initial + * value on registration of the callback. + * + * @param deviceState the new state of the device. + */ oneway void onDeviceStateChanged(int deviceState); + + /** + * Called to notify the callback that a request has become active. Guaranteed to be called + * after a subsequent call to {@link #onDeviceStateChanged(int)} if the request becoming active + * resulted in a device state change. + * + * @param token the request token previously registered with + * {@link IDeviceStateManager#requestState(IBinder, int, int)} + */ + oneway void onRequestActive(IBinder token); + + /** + * Called to notify the callback that a request has become suspended. Guaranteed to be called + * before a subsequent call to {@link #onDeviceStateChanged(int)} if the request becoming + * suspended resulted in a device state change. + * + * @param token the request token previously registered with + * {@link IDeviceStateManager#requestState(IBinder, int, int)} + */ + oneway void onRequestSuspended(IBinder token); + + /** + * Called to notify the callback that a request has become canceled. No further callbacks will + * be triggered for this request. Guaranteed to be called before a subsequent call to + * {@link #onDeviceStateChanged(int)} if the request becoming canceled resulted in a device + * state change. + * + * @param token the request token previously registered with + * {@link IDeviceStateManager#requestState(IBinder, int, int)} + */ + oneway void onRequestCanceled(IBinder token); } diff --git a/core/java/android/hardware/face/FaceAuthenticationFrame.java b/core/java/android/hardware/face/FaceAuthenticationFrame.java new file mode 100644 index 000000000000..f39d63411825 --- /dev/null +++ b/core/java/android/hardware/face/FaceAuthenticationFrame.java @@ -0,0 +1,75 @@ +/* + * 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.hardware.face; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Data model for a frame captured during face authentication. + * + * @hide + */ +public final class FaceAuthenticationFrame implements Parcelable { + @NonNull private final FaceDataFrame mData; + + /** + * Data model for a frame captured during face authentication. + * + * @param data Information about the current frame. + */ + public FaceAuthenticationFrame(@NonNull FaceDataFrame data) { + mData = data; + } + + /** + * @return Information about the current frame. + */ + @NonNull + public FaceDataFrame getData() { + return mData; + } + + private FaceAuthenticationFrame(@NonNull Parcel source) { + mData = source.readParcelable(FaceDataFrame.class.getClassLoader()); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(mData, flags); + } + + public static final Creator<FaceAuthenticationFrame> CREATOR = + new Creator<FaceAuthenticationFrame>() { + + @Override + public FaceAuthenticationFrame createFromParcel(Parcel source) { + return new FaceAuthenticationFrame(source); + } + + @Override + public FaceAuthenticationFrame[] newArray(int size) { + return new FaceAuthenticationFrame[size]; + } + }; +} diff --git a/core/java/android/hardware/face/FaceDataFrame.java b/core/java/android/hardware/face/FaceDataFrame.java new file mode 100644 index 000000000000..3a0e09b70b50 --- /dev/null +++ b/core/java/android/hardware/face/FaceDataFrame.java @@ -0,0 +1,151 @@ +/* + * 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.hardware.face; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * A container for data common to {@link FaceAuthenticationFrame} and {@link FaceEnrollFrame}. + * + * @hide + */ +public final class FaceDataFrame implements Parcelable { + private final int mAcquiredInfo; + private final int mVendorCode; + private final float mPan; + private final float mTilt; + private final float mDistance; + private final boolean mIsCancellable; + + /** + * A container for data common to {@link FaceAuthenticationFrame} and {@link FaceEnrollFrame}. + * + * @param acquiredInfo An integer corresponding to a known acquired message. + * @param vendorCode An integer representing a custom vendor-specific message. Ignored unless + * {@code acquiredInfo} is {@code FACE_ACQUIRED_VENDOR}. + * @param pan The horizontal pan of the detected face. Values in the range [-1, 1] indicate a + * good capture. + * @param tilt The vertical tilt of the detected face. Values in the range [-1, 1] indicate a + * good capture. + * @param distance The distance of the detected face from the device. Values in the range + * [-1, 1] indicate a good capture. + * @param isCancellable Whether the ongoing face operation should be canceled. + */ + public FaceDataFrame( + int acquiredInfo, + int vendorCode, + float pan, + float tilt, + float distance, + boolean isCancellable) { + mAcquiredInfo = acquiredInfo; + mVendorCode = vendorCode; + mPan = pan; + mTilt = tilt; + mDistance = distance; + mIsCancellable = isCancellable; + } + + /** + * @return An integer corresponding to a known acquired message. + * + * @see android.hardware.biometrics.BiometricFaceConstants + */ + public int getAcquiredInfo() { + return mAcquiredInfo; + } + + /** + * @return An integer representing a custom vendor-specific message. Ignored unless + * {@code acquiredInfo} is {@link + * android.hardware.biometrics.BiometricFaceConstants#FACE_ACQUIRED_VENDOR}. + * + * @see android.hardware.biometrics.BiometricFaceConstants + */ + public int getVendorCode() { + return mVendorCode; + } + + /** + * @return The horizontal pan of the detected face. Values in the range [-1, 1] indicate a good + * capture. + */ + public float getPan() { + return mPan; + } + + /** + * @return The vertical tilt of the detected face. Values in the range [-1, 1] indicate a good + * capture. + */ + public float getTilt() { + return mTilt; + } + + /** + * @return The distance of the detected face from the device. Values in the range [-1, 1] + * indicate a good capture. + */ + public float getDistance() { + return mDistance; + } + + /** + * @return Whether the ongoing face operation should be canceled. + */ + public boolean isCancellable() { + return mIsCancellable; + } + + private FaceDataFrame(@NonNull Parcel source) { + mAcquiredInfo = source.readInt(); + mVendorCode = source.readInt(); + mPan = source.readFloat(); + mTilt = source.readFloat(); + mDistance = source.readFloat(); + mIsCancellable = source.readBoolean(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mAcquiredInfo); + dest.writeInt(mVendorCode); + dest.writeFloat(mPan); + dest.writeFloat(mTilt); + dest.writeFloat(mDistance); + dest.writeBoolean(mIsCancellable); + } + + public static final Creator<FaceDataFrame> CREATOR = new Creator<FaceDataFrame>() { + @Override + public FaceDataFrame createFromParcel(Parcel source) { + return new FaceDataFrame(source); + } + + @Override + public FaceDataFrame[] newArray(int size) { + return new FaceDataFrame[size]; + } + }; +} diff --git a/core/java/android/hardware/face/FaceEnrollCell.java b/core/java/android/hardware/face/FaceEnrollCell.java new file mode 100644 index 000000000000..8415419577e2 --- /dev/null +++ b/core/java/android/hardware/face/FaceEnrollCell.java @@ -0,0 +1,96 @@ +/* + * 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.hardware.face; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * A matrix cell, corresponding to a desired face image, that may be captured during enrollment. + * + * @hide + */ +public final class FaceEnrollCell implements Parcelable { + private final int mX; + private final int mY; + private final int mZ; + + /** + * A matrix cell, corresponding to a desired face image, that may be captured during enrollment. + * + * @param x The horizontal coordinate of this cell. + * @param y The vertical coordinate of this cell. + * @param z The depth coordinate of this cell. + */ + public FaceEnrollCell(int x, int y, int z) { + mX = x; + mY = y; + mZ = z; + } + + /** + * @return The horizontal coordinate of this cell. + */ + public int getX() { + return mX; + } + + /** + * @return The vertical coordinate of this cell. + */ + public int getY() { + return mY; + } + + /** + * @return The depth coordinate of this cell. + */ + public int getZ() { + return mZ; + } + + private FaceEnrollCell(@NonNull Parcel source) { + mX = source.readInt(); + mY = source.readInt(); + mZ = source.readInt(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mX); + dest.writeInt(mY); + dest.writeInt(mZ); + } + + public static final Creator<FaceEnrollCell> CREATOR = new Creator<FaceEnrollCell>() { + @Override + public FaceEnrollCell createFromParcel(Parcel source) { + return new FaceEnrollCell(source); + } + + @Override + public FaceEnrollCell[] newArray(int size) { + return new FaceEnrollCell[size]; + } + }; +} diff --git a/core/java/android/hardware/face/FaceEnrollFrame.java b/core/java/android/hardware/face/FaceEnrollFrame.java new file mode 100644 index 000000000000..551139d240a3 --- /dev/null +++ b/core/java/android/hardware/face/FaceEnrollFrame.java @@ -0,0 +1,103 @@ +/* + * 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.hardware.face; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Data model for a frame captured during face enrollment. + * + * @hide + */ +public final class FaceEnrollFrame implements Parcelable { + @Nullable private final FaceEnrollCell mCell; + @FaceEnrollStage private final int mStage; + @NonNull private final FaceDataFrame mData; + + /** + * Data model for a frame captured during face enrollment. + * + * @param cell The cell captured during this frame of enrollment, if any. + * @param stage An integer representing the current stage of enrollment. + * @param data Information about the current frame. + */ + public FaceEnrollFrame( + @Nullable FaceEnrollCell cell, + @FaceEnrollStage int stage, + @NonNull FaceDataFrame data) { + mCell = cell; + mStage = stage; + mData = data; + } + + /** + * @return The cell captured during this frame of enrollment, if any. + */ + @Nullable + public FaceEnrollCell getCell() { + return mCell; + } + + /** + * @return An integer representing the current stage of enrollment. + */ + @FaceEnrollStage + public int getStage() { + return mStage; + } + + /** + * @return Information about the current frame. + */ + @NonNull + public FaceDataFrame getData() { + return mData; + } + + private FaceEnrollFrame(@NonNull Parcel source) { + mCell = source.readParcelable(FaceEnrollCell.class.getClassLoader()); + mStage = source.readInt(); + mData = source.readParcelable(FaceDataFrame.class.getClassLoader()); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(mCell, flags); + dest.writeInt(mStage); + dest.writeParcelable(mData, flags); + } + + public static final Creator<FaceEnrollFrame> CREATOR = new Creator<FaceEnrollFrame>() { + @Override + public FaceEnrollFrame createFromParcel(Parcel source) { + return new FaceEnrollFrame(source); + } + + @Override + public FaceEnrollFrame[] newArray(int size) { + return new FaceEnrollFrame[size]; + } + }; +} diff --git a/core/java/android/hardware/face/FaceEnrollStage.java b/core/java/android/hardware/face/FaceEnrollStage.java new file mode 100644 index 000000000000..03dba551aae2 --- /dev/null +++ b/core/java/android/hardware/face/FaceEnrollStage.java @@ -0,0 +1,68 @@ +/* + * 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.hardware.face; + +import android.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * A stage that may occur during face enrollment. + * + * @hide + */ +@Retention(RetentionPolicy.SOURCE) +@IntDef({ + FaceEnrollStage.FIRST_FRAME_RECEIVED, + FaceEnrollStage.WAITING_FOR_CENTERING, + FaceEnrollStage.HOLD_STILL_IN_CENTER, + FaceEnrollStage.ENROLLING_MOVEMENT_1, + FaceEnrollStage.ENROLLING_MOVEMENT_2, + FaceEnrollStage.ENROLLMENT_FINISHED +}) +public @interface FaceEnrollStage { + /** + * Enrollment has just begun. No action is needed from the user yet. + */ + int FIRST_FRAME_RECEIVED = 0; + + /** + * The user must center their face in the frame. + */ + int WAITING_FOR_CENTERING = 1; + + /** + * The user must keep their face centered in the frame. + */ + int HOLD_STILL_IN_CENTER = 2; + + /** + * The user must follow a first set of movement instructions. + */ + int ENROLLING_MOVEMENT_1 = 3; + + /** + * The user must follow a second set of movement instructions. + */ + int ENROLLING_MOVEMENT_2 = 4; + + /** + * Enrollment has completed. No more action is needed from the user. + */ + int ENROLLMENT_FINISHED = 5; +} diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 44a2e97e6f04..7e2be01feb01 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -1732,7 +1732,7 @@ public class InputMethodService extends AbstractInputMethodService { // If app window has portrait orientation, regardless of what display orientation // is, IME shouldn't use fullscreen-mode. || (mInputEditorInfo.internalImeOptions - & EditorInfo.IME_FLAG_APP_WINDOW_PORTRAIT) != 0) { + & EditorInfo.IME_INTERNAL_FLAG_APP_WINDOW_PORTRAIT) != 0) { return false; } return true; 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/net/OemNetworkPreferences.java b/core/java/android/net/OemNetworkPreferences.java index 6a8e3f9c01f2..5e56164cc82c 100644 --- a/core/java/android/net/OemNetworkPreferences.java +++ b/core/java/android/net/OemNetworkPreferences.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. @@ -18,14 +18,14 @@ package android.net; import android.annotation.IntDef; import android.annotation.NonNull; +import android.os.Bundle; import android.os.Parcelable; -import android.util.SparseArray; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; import java.util.Collections; -import java.util.List; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; /** @hide */ @@ -60,16 +60,16 @@ public final class OemNetworkPreferences implements Parcelable { public static final int OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY = 4; @NonNull - private final SparseArray<List<String>> mNetworkMappings; + private final Bundle mNetworkMappings; @NonNull - public SparseArray<List<String>> getNetworkPreferences() { - return mNetworkMappings.clone(); + public Map<String, Integer> getNetworkPreferences() { + return convertToUnmodifiableMap(mNetworkMappings); } - private OemNetworkPreferences(@NonNull SparseArray<List<String>> networkMappings) { + private OemNetworkPreferences(@NonNull final Bundle networkMappings) { Objects.requireNonNull(networkMappings); - mNetworkMappings = networkMappings.clone(); + mNetworkMappings = (Bundle) networkMappings.clone(); } @Override @@ -99,26 +99,45 @@ public final class OemNetworkPreferences implements Parcelable { * @hide */ public static final class Builder { - private final SparseArray<List<String>> mNetworkMappings; + private final Bundle mNetworkMappings; public Builder() { - mNetworkMappings = new SparseArray<>(); + mNetworkMappings = new Bundle(); + } + + public Builder(@NonNull final OemNetworkPreferences preferences) { + Objects.requireNonNull(preferences); + mNetworkMappings = (Bundle) preferences.mNetworkMappings.clone(); } /** - * Add a network preference for a list of packages. + * Add a network preference for a given package. Previously stored values for the given + * package will be overwritten. * - * @param preference the desired network preference to use - * @param packages full package names (e.g.: "com.google.apps.contacts") for apps to use - * the given preference + * @param packageName full package name (e.g.: "com.google.apps.contacts") of the app + * to use the given preference + * @param preference the desired network preference to use * @return The builder to facilitate chaining. */ @NonNull - public Builder addNetworkPreference(@OemNetworkPreference final int preference, - @NonNull List<String> packages) { - Objects.requireNonNull(packages); - mNetworkMappings.put(preference, - Collections.unmodifiableList(new ArrayList<>(packages))); + public Builder addNetworkPreference(@NonNull final String packageName, + @OemNetworkPreference final int preference) { + Objects.requireNonNull(packageName); + mNetworkMappings.putInt(packageName, preference); + return this; + } + + /** + * Remove a network preference for a given package. + * + * @param packageName full package name (e.g.: "com.google.apps.contacts") of the app to + * remove a preference for. + * @return The builder to facilitate chaining. + */ + @NonNull + public Builder removeNetworkPreference(@NonNull final String packageName) { + Objects.requireNonNull(packageName); + mNetworkMappings.remove(packageName); return this; } @@ -131,6 +150,14 @@ public final class OemNetworkPreferences implements Parcelable { } } + private static Map<String, Integer> convertToUnmodifiableMap(@NonNull final Bundle bundle) { + final Map<String, Integer> networkPreferences = new HashMap<>(); + for (final String key : bundle.keySet()) { + networkPreferences.put(key, bundle.getInt(key)); + } + return Collections.unmodifiableMap(networkPreferences); + } + /** @hide */ @IntDef(prefix = "OEM_NETWORK_PREFERENCE_", value = { OEM_NETWORK_PREFERENCE_DEFAULT, @@ -168,7 +195,7 @@ public final class OemNetworkPreferences implements Parcelable { @Override public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { - dest.writeSparseArray(mNetworkMappings); + dest.writeBundle(mNetworkMappings); } @Override @@ -187,7 +214,7 @@ public final class OemNetworkPreferences implements Parcelable { @Override public OemNetworkPreferences createFromParcel(@NonNull android.os.Parcel in) { return new OemNetworkPreferences( - in.readSparseArray(getClass().getClassLoader())); + in.readBundle(getClass().getClassLoader())); } }; } 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/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java index 7e7057fe56cb..0ff68fc582d8 100644 --- a/core/java/android/os/incremental/IncrementalManager.java +++ b/core/java/android/os/incremental/IncrementalManager.java @@ -304,20 +304,16 @@ public final class IncrementalManager { } /** - * Called when a callback wants to stop listen to the loading progress of an installed package. - * Decrease the count of the callbacks on the associated to the corresponding storage. - * If the count becomes zero, unregister the storage listener. + * Called to stop all listeners from listening to loading progress of an installed package. * @param codePath Path of the installed package - * @return True if the package name and associated storage id are valid. False otherwise. */ - public boolean unregisterLoadingProgressCallback(@NonNull String codePath, - @NonNull IPackageLoadingProgressCallback callback) { + public void unregisterLoadingProgressCallbacks(@NonNull String codePath) { final IncrementalStorage storage = openStorage(codePath); if (storage == null) { // storage does not exist, package not installed - return false; + return; } - return mLoadingProgressCallbacks.unregisterCallback(storage, callback); + mLoadingProgressCallbacks.cleanUpCallbacks(storage); } private static class LoadingProgressCallbacks extends IStorageLoadingProgressListener.Stub { @@ -325,7 +321,6 @@ public final class IncrementalManager { private final SparseArray<RemoteCallbackList<IPackageLoadingProgressCallback>> mCallbacks = new SparseArray<>(); - // TODO(b/165841827): disable callbacks when app state changes to fully loaded public void cleanUpCallbacks(@NonNull IncrementalStorage storage) { final int storageId = storage.getId(); final RemoteCallbackList<IPackageLoadingProgressCallback> callbacksForStorage; diff --git a/core/java/android/service/resumeonreboot/ResumeOnRebootService.java b/core/java/android/service/resumeonreboot/ResumeOnRebootService.java index 4ebaa96f4be2..ad49ffd57dc2 100644 --- a/core/java/android/service/resumeonreboot/ResumeOnRebootService.java +++ b/core/java/android/service/resumeonreboot/ResumeOnRebootService.java @@ -87,7 +87,9 @@ public abstract class ResumeOnRebootService extends Service { * Implementation for wrapping the opaque blob used for resume-on-reboot prior to * reboot. The service should not assume any structure of the blob to be wrapped. The * implementation should wrap the opaque blob in a reasonable time or throw {@link IOException} - * if it's unable to complete the action. + * if it's unable to complete the action due to retry-able errors (e.g network errors) + * and {@link IllegalArgumentException} if {@code wrapBlob} fails due to fatal errors + * (e.g corrupted blob). * * @param blob The opaque blob with size on the order of 100 bytes. * @param lifeTimeInMillis The life time of the blob. This must be strictly enforced by the @@ -95,7 +97,8 @@ public abstract class ResumeOnRebootService extends Service { * this function after expiration should * fail. * @return Wrapped blob to be persisted across reboot with size on the order of 100 bytes. - * @throws IOException if the implementation is unable to wrap the blob successfully. + * @throws IOException if the implementation is unable to wrap the blob successfully due to + * retry-able errors. */ @NonNull public abstract byte[] onWrap(@NonNull byte[] blob, @DurationMillisLong long lifeTimeInMillis) @@ -106,12 +109,13 @@ public abstract class ResumeOnRebootService extends Service { * operation would happen after reboot during direct boot mode (i.e before device is unlocked * for the first time). The implementation should unwrap the wrapped blob in a reasonable time * and returns the result or throw {@link IOException} if it's unable to complete the action - * and {@link IllegalArgumentException} if {@code unwrapBlob} fails because the wrappedBlob is - * stale. + * due to retry-able errors (e.g network error) and {@link IllegalArgumentException} + * if {@code unwrapBlob} fails due to fatal errors (e.g stale or corrupted blob). * * @param wrappedBlob The wrapped blob with size on the order of 100 bytes. * @return Unwrapped blob used for resume-on-reboot with the size on the order of 100 bytes. - * @throws IOException if the implementation is unable to unwrap the wrapped blob successfully. + * @throws IOException if the implementation is unable to unwrap the wrapped blob successfully + * due to retry-able errors. */ @NonNull public abstract byte[] onUnwrap(@NonNull byte[] wrappedBlob) throws IOException; 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/service/voice/VoiceInteractionServiceInfo.java b/core/java/android/service/voice/VoiceInteractionServiceInfo.java index f7710e6a82ce..ff03cc14e73b 100644 --- a/core/java/android/service/voice/VoiceInteractionServiceInfo.java +++ b/core/java/android/service/voice/VoiceInteractionServiceInfo.java @@ -18,6 +18,7 @@ package android.service.voice; import android.Manifest; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.AppGlobals; import android.content.ComponentName; import android.content.pm.PackageManager; @@ -44,6 +45,7 @@ public class VoiceInteractionServiceInfo { private ServiceInfo mServiceInfo; private String mSessionService; private String mRecognitionService; + private String mHotwordDetectionService; private String mSettingsActivity; private boolean mSupportsAssist; private boolean mSupportsLaunchFromKeyguard; @@ -133,6 +135,8 @@ public class VoiceInteractionServiceInfo { false); mSupportsLocalInteraction = array.getBoolean(com.android.internal. R.styleable.VoiceInteractionService_supportsLocalInteraction, false); + mHotwordDetectionService = array.getString(com.android.internal.R.styleable + .VoiceInteractionService_hotwordDetectionService); array.recycle(); if (mSessionService == null) { mParseError = "No sessionService specified"; @@ -181,4 +185,9 @@ public class VoiceInteractionServiceInfo { public boolean getSupportsLocalInteraction() { return mSupportsLocalInteraction; } + + @Nullable + public String getHotwordDetectionService() { + return mHotwordDetectionService; + } } 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/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/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index acd25077fb5a..98b4acd302cb 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -188,8 +188,6 @@ public final class SurfaceControl implements Parcelable { IBinder displayToken, int mode); private static native void nativeDeferTransactionUntil(long transactionObj, long nativeObject, long barrierObject, long frame); - private static native void nativeReparentChildren(long transactionObj, long nativeObject, - long newParentObject); private static native void nativeReparent(long transactionObj, long nativeObject, long newParentNativeObject); @@ -2970,15 +2968,6 @@ public final class SurfaceControl implements Parcelable { } /** - * @hide - */ - public Transaction reparentChildren(SurfaceControl sc, SurfaceControl newParent) { - checkPreconditions(sc); - nativeReparentChildren(mNativeObject, sc.mNativeObject, newParent.mNativeObject); - return this; - } - - /** * Re-parents a given layer to a new parent. Children inherit transform (position, scaling) * crop, visibility, and Z-ordering from their parents, as if the children were pixels within the * parent Surface. 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/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index dd0ab6503a8a..170124e348c9 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -116,14 +116,6 @@ public final class WindowManagerGlobal { */ public static final int RELAYOUT_INSETS_PENDING = 0x1; - /** - * Flag for relayout: the client may be currently using the current surface, - * so if it is to be destroyed as a part of the relayout the destroy must - * be deferred until later. The client will call performDeferredDestroy() - * when it is okay. - */ - public static final int RELAYOUT_DEFER_SURFACE_DESTROY = 0x2; - public static final int ADD_FLAG_IN_TOUCH_MODE = 0x1; public static final int ADD_FLAG_APP_VISIBLE = 0x2; public static final int ADD_FLAG_USE_TRIPLE_BUFFERING = 0x4; diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java index 2df75f6570db..bde4cb7c4fc3 100644 --- a/core/java/android/view/inputmethod/EditorInfo.java +++ b/core/java/android/view/inputmethod/EditorInfo.java @@ -299,7 +299,7 @@ public class EditorInfo implements InputType, Parcelable { * {@link EditorInfo} is using {@link Configuration#ORIENTATION_PORTRAIT} mode. * @hide */ - public static final int IME_FLAG_APP_WINDOW_PORTRAIT = 0x80000000; + public static final int IME_INTERNAL_FLAG_APP_WINDOW_PORTRAIT = 0x00000001; /** * Generic unspecified type for {@link #imeOptions}. @@ -321,7 +321,6 @@ public class EditorInfo implements InputType, Parcelable { * 1 1 IME_ACTION_NEXT * 11 IME_ACTION_DONE * 111 IME_ACTION_PREVIOUS - * 1 IME_FLAG_APP_WINDOW_PORTRAIT * 1 IME_FLAG_NO_PERSONALIZED_LEARNING * 1 IME_FLAG_NO_FULLSCREEN * 1 IME_FLAG_NAVIGATE_PREVIOUS @@ -356,7 +355,7 @@ public class EditorInfo implements InputType, Parcelable { * Masks for {@link internalImeOptions} * * <pre> - * 1 IME_FLAG_APP_WINDOW_PORTRAIT + * 1 IME_INTERNAL_FLAG_APP_WINDOW_PORTRAIT * |-------|-------|-------|-------|</pre> */ @@ -984,6 +983,7 @@ public class EditorInfo implements InputType, Parcelable { dest.writeInt(inputType); dest.writeInt(imeOptions); dest.writeString(privateImeOptions); + dest.writeInt(internalImeOptions); TextUtils.writeToParcel(actionLabel, dest, flags); dest.writeInt(actionId); dest.writeInt(initialSelStart); @@ -1019,6 +1019,7 @@ public class EditorInfo implements InputType, Parcelable { res.inputType = source.readInt(); res.imeOptions = source.readInt(); res.privateImeOptions = source.readString(); + res.internalImeOptions = source.readInt(); res.actionLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); res.actionId = source.readInt(); res.initialSelStart = source.readInt(); diff --git a/core/java/android/widget/CheckBox.java b/core/java/android/widget/CheckBox.java index 046f75fd37ac..2452e4c6d56a 100644 --- a/core/java/android/widget/CheckBox.java +++ b/core/java/android/widget/CheckBox.java @@ -18,6 +18,7 @@ package android.widget; import android.content.Context; import android.util.AttributeSet; +import android.widget.RemoteViews.RemoteView; /** * <p> @@ -52,6 +53,7 @@ import android.util.AttributeSet; * {@link android.R.styleable#View View Attributes} * </p> */ +@RemoteView public class CheckBox extends CompoundButton { public CheckBox(Context context) { this(context, null); diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java index 135ff9fcd989..63f8ee7528f2 100644 --- a/core/java/android/widget/CompoundButton.java +++ b/core/java/android/widget/CompoundButton.java @@ -27,11 +27,13 @@ import android.graphics.BlendMode; import android.graphics.Canvas; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; import android.os.Parcel; import android.os.Parcelable; import android.util.AttributeSet; import android.util.Log; import android.view.Gravity; +import android.view.RemotableViewMethod; import android.view.SoundEffectConstants; import android.view.ViewDebug; import android.view.ViewHierarchyEncoder; @@ -275,6 +277,7 @@ public abstract class CompoundButton extends Button implements Checkable { * @param resId the resource identifier of the drawable * @attr ref android.R.styleable#CompoundButton_button */ + @RemotableViewMethod(asyncImpl = "setButtonDrawableAsync") public void setButtonDrawable(@DrawableRes int resId) { final Drawable d; if (resId != 0) { @@ -285,6 +288,12 @@ public abstract class CompoundButton extends Button implements Checkable { setButtonDrawable(d); } + /** @hide **/ + public Runnable setButtonDrawableAsync(@DrawableRes int resId) { + Drawable drawable = resId == 0 ? null : getContext().getDrawable(resId); + return () -> setButtonDrawable(drawable); + } + /** * Sets a drawable as the compound button image. * @@ -336,6 +345,23 @@ public abstract class CompoundButton extends Button implements Checkable { } /** + * Sets the button of this CompoundButton to the specified Icon. + * + * @param icon an Icon holding the desired button, or {@code null} to clear + * the button + */ + @RemotableViewMethod(asyncImpl = "setButtonIconAsync") + public void setButtonIcon(@Nullable Icon icon) { + setButtonDrawable(icon == null ? null : icon.loadDrawable(getContext())); + } + + /** @hide **/ + public Runnable setButtonIconAsync(@Nullable Icon icon) { + Drawable button = icon == null ? null : icon.loadDrawable(getContext()); + return () -> setButtonDrawable(button); + } + + /** * Applies a tint to the button drawable. Does not modify the current tint * mode, which is {@link PorterDuff.Mode#SRC_IN} by default. * <p> @@ -350,6 +376,7 @@ public abstract class CompoundButton extends Button implements Checkable { * @see #setButtonTintList(ColorStateList) * @see Drawable#setTintList(ColorStateList) */ + @RemotableViewMethod public void setButtonTintList(@Nullable ColorStateList tint) { mButtonTintList = tint; mHasButtonTint = true; @@ -394,6 +421,7 @@ public abstract class CompoundButton extends Button implements Checkable { * @see #getButtonTintMode() * @see Drawable#setTintBlendMode(BlendMode) */ + @RemotableViewMethod public void setButtonTintBlendMode(@Nullable BlendMode tintMode) { mButtonBlendMode = tintMode; mHasButtonBlendMode = true; diff --git a/core/java/android/widget/RadioButton.java b/core/java/android/widget/RadioButton.java index a04d7c34c444..9b3503433e56 100644 --- a/core/java/android/widget/RadioButton.java +++ b/core/java/android/widget/RadioButton.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.content.Context; import android.util.AttributeSet; import android.view.accessibility.AccessibilityNodeInfo; +import android.widget.RemoteViews.RemoteView; import com.android.internal.R; @@ -49,6 +50,7 @@ import com.android.internal.R; * {@link android.R.styleable#View View Attributes} * </p> */ +@RemoteView public class RadioButton extends CompoundButton { public RadioButton(Context context) { diff --git a/core/java/android/widget/RadioGroup.java b/core/java/android/widget/RadioGroup.java index 4722fdc7818f..d445fdc01564 100644 --- a/core/java/android/widget/RadioGroup.java +++ b/core/java/android/widget/RadioGroup.java @@ -31,6 +31,7 @@ import android.view.ViewStructure; import android.view.accessibility.AccessibilityNodeInfo; import android.view.autofill.AutofillManager; import android.view.autofill.AutofillValue; +import android.widget.RemoteViews.RemoteView; import com.android.internal.R; @@ -59,6 +60,7 @@ import com.android.internal.R; * @see RadioButton * */ +@RemoteView public class RadioGroup extends LinearLayout { private static final String LOG_TAG = RadioGroup.class.getSimpleName(); diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index b47a0acc4fae..dfef7ca825a1 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -132,6 +132,13 @@ import java.util.function.Consumer; * <li>{@link android.widget.TextClock}</li> * <li>{@link android.widget.TextView}</li> * </ul> + * <p>As of API 31, the following widgets and layouts may also be used:</p> + * <ul> + * <li>{@link android.widget.CheckBox}</li> + * <li>{@link android.widget.RadioButton}</li> + * <li>{@link android.widget.RadioGroup}</li> + * <li>{@link android.widget.Switch}</li> + * </ul> * <p>Descendants of these classes are not supported.</p> */ public class RemoteViews implements Parcelable, Filter { @@ -185,6 +192,8 @@ public class RemoteViews implements Parcelable, Filter { private static final int REMOVE_FROM_PARENT_ACTION_TAG = 23; private static final int RESOURCE_REFLECTION_ACTION_TAG = 24; private static final int COMPLEX_UNIT_DIMENSION_REFLECTION_ACTION_TAG = 25; + private static final int SET_COMPOUND_BUTTON_CHECKED_TAG = 26; + private static final int SET_RADIO_GROUP_CHECKED = 27; /** @hide **/ @IntDef(prefix = "MARGIN_", value = { @@ -2552,6 +2561,87 @@ public class RemoteViews implements Parcelable, Filter { } } + private static class SetCompoundButtonCheckedAction extends Action { + + private final boolean mChecked; + + SetCompoundButtonCheckedAction(@IdRes int viewId, boolean checked) { + this.viewId = viewId; + mChecked = checked; + } + + SetCompoundButtonCheckedAction(Parcel in) { + viewId = in.readInt(); + mChecked = in.readBoolean(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(viewId); + dest.writeBoolean(mChecked); + } + + @Override + public void apply(View root, ViewGroup rootParent, OnClickHandler handler) + throws ActionException { + final View target = root.findViewById(viewId); + if (target == null) return; + + if (!(target instanceof CompoundButton)) { + Log.w(LOG_TAG, "Cannot set checked to view " + + viewId + " because it is not a CompoundButton"); + return; + } + + ((CompoundButton) target).setChecked(mChecked); + } + + @Override + public int getActionTag() { + return SET_COMPOUND_BUTTON_CHECKED_TAG; + } + } + + private static class SetRadioGroupCheckedAction extends Action { + + @IdRes private final int mCheckedId; + + SetRadioGroupCheckedAction(@IdRes int viewId, @IdRes int checkedId) { + this.viewId = viewId; + mCheckedId = checkedId; + } + + SetRadioGroupCheckedAction(Parcel in) { + viewId = in.readInt(); + mCheckedId = in.readInt(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(viewId); + dest.writeInt(mCheckedId); + } + + @Override + public void apply(View root, ViewGroup rootParent, OnClickHandler handler) + throws ActionException { + final View target = root.findViewById(viewId); + if (target == null) return; + + if (!(target instanceof RadioGroup)) { + Log.w(LOG_TAG, "Cannot check " + viewId + " because it's not a RadioGroup"); + return; + } + + ((RadioGroup) target).check(mCheckedId); + } + + @Override + public int getActionTag() { + return SET_RADIO_GROUP_CHECKED; + } + } + /** * Create a new RemoteViews object that will display the views contained * in the specified layout file. @@ -2766,6 +2856,10 @@ public class RemoteViews implements Parcelable, Filter { return new ResourceReflectionAction(parcel); case COMPLEX_UNIT_DIMENSION_REFLECTION_ACTION_TAG: return new ComplexUnitDimensionReflectionAction(parcel); + case SET_COMPOUND_BUTTON_CHECKED_TAG: + return new SetCompoundButtonCheckedAction(parcel); + case SET_RADIO_GROUP_CHECKED: + return new SetRadioGroupCheckedAction(parcel); default: throw new ActionException("Tag " + tag + " not found"); } @@ -3846,6 +3940,26 @@ public class RemoteViews implements Parcelable, Filter { } /** + * Equivalent to calling {@link android.widget.CompoundButton#setChecked(boolean)}. + * + * @param viewId The id of the view whose property to set. + * @param checked true to check the button, false to uncheck it. + */ + public void setCompoundButtonChecked(@IdRes int viewId, boolean checked) { + addAction(new SetCompoundButtonCheckedAction(viewId, checked)); + } + + /** + * Equivalent to calling {@link android.widget.RadioGroup#check(int)}. + * + * @param viewId The id of the view whose property to set. + * @param checkedId The unique id of the radio button to select in the group. + */ + public void setRadioGroupChecked(@IdRes int viewId, @IdRes int checkedId) { + addAction(new SetRadioGroupCheckedAction(viewId, checkedId)); + } + + /** * Provides an alternate layout ID, which can be used to inflate this view. This layout will be * used by the host when the widgets displayed on a light-background where foreground elements * and text can safely draw using a dark color without any additional background protection. diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java index 3295fd2ea1c3..d3600ef9f557 100644 --- a/core/java/android/widget/Switch.java +++ b/core/java/android/widget/Switch.java @@ -35,6 +35,7 @@ import android.graphics.Rect; import android.graphics.Region.Op; import android.graphics.Typeface; import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; import android.os.Build; import android.os.Build.VERSION_CODES; import android.text.Layout; @@ -48,12 +49,14 @@ import android.util.FloatProperty; import android.util.MathUtils; import android.view.Gravity; import android.view.MotionEvent; +import android.view.RemotableViewMethod; import android.view.SoundEffectConstants; import android.view.VelocityTracker; import android.view.ViewConfiguration; import android.view.ViewStructure; import android.view.accessibility.AccessibilityEvent; import android.view.inspector.InspectableProperty; +import android.widget.RemoteViews.RemoteView; import com.android.internal.R; @@ -84,6 +87,7 @@ import com.android.internal.R; * @attr ref android.R.styleable#Switch_thumbTextPadding * @attr ref android.R.styleable#Switch_track */ +@RemoteView public class Switch extends CompoundButton { private static final int THUMB_ANIMATION_DURATION = 250; @@ -441,6 +445,7 @@ public class Switch extends CompoundButton { * * @attr ref android.R.styleable#Switch_switchPadding */ + @RemotableViewMethod public void setSwitchPadding(int pixels) { mSwitchPadding = pixels; requestLayout(); @@ -466,6 +471,7 @@ public class Switch extends CompoundButton { * * @attr ref android.R.styleable#Switch_switchMinWidth */ + @RemotableViewMethod public void setSwitchMinWidth(int pixels) { mSwitchMinWidth = pixels; requestLayout(); @@ -491,6 +497,7 @@ public class Switch extends CompoundButton { * * @attr ref android.R.styleable#Switch_thumbTextPadding */ + @RemotableViewMethod public void setThumbTextPadding(int pixels) { mThumbTextPadding = pixels; requestLayout(); @@ -533,10 +540,17 @@ public class Switch extends CompoundButton { * * @attr ref android.R.styleable#Switch_track */ + @RemotableViewMethod(asyncImpl = "setTrackResourceAsync") public void setTrackResource(@DrawableRes int resId) { setTrackDrawable(getContext().getDrawable(resId)); } + /** @hide **/ + public Runnable setTrackResourceAsync(@DrawableRes int resId) { + Drawable drawable = resId == 0 ? null : getContext().getDrawable(resId); + return () -> setTrackDrawable(drawable); + } + /** * Get the drawable used for the track that the switch slides within. * @@ -550,6 +564,23 @@ public class Switch extends CompoundButton { } /** + * Set the drawable used for the track that the switch slides within to the specified Icon. + * + * @param icon an Icon holding the desired track, or {@code null} to clear + * the track + */ + @RemotableViewMethod(asyncImpl = "setTrackIconAsync") + public void setTrackIcon(@Nullable Icon icon) { + setTrackDrawable(icon == null ? null : icon.loadDrawable(getContext())); + } + + /** @hide **/ + public Runnable setTrackIconAsync(@Nullable Icon icon) { + Drawable track = icon == null ? null : icon.loadDrawable(getContext()); + return () -> setTrackDrawable(track); + } + + /** * Applies a tint to the track drawable. Does not modify the current * tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default. * <p> @@ -563,6 +594,7 @@ public class Switch extends CompoundButton { * @see #getTrackTintList() * @see Drawable#setTintList(ColorStateList) */ + @RemotableViewMethod public void setTrackTintList(@Nullable ColorStateList tint) { mTrackTintList = tint; mHasTrackTint = true; @@ -607,6 +639,7 @@ public class Switch extends CompoundButton { * @see #getTrackTintMode() * @see Drawable#setTintBlendMode(BlendMode) */ + @RemotableViewMethod public void setTrackTintBlendMode(@Nullable BlendMode blendMode) { mTrackBlendMode = blendMode; mHasTrackTintMode = true; @@ -686,10 +719,17 @@ public class Switch extends CompoundButton { * * @attr ref android.R.styleable#Switch_thumb */ + @RemotableViewMethod(asyncImpl = "setThumbResourceAsync") public void setThumbResource(@DrawableRes int resId) { setThumbDrawable(getContext().getDrawable(resId)); } + /** @hide **/ + public Runnable setThumbResourceAsync(@DrawableRes int resId) { + Drawable drawable = resId == 0 ? null : getContext().getDrawable(resId); + return () -> setThumbDrawable(drawable); + } + /** * Get the drawable used for the switch "thumb" - the piece that the user * can physically touch and drag along the track. @@ -704,6 +744,24 @@ public class Switch extends CompoundButton { } /** + * Set the drawable used for the switch "thumb" - the piece that the user + * can physically touch and drag along the track - to the specified Icon. + * + * @param icon an Icon holding the desired thumb, or {@code null} to clear + * the thumb + */ + @RemotableViewMethod(asyncImpl = "setThumbIconAsync") + public void setThumbIcon(@Nullable Icon icon) { + setThumbDrawable(icon == null ? null : icon.loadDrawable(getContext())); + } + + /** @hide **/ + public Runnable setThumbIconAsync(@Nullable Icon icon) { + Drawable track = icon == null ? null : icon.loadDrawable(getContext()); + return () -> setThumbDrawable(track); + } + + /** * Applies a tint to the thumb drawable. Does not modify the current * tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default. * <p> @@ -717,6 +775,7 @@ public class Switch extends CompoundButton { * @see #getThumbTintList() * @see Drawable#setTintList(ColorStateList) */ + @RemotableViewMethod public void setThumbTintList(@Nullable ColorStateList tint) { mThumbTintList = tint; mHasThumbTint = true; @@ -761,6 +820,7 @@ public class Switch extends CompoundButton { * @see #getThumbTintMode() * @see Drawable#setTintBlendMode(BlendMode) */ + @RemotableViewMethod public void setThumbTintBlendMode(@Nullable BlendMode blendMode) { mThumbBlendMode = blendMode; mHasThumbTintMode = true; @@ -822,6 +882,7 @@ public class Switch extends CompoundButton { * * @attr ref android.R.styleable#Switch_splitTrack */ + @RemotableViewMethod public void setSplitTrack(boolean splitTrack) { mSplitTrack = splitTrack; invalidate(); @@ -852,6 +913,7 @@ public class Switch extends CompoundButton { * * @attr ref android.R.styleable#Switch_textOn */ + @RemotableViewMethod public void setTextOn(CharSequence textOn) { mTextOn = textOn; requestLayout(); @@ -875,6 +937,7 @@ public class Switch extends CompoundButton { * * @attr ref android.R.styleable#Switch_textOff */ + @RemotableViewMethod public void setTextOff(CharSequence textOff) { mTextOff = textOff; requestLayout(); @@ -889,6 +952,7 @@ public class Switch extends CompoundButton { * @param showText {@code true} to display on/off text * @attr ref android.R.styleable#Switch_showText */ + @RemotableViewMethod public void setShowText(boolean showText) { if (mShowText != showText) { mShowText = showText; diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 8cfbca88c596..796607065944 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -8750,7 +8750,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } if (getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT) { - outAttrs.internalImeOptions |= EditorInfo.IME_FLAG_APP_WINDOW_PORTRAIT; + outAttrs.internalImeOptions |= EditorInfo.IME_INTERNAL_FLAG_APP_WINDOW_PORTRAIT; } if (isMultilineInputType(outAttrs.inputType)) { // Multi-line text editors should always show an enter key. diff --git a/core/java/com/android/internal/widget/DisableImageView.java b/core/java/com/android/internal/widget/DisableImageView.java new file mode 100644 index 000000000000..0d9bf7168ebf --- /dev/null +++ b/core/java/com/android/internal/widget/DisableImageView.java @@ -0,0 +1,68 @@ +/* + * 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.internal.widget; + +import android.annotation.Nullable; +import android.content.Context; +import android.graphics.ColorMatrix; +import android.graphics.ColorMatrixColorFilter; +import android.util.AttributeSet; +import android.widget.ImageView; +import android.widget.RemoteViews; + +/** + * ImageView which applies a saturation image filter, used by AppWidgets to represent disabled icon + */ +@RemoteViews.RemoteView +public class DisableImageView extends ImageView { + + public DisableImageView(Context context) { + this(context, null, 0, 0); + } + + public DisableImageView(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0, 0); + } + + public DisableImageView(Context context, @Nullable AttributeSet attrs, + int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public DisableImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + // Apply the disabled filter + ColorMatrix brightnessMatrix = new ColorMatrix(); + float brightnessF = 0.5f; + int brightnessI = (int) (255 * brightnessF); + // Brightness: C-new = C-old*(1-amount) + amount + float scale = 1f - brightnessF; + float[] mat = brightnessMatrix.getArray(); + mat[0] = scale; + mat[6] = scale; + mat[12] = scale; + mat[4] = brightnessI; + mat[9] = brightnessI; + mat[14] = brightnessI; + + ColorMatrix filterMatrix = new ColorMatrix(); + filterMatrix.setSaturation(0); + filterMatrix.preConcat(brightnessMatrix); + setColorFilter(new ColorMatrixColorFilter(filterMatrix)); + } +} diff --git a/core/java/com/android/internal/widget/OWNERS b/core/java/com/android/internal/widget/OWNERS index 3fc3933e9a1c..d284d5167843 100644 --- a/core/java/com/android/internal/widget/OWNERS +++ b/core/java/com/android/internal/widget/OWNERS @@ -11,6 +11,7 @@ per-file *Notification* = file:/services/core/java/com/android/server/notificati per-file *Messaging* = file:/services/core/java/com/android/server/notification/OWNERS per-file *Message* = file:/services/core/java/com/android/server/notification/OWNERS per-file *Conversation* = file:/services/core/java/com/android/server/notification/OWNERS +per-file *People* = file:/services/core/java/com/android/server/notification/OWNERS per-file *ImageResolver* = file:/services/core/java/com/android/server/notification/OWNERS per-file CallLayout.java = file:/services/core/java/com/android/server/notification/OWNERS per-file CachingIconView.java = file:/services/core/java/com/android/server/notification/OWNERS diff --git a/core/java/com/android/server/BootReceiver.java b/core/java/com/android/server/BootReceiver.java index b4d8e506c61a..95999a716707 100644 --- a/core/java/com/android/server/BootReceiver.java +++ b/core/java/com/android/server/BootReceiver.java @@ -23,7 +23,6 @@ import android.content.pm.IPackageManager; import android.os.Build; import android.os.DropBoxManager; import android.os.Environment; -import android.os.FileObserver; import android.os.FileUtils; import android.os.RecoverySystem; import android.os.RemoteException; @@ -74,7 +73,6 @@ public class BootReceiver extends BroadcastReceiver { SystemProperties.getInt("ro.debuggable", 0) == 1 ? 196608 : 65536; private static final int GMSCORE_LASTK_LOG_SIZE = 196608; - private static final File TOMBSTONE_DIR = new File("/data/tombstones"); private static final String TAG_TOMBSTONE = "SYSTEM_TOMBSTONE"; // The pre-froyo package and class of the system updater, which @@ -85,9 +83,6 @@ public class BootReceiver extends BroadcastReceiver { private static final String OLD_UPDATER_CLASS = "com.google.android.systemupdater.SystemUpdateReceiver"; - // Keep a reference to the observer so the finalizer doesn't disable it. - private static FileObserver sTombstoneObserver = null; - private static final String LOG_FILES_FILE = "log-files.xml"; private static final AtomicFile sFile = new AtomicFile(new File( Environment.getDataSystemDirectory(), LOG_FILES_FILE), "log-files"); @@ -153,7 +148,7 @@ public class BootReceiver extends BroadcastReceiver { Downloads.removeAllDownloadsByPackage(context, OLD_UPDATER_PACKAGE, OLD_UPDATER_CLASS); } - private String getPreviousBootHeaders() { + private static String getPreviousBootHeaders() { try { return FileUtils.readTextFile(lastHeaderFile, 0, null); } catch (IOException e) { @@ -161,7 +156,7 @@ public class BootReceiver extends BroadcastReceiver { } } - private String getCurrentBootHeaders() throws IOException { + private static String getCurrentBootHeaders() throws IOException { return new StringBuilder(512) .append("Build: ").append(Build.FINGERPRINT).append("\n") .append("Hardware: ").append(Build.BOARD).append("\n") @@ -175,7 +170,7 @@ public class BootReceiver extends BroadcastReceiver { } - private String getBootHeadersToLogAndUpdate() throws IOException { + private static String getBootHeadersToLogAndUpdate() throws IOException { final String oldHeaders = getPreviousBootHeaders(); final String newHeaders = getCurrentBootHeaders(); @@ -247,38 +242,27 @@ public class BootReceiver extends BroadcastReceiver { logFsMountTime(); addFsckErrorsToDropBoxAndLogFsStat(db, timestamps, headers, -LOG_SIZE, "SYSTEM_FSCK"); logSystemServerShutdownTimeMetrics(); + writeTimestamps(timestamps); + } - // Scan existing tombstones (in case any new ones appeared) - File[] tombstoneFiles = TOMBSTONE_DIR.listFiles(); - for (int i = 0; tombstoneFiles != null && i < tombstoneFiles.length; i++) { - if (tombstoneFiles[i].isFile()) { - addFileToDropBox(db, timestamps, headers, tombstoneFiles[i].getPath(), - LOG_SIZE, "SYSTEM_TOMBSTONE"); - } + /** + * Add a tombstone to the DropBox. + * + * @param ctx Context + * @param tombstone path to the tombstone + */ + public static void addTombstoneToDropBox(Context ctx, File tombstone) { + final DropBoxManager db = ctx.getSystemService(DropBoxManager.class); + final String bootReason = SystemProperties.get("ro.boot.bootreason", null); + HashMap<String, Long> timestamps = readTimestamps(); + try { + final String headers = getBootHeadersToLogAndUpdate(); + addFileToDropBox(db, timestamps, headers, tombstone.getPath(), LOG_SIZE, + TAG_TOMBSTONE); + } catch (IOException e) { + Slog.e(TAG, "Can't log tombstone", e); } - writeTimestamps(timestamps); - - // Start watching for new tombstone files; will record them as they occur. - // This gets registered with the singleton file observer thread. - sTombstoneObserver = new FileObserver(TOMBSTONE_DIR.getPath(), FileObserver.CREATE) { - @Override - public void onEvent(int event, String path) { - HashMap<String, Long> timestamps = readTimestamps(); - try { - File file = new File(TOMBSTONE_DIR, path); - if (file.isFile() && file.getName().startsWith("tombstone_")) { - addFileToDropBox(db, timestamps, headers, file.getPath(), LOG_SIZE, - TAG_TOMBSTONE); - } - } catch (IOException e) { - Slog.e(TAG, "Can't log tombstone", e); - } - writeTimestamps(timestamps); - } - }; - - sTombstoneObserver.startWatching(); } private static void addLastkToDropBox( @@ -761,7 +745,7 @@ public class BootReceiver extends BroadcastReceiver { } } - private void writeTimestamps(HashMap<String, Long> timestamps) { + private static void writeTimestamps(HashMap<String, Long> timestamps) { synchronized (sFile) { final FileOutputStream stream; try { diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 0b48e72a4acd..8edc8a186c59 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -207,9 +207,9 @@ cc_library_shared { ], shared_libs: [ - "audioclient-types-aidl-unstable-cpp", - "audioflinger-aidl-unstable-cpp", - "av-types-aidl-unstable-cpp", + "audioclient-types-aidl-cpp", + "audioflinger-aidl-cpp", + "av-types-aidl-cpp", "libandroidicu", "libbpf_android", "libnetdbpf", diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 05fcaec82f84..859730872e8c 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -1409,16 +1409,6 @@ static void nativeDeferTransactionUntil(JNIEnv* env, jclass clazz, jlong transac transaction->deferTransactionUntil_legacy(ctrl, barrier, frameNumber); } -static void nativeReparentChildren(JNIEnv* env, jclass clazz, jlong transactionObj, - jlong nativeObject, - jlong newParentObject) { - - auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject); - auto newParent = reinterpret_cast<SurfaceControl *>(newParentObject); - auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); - transaction->reparentChildren(ctrl, newParent); -} - static void nativeReparent(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject, jlong newParentObject) { @@ -1838,8 +1828,6 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeGetProtectedContentSupport }, {"nativeDeferTransactionUntil", "(JJJJ)V", (void*)nativeDeferTransactionUntil }, - {"nativeReparentChildren", "(JJJ)V", - (void*)nativeReparentChildren } , {"nativeReparent", "(JJJ)V", (void*)nativeReparent }, {"nativeCaptureDisplay", diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 269ccbbbfaec..827bf7b70cbc 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" /> @@ -5431,7 +5427,7 @@ <permission android:name="android.permission.INPUT_CONSUMER" android:protectionLevel="signature" /> - <!-- @hide Allows an application to control the system's device state managed by the + <!-- @hide @TestApi Allows an application to control the system's device state managed by the {@link android.service.devicestate.DeviceStateManagerService}. For example, on foldable devices this would grant access to toggle between the folded and unfolded states. --> <permission android:name="android.permission.CONTROL_DEVICE_STATE" diff --git a/core/res/res/layout/work_widget_mask_view.xml b/core/res/res/layout/work_widget_mask_view.xml index 39e1bbb467b4..9e1692f23a1e 100644 --- a/core/res/res/layout/work_widget_mask_view.xml +++ b/core/res/res/layout/work_widget_mask_view.xml @@ -22,7 +22,8 @@ Copyright (C) 2015 The Android Open Source Project android:importantForAccessibility="noHideDescendants" android:clickable="true"> - <ImageView android:id="@+id/work_widget_app_icon" + <com.android.internal.widget.DisableImageView + android:id="@+id/work_widget_app_icon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index eb867090f8ba..14df77527a08 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -8500,6 +8500,9 @@ interaction requests from an Activity. This flag is new in {@link android.os.Build.VERSION_CODES#N} and not used in previous versions. --> <attr name="supportsLocalInteraction" format="boolean" /> + <!-- The service that provides {@link android.service.voice.HotwordDetectionService}. + @hide @SystemApi --> + <attr name="hotwordDetectionService" format="string" /> </declare-styleable> <!-- Use <code>voice-enrollment-application</code> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index a7e8f2adab9c..30cfb8986035 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3059,6 +3059,8 @@ <public name="hand_second" /> <public name="memtagMode" /> <public name="nativeHeapZeroInit" /> + <!-- @hide @SystemApi --> + <public name="hotwordDetectionService" /> </public-group> <public-group type="drawable" first-id="0x010800b5"> diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java index 36f01f9a951d..e7fdfb8c19e9 100644 --- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java +++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java @@ -17,8 +17,11 @@ package android.hardware.devicestate; import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; import android.annotation.Nullable; +import android.os.IBinder; import android.os.RemoteException; import androidx.test.filters.SmallTest; @@ -30,6 +33,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.util.ArrayList; import java.util.HashSet; import java.util.Set; @@ -41,6 +45,9 @@ import java.util.Set; @RunWith(JUnit4.class) @SmallTest public final class DeviceStateManagerGlobalTest { + private static final int DEFAULT_DEVICE_STATE = 0; + private static final int OTHER_DEVICE_STATE = 1; + private TestDeviceStateManagerService mService; private DeviceStateManagerGlobal mDeviceStateManagerGlobal; @@ -52,7 +59,7 @@ public final class DeviceStateManagerGlobalTest { @Test public void registerListener() { - mService.setDeviceState(0); + mService.setBaseState(DEFAULT_DEVICE_STATE); TestDeviceStateListener listener1 = new TestDeviceStateListener(); TestDeviceStateListener listener2 = new TestDeviceStateListener(); @@ -61,28 +68,58 @@ public final class DeviceStateManagerGlobalTest { ConcurrentUtils.DIRECT_EXECUTOR); mDeviceStateManagerGlobal.registerDeviceStateListener(listener2, ConcurrentUtils.DIRECT_EXECUTOR); - assertEquals(0, listener1.getLastReportedState().intValue()); - assertEquals(0, listener2.getLastReportedState().intValue()); + assertEquals(DEFAULT_DEVICE_STATE, listener1.getLastReportedState().intValue()); + assertEquals(DEFAULT_DEVICE_STATE, listener2.getLastReportedState().intValue()); - mService.setDeviceState(1); - assertEquals(1, listener1.getLastReportedState().intValue()); - assertEquals(1, listener2.getLastReportedState().intValue()); + mService.setBaseState(OTHER_DEVICE_STATE); + assertEquals(OTHER_DEVICE_STATE, listener1.getLastReportedState().intValue()); + assertEquals(OTHER_DEVICE_STATE, listener2.getLastReportedState().intValue()); } @Test public void unregisterListener() { - mService.setDeviceState(0); + mService.setBaseState(DEFAULT_DEVICE_STATE); TestDeviceStateListener listener = new TestDeviceStateListener(); mDeviceStateManagerGlobal.registerDeviceStateListener(listener, ConcurrentUtils.DIRECT_EXECUTOR); - assertEquals(0, listener.getLastReportedState().intValue()); + assertEquals(DEFAULT_DEVICE_STATE, listener.getLastReportedState().intValue()); mDeviceStateManagerGlobal.unregisterDeviceStateListener(listener); - mService.setDeviceState(1); - assertEquals(0, listener.getLastReportedState().intValue()); + mService.setBaseState(OTHER_DEVICE_STATE); + assertEquals(DEFAULT_DEVICE_STATE, listener.getLastReportedState().intValue()); + } + + @Test + public void submittingRequestRegisteredCallback() { + assertTrue(mService.mCallbacks.isEmpty()); + + DeviceStateRequest request = DeviceStateRequest.newBuilder(DEFAULT_DEVICE_STATE).build(); + mDeviceStateManagerGlobal.requestState(request, null /* executor */, null /* callback */); + + assertFalse(mService.mCallbacks.isEmpty()); + } + + @Test + public void submitRequest() { + mService.setBaseState(DEFAULT_DEVICE_STATE); + + TestDeviceStateListener listener = new TestDeviceStateListener(); + mDeviceStateManagerGlobal.registerDeviceStateListener(listener, + ConcurrentUtils.DIRECT_EXECUTOR); + + assertEquals(DEFAULT_DEVICE_STATE, listener.getLastReportedState().intValue()); + + DeviceStateRequest request = DeviceStateRequest.newBuilder(OTHER_DEVICE_STATE).build(); + mDeviceStateManagerGlobal.requestState(request, null /* executor */, null /* callback */); + + assertEquals(OTHER_DEVICE_STATE, listener.getLastReportedState().intValue()); + + mDeviceStateManagerGlobal.cancelRequest(request); + + assertEquals(DEFAULT_DEVICE_STATE, listener.getLastReportedState().intValue()); } private final class TestDeviceStateListener implements DeviceStateManager.DeviceStateListener { @@ -100,8 +137,23 @@ public final class DeviceStateManagerGlobalTest { } } - private final class TestDeviceStateManagerService extends IDeviceStateManager.Stub { - private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE; + private static final class TestDeviceStateManagerService extends IDeviceStateManager.Stub { + public static final class Request { + public final IBinder token; + public final int state; + public final int flags; + + private Request(IBinder token, int state, int flags) { + this.token = token; + this.state = state; + this.flags = flags; + } + } + + private int mBaseState = DEFAULT_DEVICE_STATE; + private int mMergedState = DEFAULT_DEVICE_STATE; + private ArrayList<Request> mRequests = new ArrayList<>(); + private Set<IDeviceStateManagerCallback> mCallbacks = new HashSet<>(); @Override @@ -112,19 +164,86 @@ public final class DeviceStateManagerGlobalTest { mCallbacks.add(callback); try { - callback.onDeviceStateChanged(mDeviceState); + callback.onDeviceStateChanged(mMergedState); } catch (RemoteException e) { // Do nothing. Should never happen. } } - public void setDeviceState(int deviceState) { - boolean stateChanged = mDeviceState != deviceState; - mDeviceState = deviceState; - if (stateChanged) { + @Override + public int[] getSupportedDeviceStates() { + return new int[] { DEFAULT_DEVICE_STATE, OTHER_DEVICE_STATE }; + } + + @Override + public void requestState(IBinder token, int state, int flags) { + if (!mRequests.isEmpty()) { + final Request topRequest = mRequests.get(mRequests.size() - 1); + for (IDeviceStateManagerCallback callback : mCallbacks) { + try { + callback.onRequestSuspended(topRequest.token); + } catch (RemoteException e) { + // Do nothing. Should never happen. + } + } + } + + final Request request = new Request(token, state, flags); + mRequests.add(request); + notifyStateChangedIfNeeded(); + + for (IDeviceStateManagerCallback callback : mCallbacks) { + try { + callback.onRequestActive(token); + } catch (RemoteException e) { + // Do nothing. Should never happen. + } + } + } + + @Override + public void cancelRequest(IBinder token) { + int index = -1; + for (int i = 0; i < mRequests.size(); i++) { + if (mRequests.get(i).token.equals(token)) { + index = i; + break; + } + } + + if (index == -1) { + throw new IllegalArgumentException("Unknown request: " + token); + } + + mRequests.remove(index); + for (IDeviceStateManagerCallback callback : mCallbacks) { + try { + callback.onRequestCanceled(token); + } catch (RemoteException e) { + // Do nothing. Should never happen. + } + } + notifyStateChangedIfNeeded(); + } + + public void setBaseState(int state) { + mBaseState = state; + notifyStateChangedIfNeeded(); + } + + private void notifyStateChangedIfNeeded() { + final int originalMergedState = mMergedState; + + if (!mRequests.isEmpty()) { + mMergedState = mRequests.get(mRequests.size() - 1).state; + } else { + mMergedState = mBaseState; + } + + if (mMergedState != originalMergedState) { for (IDeviceStateManagerCallback callback : mCallbacks) { try { - callback.onDeviceStateChanged(mDeviceState); + callback.onDeviceStateChanged(mMergedState); } catch (RemoteException e) { // Do nothing. Should never happen. } 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/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/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/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java index fa4f8b1674d1..3ebca6ad302d 100644 --- a/keystore/java/android/security/keystore/KeyProperties.java +++ b/keystore/java/android/security/keystore/KeyProperties.java @@ -20,6 +20,8 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringDef; +import android.annotation.SystemApi; +import android.os.Process; import android.security.KeyStore; import android.security.keymaster.KeymasterDefs; @@ -874,9 +876,18 @@ public abstract class KeyProperties { * which it must be configured in SEPolicy. * @hide */ + @SystemApi public static final int NAMESPACE_APPLICATION = -1; /** + * The namespace identifier for the WIFI Keystore namespace. + * This must be kept in sync with system/sepolicy/private/keystore2_key_contexts + * @hide + */ + @SystemApi + public static final int NAMESPACE_WIFI = 102; + + /** * For legacy support, translate namespaces into known UIDs. * @hide */ @@ -884,6 +895,8 @@ public abstract class KeyProperties { switch (namespace) { case NAMESPACE_APPLICATION: return KeyStore.UID_SELF; + case NAMESPACE_WIFI: + return Process.WIFI_UID; // TODO Translate WIFI and VPN UIDs once the namespaces are defined. // b/171305388 and b/171305607 default: @@ -900,6 +913,8 @@ public abstract class KeyProperties { switch (uid) { case KeyStore.UID_SELF: return NAMESPACE_APPLICATION; + case Process.WIFI_UID: + return NAMESPACE_WIFI; // TODO Translate WIFI and VPN UIDs once the namespaces are defined. // b/171305388 and b/171305607 default: diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java index 75ac61a22cab..e1011155248e 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java @@ -352,14 +352,17 @@ public class AndroidKeyStoreProvider extends Provider { try { response = keyStore.getKeyEntry(descriptor); } catch (android.security.KeyStoreException e) { - if (e.getErrorCode() == ResponseCode.KEY_PERMANENTLY_INVALIDATED) { - throw new KeyPermanentlyInvalidatedException( - "User changed or deleted their auth credentials", - e); - } else { - throw (UnrecoverableKeyException) - new UnrecoverableKeyException("Failed to obtain information about key") - .initCause(e); + switch (e.getErrorCode()) { + case ResponseCode.KEY_NOT_FOUND: + return null; + case ResponseCode.KEY_PERMANENTLY_INVALIDATED: + throw new KeyPermanentlyInvalidatedException( + "User changed or deleted their auth credentials", + e); + default: + throw (UnrecoverableKeyException) + new UnrecoverableKeyException("Failed to obtain information about key") + .initCause(e); } } 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..d54be0e62527 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); @@ -1662,6 +1665,7 @@ public class BubbleStackView extends FrameLayout } beforeExpandedViewAnimation(); + updateBadgesAndZOrder(false /* setBadgeForCollapsedStack */); mBubbleContainer.setActiveController(mExpandedAnimationController); updateOverflowVisibility(); updatePointerPosition(); @@ -1875,7 +1879,7 @@ public class BubbleStackView extends FrameLayout mExpandedBubble)); } updateOverflowVisibility(); - + updateBadgesAndZOrder(true /* setBadgeForCollapsedStack */); afterExpandedViewAnimation(); if (previouslySelected != null) { previouslySelected.setContentVisibility(false); @@ -2623,7 +2627,6 @@ public class BubbleStackView extends FrameLayout } mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide(); - updateBadgesAndZOrder(false /* setBadgeForCollapsedStack */); } /** 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/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/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index 8525e9979aef..ee0be010c233 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -65,6 +65,7 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; @@ -245,6 +246,8 @@ public class Tuner implements AutoCloseable { private static final int MSG_ON_FILTER_STATUS = 3; private static final int MSG_ON_LNB_EVENT = 4; + private static final int FILTER_CLEANUP_THRESHOLD = 256; + /** @hide */ @IntDef(prefix = "DVR_TYPE_", value = {DVR_TYPE_RECORD, DVR_TYPE_PLAYBACK}) @Retention(RetentionPolicy.SOURCE) @@ -1208,6 +1211,15 @@ public class Tuner implements AutoCloseable { synchronized (mFilters) { WeakReference<Filter> weakFilter = new WeakReference<Filter>(filter); mFilters.add(weakFilter); + if (mFilters.size() > FILTER_CLEANUP_THRESHOLD) { + Iterator<WeakReference<Filter>> iterator = mFilters.iterator(); + while (iterator.hasNext()) { + WeakReference<Filter> wFilter = iterator.next(); + if (wFilter.get() == null) { + iterator.remove(); + } + } + } } } return filter; diff --git a/media/jni/Android.bp b/media/jni/Android.bp index 67a2c49e5746..65b64d7e8df3 100644 --- a/media/jni/Android.bp +++ b/media/jni/Android.bp @@ -31,8 +31,8 @@ cc_library_shared { ], shared_libs: [ - "audioclient-types-aidl-unstable-cpp", - "av-types-aidl-unstable-cpp", + "audioclient-types-aidl-cpp", + "av-types-aidl-cpp", "libandroid_runtime", "libaudioclient", "libnativehelper", 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 6b788170a944..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" @@ -34,7 +35,7 @@ using ::aidl::android::media::tv::tuner::TunerFilterSectionTableInfo; using ::aidl::android::media::tv::tuner::TunerFilterSharedHandleInfo; using ::aidl::android::media::tv::tuner::TunerFilterTlvConfiguration; using ::aidl::android::media::tv::tuner::TunerFilterTsConfiguration; - +using ::android::hardware::hidl_vec; using ::android::hardware::tv::tuner::V1_0::DemuxFilterMainType; using ::android::hardware::tv::tuner::V1_0::DemuxMmtpFilterType; using ::android::hardware::tv::tuner::V1_0::DemuxQueueNotifyBits; @@ -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(); @@ -687,10 +703,10 @@ void TunerFilterCallback::getHidlFilterEvent(const vector<TunerFilterEvent>& fil void TunerFilterCallback::getHidlMediaEvent( const vector<TunerFilterEvent>& filterEvents, DemuxFilterEvent& event) { + event.events.resize(filterEvents.size()); for (int i = 0; i < filterEvents.size(); i++) { hidl_handle handle = hidl_handle(makeFromAidl(filterEvents[i] .get<TunerFilterEvent::media>().avMemory)); - event.events.resize(i + 1); event.events[i].media({ .avMemory = handle, .streamId = static_cast<DemuxStreamId>(filterEvents[i] @@ -736,9 +752,9 @@ void TunerFilterCallback::getHidlMediaEvent( void TunerFilterCallback::getHidlSectionEvent( const vector<TunerFilterEvent>& filterEvents, DemuxFilterEvent& event) { + event.events.resize(filterEvents.size()); for (int i = 0; i < filterEvents.size(); i++) { auto section = filterEvents[i].get<TunerFilterEvent::section>(); - event.events.resize(i + 1); event.events[i].section({ .tableId = static_cast<uint16_t>(section.tableId), .version = static_cast<uint16_t>(section.version), @@ -750,9 +766,9 @@ void TunerFilterCallback::getHidlSectionEvent( void TunerFilterCallback::getHidlPesEvent( const vector<TunerFilterEvent>& filterEvents, DemuxFilterEvent& event) { + event.events.resize(filterEvents.size()); for (int i = 0; i < filterEvents.size(); i++) { auto pes = filterEvents[i].get<TunerFilterEvent::pes>(); - event.events.resize(i + 1); event.events[i].pes({ .streamId = static_cast<DemuxStreamId>(pes.streamId), .dataLength = static_cast<uint16_t>(pes.dataLength), @@ -763,9 +779,10 @@ void TunerFilterCallback::getHidlPesEvent( void TunerFilterCallback::getHidlTsRecordEvent(const vector<TunerFilterEvent>& filterEvents, DemuxFilterEvent& event, DemuxFilterEventExt& eventExt) { + event.events.resize(filterEvents.size()); + eventExt.events.resize(filterEvents.size()); for (int i = 0; i < filterEvents.size(); i++) { auto ts = filterEvents[i].get<TunerFilterEvent::tsRecord>(); - event.events.resize(i + 1); event.events[i].tsRecord({ .tsIndexMask = static_cast<uint32_t>(ts.tsIndexMask), .byteNumber = static_cast<uint64_t>(ts.byteNumber), @@ -787,7 +804,6 @@ void TunerFilterCallback::getHidlTsRecordEvent(const vector<TunerFilterEvent>& f break; } - eventExt.events.resize(i + 1); if (ts.isExtended) { eventExt.events[i].tsRecord({ .pts = static_cast<uint64_t>(ts.pts), @@ -801,15 +817,15 @@ void TunerFilterCallback::getHidlTsRecordEvent(const vector<TunerFilterEvent>& f void TunerFilterCallback::getHidlMmtpRecordEvent(const vector<TunerFilterEvent>& filterEvents, DemuxFilterEvent& event, DemuxFilterEventExt& eventExt) { + event.events.resize(filterEvents.size()); + eventExt.events.resize(filterEvents.size()); for (int i = 0; i < filterEvents.size(); i++) { auto mmtp = filterEvents[i].get<TunerFilterEvent::mmtpRecord>(); - event.events.resize(i + 1); event.events[i].mmtpRecord({ .scHevcIndexMask = static_cast<uint32_t>(mmtp.scHevcIndexMask), .byteNumber = static_cast<uint64_t>(mmtp.byteNumber), }); - eventExt.events.resize(i + 1); if (mmtp.isExtended) { eventExt.events[i].mmtpRecord({ .pts = static_cast<uint64_t>(mmtp.pts), @@ -825,9 +841,9 @@ void TunerFilterCallback::getHidlMmtpRecordEvent(const vector<TunerFilterEvent>& void TunerFilterCallback::getHidlDownloadEvent(const vector<TunerFilterEvent>& filterEvents, DemuxFilterEvent& event) { + event.events.resize(filterEvents.size()); for (int i = 0; i < filterEvents.size(); i++) { auto download = filterEvents[i].get<TunerFilterEvent::download>(); - event.events.resize(i + 1); event.events[i].download({ .itemId = static_cast<uint32_t>(download.itemId), .mpuSequenceNumber = static_cast<uint32_t>(download.mpuSequenceNumber), @@ -840,9 +856,9 @@ void TunerFilterCallback::getHidlDownloadEvent(const vector<TunerFilterEvent>& f void TunerFilterCallback::getHidlIpPayloadEvent(const vector<TunerFilterEvent>& filterEvents, DemuxFilterEvent& event) { + event.events.resize(filterEvents.size()); for (int i = 0; i < filterEvents.size(); i++) { auto ip = filterEvents[i].get<TunerFilterEvent::ipPayload>(); - event.events.resize(i + 1); event.events[i].ipPayload({ .dataLength = static_cast<uint16_t>(ip.dataLength), }); @@ -851,15 +867,15 @@ void TunerFilterCallback::getHidlIpPayloadEvent(const vector<TunerFilterEvent>& void TunerFilterCallback::getHidlTemiEvent(const vector<TunerFilterEvent>& filterEvents, DemuxFilterEvent& event) { + event.events.resize(filterEvents.size()); for (int i = 0; i < filterEvents.size(); i++) { auto temi = filterEvents[i].get<TunerFilterEvent::temi>(); - event.events.resize(i + 1); event.events[i].temi({ .pts = static_cast<uint64_t>(temi.pts), .descrTag = static_cast<uint8_t>(temi.descrTag), }); - vector<uint8_t> descrData(temi.descrData.size()); - copy(temi.descrData.begin(), temi.descrData.end(), descrData.begin()); + hidl_vec<uint8_t> descrData(temi.descrData.begin(), temi.descrData.end()); + event.events[i].temi().descrData = descrData; } } @@ -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/FrontendClient.cpp b/media/jni/tuner/FrontendClient.cpp index 08573a61c623..f454907851a4 100644 --- a/media/jni/tuner/FrontendClient.cpp +++ b/media/jni/tuner/FrontendClient.cpp @@ -27,23 +27,43 @@ using ::aidl::android::media::tv::tuner::TunerFrontendUnionSettings; using ::android::hardware::tv::tuner::V1_0::FrontendAnalogSifStandard; using ::android::hardware::tv::tuner::V1_0::FrontendAnalogType; using ::android::hardware::tv::tuner::V1_0::FrontendAtscModulation; +using ::android::hardware::tv::tuner::V1_0::FrontendAtsc3Bandwidth; using ::android::hardware::tv::tuner::V1_0::FrontendAtsc3Modulation; +using ::android::hardware::tv::tuner::V1_0::FrontendAtsc3TimeInterleaveMode; using ::android::hardware::tv::tuner::V1_0::FrontendDvbcAnnex; using ::android::hardware::tv::tuner::V1_0::FrontendDvbcModulation; +using ::android::hardware::tv::tuner::V1_0::FrontendDvbcSpectralInversion; using ::android::hardware::tv::tuner::V1_0::FrontendDvbsModulation; using ::android::hardware::tv::tuner::V1_0::FrontendDvbsStandard; +using ::android::hardware::tv::tuner::V1_0::FrontendDvbsRolloff; +using ::android::hardware::tv::tuner::V1_0::FrontendDvbtBandwidth; +using ::android::hardware::tv::tuner::V1_0::FrontendDvbtGuardInterval; using ::android::hardware::tv::tuner::V1_0::FrontendDvbtHierarchy; using ::android::hardware::tv::tuner::V1_0::FrontendDvbtStandard; +using ::android::hardware::tv::tuner::V1_0::FrontendInnerFec; using ::android::hardware::tv::tuner::V1_0::FrontendIsdbsModulation; +using ::android::hardware::tv::tuner::V1_0::FrontendIsdbsRolloff; using ::android::hardware::tv::tuner::V1_0::FrontendIsdbs3Modulation; +using ::android::hardware::tv::tuner::V1_0::FrontendIsdbs3Rolloff; +using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtBandwidth; +using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtGuardInterval; +using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtMode; using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtModulation; using ::android::hardware::tv::tuner::V1_0::FrontendScanAtsc3PlpInfo; -using ::android::hardware::tv::tuner::V1_0::FrontendType; +using ::android::hardware::tv::tuner::V1_0::LnbVoltage; using ::android::hardware::tv::tuner::V1_1::Constant; +using ::android::hardware::tv::tuner::V1_1::FrontendCableTimeInterleaveMode; +using ::android::hardware::tv::tuner::V1_1::FrontendDtmbBandwidth; +using ::android::hardware::tv::tuner::V1_1::FrontendDtmbGuardInterval; using ::android::hardware::tv::tuner::V1_1::FrontendDtmbModulation; +using ::android::hardware::tv::tuner::V1_1::FrontendDtmbTimeInterleaveMode; +using ::android::hardware::tv::tuner::V1_1::FrontendDtmbTransmissionMode; +using ::android::hardware::tv::tuner::V1_1::FrontendDvbcBandwidth; using ::android::hardware::tv::tuner::V1_1::FrontendDvbtConstellation; +using ::android::hardware::tv::tuner::V1_1::FrontendDvbtTransmissionMode; using ::android::hardware::tv::tuner::V1_1::FrontendModulation; using ::android::hardware::tv::tuner::V1_1::FrontendSpectralInversion; +using ::android::hardware::tv::tuner::V1_1::FrontendType; namespace android { @@ -160,9 +180,16 @@ vector<FrontendStatus> FrontendClient::getStatus(vector<FrontendStatusType> stat vector<FrontendStatus> status; if (mTunerFrontend != NULL) { - // TODO: handle error message. - /*status = mTunerFrontend->getStatus(statusTypes); - return status;*/ + vector<TunerFrontendStatus> aidlStatus; + vector<int> types; + for (auto t : statusTypes) { + types.push_back((int)t); + } + Status s = mTunerFrontend->getStatus(types, &aidlStatus); + if (ClientHelper::getServiceSpecificErrorCode(s) != Result::SUCCESS) { + return status; + } + return getHidlStatus(aidlStatus); } if (mFrontend != NULL && statusTypes.size() > 0) { @@ -180,14 +207,22 @@ vector<FrontendStatus> FrontendClient::getStatus(vector<FrontendStatusType> stat return status; } + vector<FrontendStatusExt1_1> FrontendClient::getStatusExtended_1_1( vector<FrontendStatusTypeExt1_1> statusTypes) { vector<FrontendStatusExt1_1> status; if (mTunerFrontend != NULL) { - // TODO: handle error message. - /*status = mTunerFrontend->getStatusExtended_1_1(statusTypes); - return status;*/ + vector<TunerFrontendStatus> aidlStatus; + vector<int> types; + for (auto t : statusTypes) { + types.push_back((int)t); + } + Status s = mTunerFrontend->getStatusExtended_1_1(types, &aidlStatus); + if (ClientHelper::getServiceSpecificErrorCode(s) != Result::SUCCESS) { + return status; + } + return getHidlStatusExt(aidlStatus); } if (mFrontend_1_1 != NULL && statusTypes.size() > 0) { @@ -302,6 +337,400 @@ int FrontendClient::getId() { return mId; } +vector<FrontendStatus> FrontendClient::getHidlStatus(vector<TunerFrontendStatus>& aidlStatus) { + vector<FrontendStatus> hidlStatus; + for (TunerFrontendStatus s : aidlStatus) { + FrontendStatus status; + switch (s.getTag()) { + case TunerFrontendStatus::isDemodLocked: { + status.isDemodLocked(s.get<TunerFrontendStatus::isDemodLocked>()); + hidlStatus.push_back(status); + break; + } + case TunerFrontendStatus::snr: { + status.snr(s.get<TunerFrontendStatus::snr>()); + hidlStatus.push_back(status); + break; + } + case TunerFrontendStatus::ber: { + status.ber((uint32_t)s.get<TunerFrontendStatus::ber>()); + hidlStatus.push_back(status); + break; + } + case TunerFrontendStatus::per: { + status.per((uint32_t)s.get<TunerFrontendStatus::per>()); + hidlStatus.push_back(status); + break; + } + case TunerFrontendStatus::preBer: { + status.preBer((uint32_t)s.get<TunerFrontendStatus::preBer>()); + hidlStatus.push_back(status); + break; + } + case TunerFrontendStatus::signalQuality: { + status.signalQuality((uint32_t)s.get<TunerFrontendStatus::signalQuality>()); + hidlStatus.push_back(status); + break; + } + case TunerFrontendStatus::signalStrength: { + status.signalStrength(s.get<TunerFrontendStatus::signalStrength>()); + hidlStatus.push_back(status); + break; + } + case TunerFrontendStatus::symbolRate: { + status.symbolRate((uint32_t)s.get<TunerFrontendStatus::symbolRate>()); + hidlStatus.push_back(status); + break; + } + case TunerFrontendStatus::innerFec: { + status.innerFec(static_cast<FrontendInnerFec>( + s.get<TunerFrontendStatus::innerFec>())); + hidlStatus.push_back(status); + break; + } + case TunerFrontendStatus::modulation: { + auto aidlMod = s.get<TunerFrontendStatus::modulation>(); + switch (mType) { + case (int)FrontendType::DVBC: + status.modulation().dvbc(static_cast<FrontendDvbcModulation>(aidlMod)); + hidlStatus.push_back(status); + break; + case (int)FrontendType::DVBS: + status.modulation().dvbs(static_cast<FrontendDvbsModulation>(aidlMod)); + hidlStatus.push_back(status); + break; + case (int)FrontendType::ISDBS: + status.modulation().isdbs(static_cast<FrontendIsdbsModulation>(aidlMod)); + hidlStatus.push_back(status); + break; + case (int)FrontendType::ISDBS3: + status.modulation().isdbs3(static_cast<FrontendIsdbs3Modulation>(aidlMod)); + hidlStatus.push_back(status); + break; + case (int)FrontendType::ISDBT: + status.modulation().isdbt(static_cast<FrontendIsdbtModulation>(aidlMod)); + hidlStatus.push_back(status); + break; + default: + break; + } + break; + } + case TunerFrontendStatus::inversion: { + status.inversion(static_cast<FrontendDvbcSpectralInversion>( + s.get<TunerFrontendStatus::inversion>())); + hidlStatus.push_back(status); + break; + } + case TunerFrontendStatus::lnbVoltage: { + status.lnbVoltage(static_cast<LnbVoltage>( + s.get<TunerFrontendStatus::lnbVoltage>())); + hidlStatus.push_back(status); + break; + } + case TunerFrontendStatus::plpId: { + status.plpId((uint8_t)s.get<TunerFrontendStatus::plpId>()); + hidlStatus.push_back(status); + break; + } + case TunerFrontendStatus::isEWBS: { + status.isEWBS(s.get<TunerFrontendStatus::isEWBS>()); + hidlStatus.push_back(status); + break; + } + case TunerFrontendStatus::agc: { + status.agc((uint8_t)s.get<TunerFrontendStatus::agc>()); + hidlStatus.push_back(status); + break; + } + case TunerFrontendStatus::isLnaOn: { + status.isLnaOn(s.get<TunerFrontendStatus::isLnaOn>()); + hidlStatus.push_back(status); + break; + } + case TunerFrontendStatus::isLayerError: { + auto aidlE = s.get<TunerFrontendStatus::isLayerError>(); + hidl_vec<bool> e(aidlE.begin(), aidlE.end()); + status.isLayerError(e); + hidlStatus.push_back(status); + break; + } + case TunerFrontendStatus::mer: { + status.mer(s.get<TunerFrontendStatus::mer>()); + hidlStatus.push_back(status); + break; + } + case TunerFrontendStatus::freqOffset: { + status.freqOffset(s.get<TunerFrontendStatus::freqOffset>()); + hidlStatus.push_back(status); + break; + } + case TunerFrontendStatus::hierarchy: { + status.hierarchy(static_cast<FrontendDvbtHierarchy>( + s.get<TunerFrontendStatus::freqOffset>())); + hidlStatus.push_back(status); + break; + } + case TunerFrontendStatus::isRfLocked: { + status.isRfLocked(s.get<TunerFrontendStatus::isRfLocked>()); + hidlStatus.push_back(status); + break; + } + case TunerFrontendStatus::plpInfo: { + int size = s.get<TunerFrontendStatus::plpInfo>().size(); + status.plpInfo().resize(size); + for (int i = 0; i < size; i++) { + auto aidlInfo = s.get<TunerFrontendStatus::plpInfo>()[i]; + status.plpInfo()[i] = { + .plpId = (uint8_t)aidlInfo.plpId, + .isLocked = aidlInfo.isLocked, + .uec = (uint32_t)aidlInfo.uec, + }; + } + hidlStatus.push_back(status); + break; + } + default: + break; + } + } + return hidlStatus; +} + +vector<FrontendStatusExt1_1> FrontendClient::getHidlStatusExt( + vector<TunerFrontendStatus>& aidlStatus) { + vector<FrontendStatusExt1_1> hidlStatus; + for (TunerFrontendStatus s : aidlStatus) { + FrontendStatusExt1_1 status; + switch (s.getTag()) { + case TunerFrontendStatus::modulations: { + for (auto aidlMod : s.get<TunerFrontendStatus::modulations>()) { + int size = status.modulations().size(); + status.modulations().resize(size + 1); + switch (mType) { + case (int)FrontendType::DVBC: + status.modulations()[size].dvbc( + static_cast<FrontendDvbcModulation>(aidlMod)); + break; + case (int)FrontendType::DVBS: + status.modulations()[size].dvbs( + static_cast<FrontendDvbsModulation>(aidlMod)); + break; + case (int)FrontendType::DVBT: + status.modulations()[size].dvbt( + static_cast<FrontendDvbtConstellation>(aidlMod)); + break; + case (int)FrontendType::ISDBS: + status.modulations()[size].isdbs( + static_cast<FrontendIsdbsModulation>(aidlMod)); + break; + case (int)FrontendType::ISDBS3: + status.modulations()[size].isdbs3( + static_cast<FrontendIsdbs3Modulation>(aidlMod)); + break; + case (int)FrontendType::ISDBT: + status.modulations()[size].isdbt( + static_cast<FrontendIsdbtModulation>(aidlMod)); + break; + case (int)FrontendType::ATSC: + status.modulations()[size].atsc( + static_cast<FrontendAtscModulation>(aidlMod)); + break; + case (int)FrontendType::ATSC3: + status.modulations()[size].atsc3( + static_cast<FrontendAtsc3Modulation>(aidlMod)); + break; + case (int)FrontendType::DTMB: + status.modulations()[size].dtmb( + static_cast<FrontendDtmbModulation>(aidlMod)); + break; + default: + status.modulations().resize(size); + break; + } + } + hidlStatus.push_back(status); + break; + } + case TunerFrontendStatus::bers: { + auto aidlB = s.get<TunerFrontendStatus::bers>(); + hidl_vec<uint32_t> b(aidlB.begin(), aidlB.end()); + status.bers(b); + hidlStatus.push_back(status); + break; + } + case TunerFrontendStatus::codeRates: { + int size = s.get<TunerFrontendStatus::codeRates>().size(); + status.codeRates().resize(size); + for (int i = 0; i < size; i++) { + auto aidlCodeRate = s.get<TunerFrontendStatus::codeRates>()[i]; + status.codeRates()[i] = + static_cast<hardware::tv::tuner::V1_1::FrontendInnerFec>(aidlCodeRate); + } + hidlStatus.push_back(status); + break; + } + case TunerFrontendStatus::bandwidth: { + auto aidlBand = s.get<TunerFrontendStatus::bandwidth>(); + switch (mType) { + case (int)FrontendType::ATSC3: + status.bandwidth().atsc3(static_cast<FrontendAtsc3Bandwidth>(aidlBand)); + hidlStatus.push_back(status); + break; + case (int)FrontendType::DVBC: + status.bandwidth().dvbc(static_cast<FrontendDvbcBandwidth>(aidlBand)); + hidlStatus.push_back(status); + break; + case (int)FrontendType::DVBT: + status.bandwidth().dvbt(static_cast<FrontendDvbtBandwidth>(aidlBand)); + hidlStatus.push_back(status); + break; + case (int)FrontendType::ISDBT: + status.bandwidth().isdbt(static_cast<FrontendIsdbtBandwidth>(aidlBand)); + hidlStatus.push_back(status); + break; + case (int)FrontendType::DTMB: + status.bandwidth().dtmb(static_cast<FrontendDtmbBandwidth>(aidlBand)); + hidlStatus.push_back(status); + break; + default: + break; + } + break; + } + case TunerFrontendStatus::interval: { + auto aidlInter = s.get<TunerFrontendStatus::interval>(); + switch (mType) { + case (int)FrontendType::DVBT: + status.interval().dvbt(static_cast<FrontendDvbtGuardInterval>(aidlInter)); + hidlStatus.push_back(status); + break; + case (int)FrontendType::ISDBT: + status.interval().isdbt(static_cast<FrontendIsdbtGuardInterval>(aidlInter)); + hidlStatus.push_back(status); + break; + case (int)FrontendType::DTMB: + status.interval().dtmb(static_cast<FrontendDtmbGuardInterval>(aidlInter)); + hidlStatus.push_back(status); + break; + default: + break; + } + break; + } + case TunerFrontendStatus::transmissionMode: { + auto aidlTran = s.get<TunerFrontendStatus::transmissionMode>(); + switch (mType) { + case (int)FrontendType::DVBT: + status.transmissionMode().dvbt( + static_cast<FrontendDvbtTransmissionMode>(aidlTran)); + hidlStatus.push_back(status); + break; + case (int)FrontendType::ISDBT: + status.transmissionMode().isdbt(static_cast<FrontendIsdbtMode>(aidlTran)); + hidlStatus.push_back(status); + break; + case (int)FrontendType::DTMB: + status.transmissionMode().dtmb( + static_cast<FrontendDtmbTransmissionMode>(aidlTran)); + hidlStatus.push_back(status); + break; + default: + break; + } + break; + } + case TunerFrontendStatus::uec: { + status.uec((uint32_t)s.get<TunerFrontendStatus::uec>()); + hidlStatus.push_back(status); + break; + } + case TunerFrontendStatus::systemId: { + status.systemId((uint16_t)s.get<TunerFrontendStatus::systemId>()); + hidlStatus.push_back(status); + break; + } + case TunerFrontendStatus::interleaving: { + for (auto aidlInter : s.get<TunerFrontendStatus::interleaving>()) { + int size = status.interleaving().size(); + status.interleaving().resize(size + 1); + switch (mType) { + case (int)FrontendType::DVBC: + status.interleaving()[size].dvbc( + static_cast<FrontendCableTimeInterleaveMode>(aidlInter)); + break; + case (int)FrontendType::ATSC3: + status.interleaving()[size].atsc3( + static_cast<FrontendAtsc3TimeInterleaveMode>(aidlInter)); + break; + case (int)FrontendType::DTMB: + status.interleaving()[size].dtmb( + static_cast<FrontendDtmbTimeInterleaveMode>(aidlInter)); + break; + default: + status.interleaving().resize(size); + break; + } + } + hidlStatus.push_back(status); + break; + } + case TunerFrontendStatus::isdbtSegment: { + auto aidlSeg = s.get<TunerFrontendStatus::isdbtSegment>(); + hidl_vec<uint8_t> s(aidlSeg.begin(), aidlSeg.end()); + status.isdbtSegment(s); + hidlStatus.push_back(status); + break; + } + case TunerFrontendStatus::tsDataRate: { + auto aidlTs = s.get<TunerFrontendStatus::tsDataRate>(); + hidl_vec<uint32_t> ts(aidlTs.begin(), aidlTs.end()); + status.tsDataRate(ts); + hidlStatus.push_back(status); + break; + } + case TunerFrontendStatus::rollOff: { + auto aidlRoll = s.get<TunerFrontendStatus::rollOff>(); + switch (mType) { + case (int)FrontendType::DVBS: + status.rollOff().dvbs(static_cast<FrontendDvbsRolloff>(aidlRoll)); + hidlStatus.push_back(status); + break; + case (int)FrontendType::ISDBS: + status.rollOff().isdbs(static_cast<FrontendIsdbsRolloff>(aidlRoll)); + hidlStatus.push_back(status); + break; + case (int)FrontendType::ISDBS3: + status.rollOff().isdbs3(static_cast<FrontendIsdbs3Rolloff>(aidlRoll)); + hidlStatus.push_back(status); + break; + default: + break; + } + break; + } + case TunerFrontendStatus::isMiso: { + status.isMiso(s.get<TunerFrontendStatus::isMiso>()); + hidlStatus.push_back(status); + break; + } + case TunerFrontendStatus::isLinear: { + status.isLinear(s.get<TunerFrontendStatus::isLinear>()); + hidlStatus.push_back(status); + break; + } + case TunerFrontendStatus::isShortFrames: { + status.isShortFrames(s.get<TunerFrontendStatus::isShortFrames>()); + hidlStatus.push_back(status); + break; + } + default: + break; + } + } + return hidlStatus; +} + TunerFrontendSettings FrontendClient::getAidlFrontendSettings(const FrontendSettings& settings, const FrontendSettingsExt1_1& settingsExt1_1) { bool isExtended = validateExtendedSettings(settingsExt1_1); @@ -686,14 +1115,15 @@ FrontendScanMessage TunerFrontendCallback::getHalScanMessage( vector<TunerFrontendScanAtsc3PlpInfo> plp = message.get<TunerFrontendScanMessage::atsc3PlpInfos>(); hidl_vec<FrontendScanAtsc3PlpInfo> plpInfo; - for (TunerFrontendScanAtsc3PlpInfo info : plp) { + int size = plp.size(); + plpInfo.resize(size); + for (int i = 0; i < size; i++) { + auto info = message.get<TunerFrontendScanMessage::atsc3PlpInfos>()[i]; FrontendScanAtsc3PlpInfo p{ .plpId = static_cast<uint8_t>(info.plpId), .bLlsFlag = info.llsFlag, }; - int size = plpInfo.size(); - plpInfo.resize(size + 1); - plpInfo[size] = p; + plpInfo[i] = p; } scanMessage.atsc3PlpInfos(plpInfo); break; diff --git a/media/jni/tuner/FrontendClient.h b/media/jni/tuner/FrontendClient.h index b0107ff174d4..298b3974aeb9 100644 --- a/media/jni/tuner/FrontendClient.h +++ b/media/jni/tuner/FrontendClient.h @@ -43,6 +43,7 @@ using ::aidl::android::media::tv::tuner::TunerFrontendIsdbs3Settings; using ::aidl::android::media::tv::tuner::TunerFrontendIsdbtSettings; using ::aidl::android::media::tv::tuner::TunerFrontendScanMessage; using ::aidl::android::media::tv::tuner::TunerFrontendSettings; +using ::aidl::android::media::tv::tuner::TunerFrontendStatus; using ::android::hardware::Return; using ::android::hardware::Void; @@ -182,6 +183,9 @@ public: int getId(); private: + vector<FrontendStatus> getHidlStatus(vector<TunerFrontendStatus>& aidlStatus); + vector<FrontendStatusExt1_1> getHidlStatusExt(vector<TunerFrontendStatus>& aidlStatus); + TunerFrontendSettings getAidlFrontendSettings( const FrontendSettings& settings, const FrontendSettingsExt1_1& settingsExt1_1); TunerFrontendAnalogSettings getAidlAnalogSettings( 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/native/android/system_fonts.cpp b/native/android/system_fonts.cpp index 45f42f1b5dc6..48d738039696 100644 --- a/native/android/system_fonts.cpp +++ b/native/android/system_fonts.cpp @@ -264,7 +264,7 @@ AFont* _Nonnull AFontMatcher_match( static_cast<minikin::FamilyVariant>(matcher->mFamilyVariant), 1 /* maxRun */); - const minikin::Font* font = runs[0].fakedFont.font; + const std::shared_ptr<minikin::Font>& font = runs[0].fakedFont.font; std::unique_ptr<AFont> result = std::make_unique<AFont>(); const android::MinikinFontSkia* minikinFontSkia = reinterpret_cast<android::MinikinFontSkia*>(font->typeface().get()); diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java index 2716e092c6c3..3b054e942178 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 @@ -4871,9 +4845,13 @@ public class ConnectivityManager { } } - private void setOemNetworkPreference(@NonNull OemNetworkPreferences preference) { - Log.d(TAG, "setOemNetworkPreference called with preference: " - + preference.toString()); + private void setOemNetworkPreference(@NonNull final OemNetworkPreferences preference) { + try { + mService.setOemNetworkPreference(preference); + } catch (RemoteException e) { + Log.e(TAG, "setOemNetworkPreference() failed for preference: " + preference.toString()); + throw e.rethrowFromSystemServer(); + } } @NonNull diff --git a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl index 1b4d2e413943..e2672c480c10 100644 --- a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl +++ b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl @@ -29,6 +29,7 @@ import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.NetworkRequest; import android.net.NetworkState; +import android.net.OemNetworkPreferences; import android.net.ProxyInfo; import android.net.UidRange; import android.net.QosSocketInfo; @@ -156,9 +157,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); @@ -243,4 +241,6 @@ interface IConnectivityManager void registerQosSocketCallback(in QosSocketInfo socketInfo, in IQosCallback callback); void unregisterQosCallback(in IQosCallback callback); + + void setOemNetworkPreference(in OemNetworkPreferences preference); } 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/drawable/controls_dialog_bg.xml b/packages/SystemUI/res/drawable/controls_dialog_bg.xml new file mode 100644 index 000000000000..cb4686dd04a7 --- /dev/null +++ b/packages/SystemUI/res/drawable/controls_dialog_bg.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. +--> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <solid android:color="?android:attr/colorBackgroundFloating" /> + <corners android:radius="@dimen/notification_corner_radius" /> +</shape> diff --git a/packages/SystemUI/res/drawable/udfps_progress_bar.xml b/packages/SystemUI/res/drawable/udfps_progress_bar.xml new file mode 100644 index 000000000000..e5389f3b99ef --- /dev/null +++ b/packages/SystemUI/res/drawable/udfps_progress_bar.xml @@ -0,0 +1,44 @@ +<?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. + --> + +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:id="@android:id/background"> + <shape + android:innerRadiusRatio="2.2" + android:shape="ring" + android:thickness="@dimen/udfps_enroll_progress_thickness" + android:useLevel="false" + android:tint="?android:colorControlNormal"> + <solid android:color="@*android:color/white_disabled_material" /> + </shape> + </item> + <item android:id="@android:id/progress"> + <rotate + android:fromDegrees="270" + android:pivotX="50%" + android:pivotY="50%" + android:toDegrees="270"> + <shape + android:innerRadiusRatio="2.2" + android:shape="ring" + android:thickness="@dimen/udfps_enroll_progress_thickness" + android:tint="?android:attr/colorControlActivated"> + <solid android:color="@android:color/white" /> + </shape> + </rotate> + </item> +</layer-list>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/controls_in_dialog.xml b/packages/SystemUI/res/layout/controls_in_dialog.xml new file mode 100644 index 000000000000..983999f9a5f8 --- /dev/null +++ b/packages/SystemUI/res/layout/controls_in_dialog.xml @@ -0,0 +1,45 @@ +<?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. +--> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/control_detail_root" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginVertical="@dimen/controls_activity_view_top_offset" + android:layout_marginHorizontal="@dimen/controls_activity_view_side_offset" + android:padding="8dp" + android:orientation="vertical" + android:background="@drawable/controls_dialog_bg"> + + <com.android.systemui.globalactions.MinHeightScrollView + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + android:orientation="vertical" + android:scrollbars="none"> + + <LinearLayout + android:id="@+id/global_actions_controls" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:clipChildren="false" + android:orientation="vertical" + android:clipToPadding="false" /> + + </com.android.systemui.globalactions.MinHeightScrollView> +</LinearLayout> diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml index 04de9784812f..862076b650b9 100644 --- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml +++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml @@ -81,6 +81,17 @@ android:contentDescription="@string/accessibility_phone_button" android:tint="?attr/wallpaperTextColor" /> + <ImageView + android:id="@+id/alt_left_button" + android:layout_height="@dimen/keyguard_affordance_height" + android:layout_width="@dimen/keyguard_affordance_width" + android:layout_gravity="bottom|start" + android:scaleType="center" + android:tint="?attr/wallpaperTextColor" + android:layout_marginStart="24dp" + android:layout_marginBottom="48dp" + android:visibility="gone" /> + <FrameLayout android:id="@+id/overlay_container" android:layout_width="match_parent" diff --git a/packages/SystemUI/res/layout/long_screenshot.xml b/packages/SystemUI/res/layout/long_screenshot.xml index 8f3345f9d85c..e2f3e2a306e3 100644 --- a/packages/SystemUI/res/layout/long_screenshot.xml +++ b/packages/SystemUI/res/layout/long_screenshot.xml @@ -74,7 +74,7 @@ android:id="@+id/preview" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginBottom="24dp" + android:layout_marginBottom="42dp" android:layout_marginHorizontal="48dp" android:adjustViewBounds="true" app:layout_constrainedHeight="true" @@ -91,19 +91,33 @@ android:id="@+id/crop_view" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginBottom="24dp" + android:layout_marginBottom="42dp" app:layout_constrainedHeight="true" app:layout_constrainedWidth="true" app:layout_constraintTop_toBottomOf="@id/guideline" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintBottom_toBottomOf="parent" - app:handleThickness="3dp" + app:handleThickness="@dimen/screenshot_crop_handle_thickness" app:handleColor="@*android:color/accent_device_default" - app:scrimColor="#9444" + app:scrimColor="@color/screenshot_crop_scrim" tools:background="?android:colorBackground" tools:minHeight="100dp" tools:minWidth="100dp" /> + <com.android.systemui.screenshot.MagnifierView + android:id="@+id/magnifier" + android:visibility="invisible" + android:layout_width="200dp" + android:layout_height="200dp" + app:layout_constraintTop_toBottomOf="@id/guideline" + app:layout_constraintLeft_toLeftOf="parent" + app:handleThickness="@dimen/screenshot_crop_handle_thickness" + app:handleColor="@*android:color/accent_device_default" + app:scrimColor="@color/screenshot_crop_scrim" + app:borderThickness="4dp" + app:borderColor="#fff" + /> + </androidx.constraintlayout.widget.ConstraintLayout> diff --git a/packages/SystemUI/res/layout/udfps_view.xml b/packages/SystemUI/res/layout/udfps_view.xml index c0788051efed..6ae306e17209 100644 --- a/packages/SystemUI/res/layout/udfps_view.xml +++ b/packages/SystemUI/res/layout/udfps_view.xml @@ -20,4 +20,17 @@ android:id="@+id/udfps_view" android:layout_width="match_parent" android:layout_height="match_parent" - systemui:sensorTouchAreaCoefficient="0.5"/> + systemui:sensorTouchAreaCoefficient="0.5"> + + <!-- Enrollment progress bar--> + <com.android.systemui.biometrics.UdfpsProgressBar + android:id="@+id/progress_bar" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:max="100" + android:padding="@dimen/udfps_enroll_progress_thickness" + android:progress="0" + android:layout_gravity="center" + android:visibility="gone"/> + +</com.android.systemui.biometrics.UdfpsView> 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/attrs.xml b/packages/SystemUI/res/values/attrs.xml index 6c55fb62e638..8166e35d5b6a 100644 --- a/packages/SystemUI/res/values/attrs.xml +++ b/packages/SystemUI/res/values/attrs.xml @@ -178,6 +178,14 @@ <attr name="scrimColor" format="color" /> </declare-styleable> + <declare-styleable name="MagnifierView"> + <attr name="handleThickness" format="dimension" /> + <attr name="handleColor" format="color" /> + <attr name="scrimColor" format="color" /> + <attr name="borderThickness" format="dimension" /> + <attr name="borderColor" format="color" /> + </declare-styleable> + <declare-styleable name="RoundedCornerProgressDrawable"> <attr name="android:drawable" /> </declare-styleable> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index a7cf3e91dbba..5fb6de7bb588 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -197,6 +197,9 @@ <color name="global_screenshot_dismiss_foreground">@color/GM2_grey_500</color> <color name="global_screenshot_background_protection_start">#40000000</color> <!-- 25% black --> + <!-- Long screenshot UI --> + <color name="screenshot_crop_scrim">#9444</color> + <!-- GM2 colors --> <color name="GM2_grey_50">#F8F9FA</color> <color name="GM2_grey_100">#F1F3F4</color> @@ -261,6 +264,7 @@ <color name="control_enabled_cool_foreground">@color/GM2_blue_300</color> <color name="control_thumbnail_tint">#33000000</color> <color name="control_thumbnail_shadow_color">@*android:color/black</color> + <color name="controls_lockscreen_scrim">#AA000000</color> <!-- Docked misalignment message --> <color name="misalignment_text_color">#F28B82</color> 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/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index d92f4ea65390..afa98b56a13c 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -345,6 +345,7 @@ <dimen name="screenshot_action_chip_padding_end">16dp</dimen> <dimen name="screenshot_action_chip_text_size">14sp</dimen> <dimen name="screenshot_dismissal_height_delta">80dp</dimen> + <dimen name="screenshot_crop_handle_thickness">3dp</dimen> <!-- The width of the view containing navigation buttons --> @@ -1116,6 +1117,9 @@ <!-- Y translation for credential contents when animating in --> <dimen name="biometric_dialog_credential_translation_offset">60dp</dimen> + <!-- UDFPS enrollment progress bar thickness --> + <dimen name="udfps_enroll_progress_thickness">12dp</dimen> + <!-- Wireless Charging Animation values --> <dimen name="wireless_charging_dots_radius_start">0dp</dimen> <dimen name="wireless_charging_dots_radius_end">4dp</dimen> @@ -1259,6 +1263,7 @@ <!-- Home Controls activity view detail panel--> <dimen name="controls_activity_view_top_offset">100dp</dimen> + <dimen name="controls_activity_view_side_offset">12dp</dimen> <dimen name="controls_activity_view_text_size">17sp</dimen> <dimen name="controls_activity_view_corner_radius">@*android:dimen/config_bottomDialogCornerRadius</dimen> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 4b04eebfddf0..7c72548a7252 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -662,6 +662,19 @@ <item name="android:windowExitAnimation">@anim/bottomsheet_out</item> </style> + <style name="Theme.SystemUI.Dialog.Control.LockScreen" parent="@android:style/Theme.DeviceDefault.Dialog.NoActionBar"> + <item name="android:windowAnimationStyle">@style/Animation.Fade</item> + <item name="android:windowFullscreen">true</item> + <item name="android:windowIsFloating">false</item> + <item name="android:windowBackground">@color/controls_lockscreen_scrim</item> + <item name="android:backgroundDimEnabled">true</item> + </style> + + <style name="Animation.Fade"> + <item name="android:windowEnterAnimation">@android:anim/fade_in</item> + <item name="android:windowExitAnimation">@android:anim/fade_out</item> + </style> + <style name="Control" /> <style name="Control.MenuItem"> @@ -752,4 +765,12 @@ <item name="android:textSize">14sp</item> <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> </style> + + <style name="UdfpsProgressBarStyle" + parent="android:style/Widget.Material.ProgressBar.Horizontal"> + <item name="android:indeterminate">false</item> + <item name="android:max">10000</item> + <item name="android:mirrorForRtl">false</item> + <item name="android:progressDrawable">@drawable/udfps_progress_bar</item> + </style> </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/biometrics/UdfpsAnimationEnroll.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java index e07c84034b31..5290986b2a1c 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java @@ -38,6 +38,7 @@ public class UdfpsAnimationEnroll extends UdfpsAnimation { private static final String TAG = "UdfpsAnimationEnroll"; private static final float SHADOW_RADIUS = 5.f; + private static final float PROGRESS_BAR_RADIUS = 140.f; @Nullable private RectF mSensorRect; @NonNull private final Paint mSensorPaint; @@ -81,12 +82,12 @@ public class UdfpsAnimationEnroll extends UdfpsAnimation { @Override public int getPaddingX() { - return (int) Math.ceil(SHADOW_RADIUS); + return (int) Math.ceil(PROGRESS_BAR_RADIUS); } @Override public int getPaddingY() { - return (int) Math.ceil(SHADOW_RADIUS); + return (int) Math.ceil(PROGRESS_BAR_RADIUS); } @Override @@ -104,12 +105,4 @@ public class UdfpsAnimationEnroll extends UdfpsAnimation { public int getOpacity() { return 0; } - - public void onEnrollmentProgress(int remaining) { - Log.d(TAG, "Remaining: " + remaining); - } - - public void onEnrollmentHelp() { - Log.d(TAG, "onEnrollmentHelp"); - } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java index 4e3419e1fab3..41ea4d66f575 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java @@ -103,18 +103,6 @@ public class UdfpsAnimationView extends View implements DozeReceiver, postInvalidate(); } - void onEnrollmentProgress(int remaining) { - if (mUdfpsAnimation instanceof UdfpsAnimationEnroll) { - ((UdfpsAnimationEnroll) mUdfpsAnimation).onEnrollmentProgress(remaining); - } - } - - void onEnrollmentHelp() { - if (mUdfpsAnimation instanceof UdfpsAnimationEnroll) { - ((UdfpsAnimationEnroll) mUdfpsAnimation).onEnrollmentHelp(); - } - } - public int getPaddingX() { if (mUdfpsAnimation == null) { return 0; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index baa597398188..edf046864a7c 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -84,6 +84,7 @@ public class UdfpsController implements DozeReceiver, HbmCallback { private boolean mIsOverlayRequested; // Reason the overlay has been requested. See IUdfpsOverlayController for definitions. private int mRequestReason; + @Nullable UdfpsEnrollHelper mEnrollHelper; // The fingerprint AOD trigger doesn't provide an ACTION_UP/ACTION_CANCEL event to tell us when // to turn off high brightness mode. To get around this limitation, the state of the AOD @@ -95,6 +96,9 @@ public class UdfpsController implements DozeReceiver, HbmCallback { public class UdfpsOverlayController extends IUdfpsOverlayController.Stub { @Override public void showUdfpsOverlay(int sensorId, int reason) { + if (reason == IUdfpsOverlayController.REASON_ENROLL) { + mEnrollHelper = new UdfpsEnrollHelper(); + } UdfpsController.this.showOverlay(reason); } @@ -260,15 +264,17 @@ public class UdfpsController implements DozeReceiver, HbmCallback { // Transform dimensions if the device is in landscape mode. switch (mContext.getDisplay().getRotation()) { case Surface.ROTATION_90: - mCoreLayoutParams.x = mSensorProps.sensorLocationY - mSensorProps.sensorRadius; - mCoreLayoutParams.y = - p.y - mSensorProps.sensorLocationX - mSensorProps.sensorRadius; + mCoreLayoutParams.x = mSensorProps.sensorLocationY - mSensorProps.sensorRadius + - paddingX; + mCoreLayoutParams.y = p.y - mSensorProps.sensorLocationX - mSensorProps.sensorRadius + - paddingY; break; case Surface.ROTATION_270: - mCoreLayoutParams.x = - p.x - mSensorProps.sensorLocationY - mSensorProps.sensorRadius; - mCoreLayoutParams.y = mSensorProps.sensorLocationX - mSensorProps.sensorRadius; + mCoreLayoutParams.x = p.x - mSensorProps.sensorLocationY - mSensorProps.sensorRadius + - paddingX; + mCoreLayoutParams.y = mSensorProps.sensorLocationX - mSensorProps.sensorRadius + - paddingY; break; default: @@ -295,6 +301,7 @@ public class UdfpsController implements DozeReceiver, HbmCallback { Log.v(TAG, "showUdfpsOverlay | adding window"); final UdfpsAnimation animation = getUdfpsAnimationForReason(reason); mView.setUdfpsAnimation(animation); + mView.setEnrollHelper(mEnrollHelper); mWindowManager.addView(mView, computeLayoutParams(animation)); mView.setOnTouchListener(mOnTouchListener); mIsOverlayShowing = true; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java new file mode 100644 index 000000000000..ac6a2121eaae --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java @@ -0,0 +1,48 @@ +/* + * 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.systemui.biometrics; + +import androidx.annotation.NonNull; + +/** + * Helps keep track of enrollment state and animates the progress bar accordingly. + */ +public class UdfpsEnrollHelper { + private static final String TAG = "UdfpsEnrollHelper"; + + + private int mTotalSteps = -1; + private int mCurrentProgress = 0; + + void onEnrollmentProgress(int remaining, @NonNull UdfpsProgressBar progressBar) { + if (mTotalSteps == -1) { + mTotalSteps = remaining; + } + + mCurrentProgress = progressBar.getMax() * Math.max(0, mTotalSteps + 1 - remaining) + / (mTotalSteps + 1); + progressBar.setProgress(mCurrentProgress, true /* animate */); + } + + void updateProgress(@NonNull UdfpsProgressBar progressBar) { + progressBar.setProgress(mCurrentProgress); + } + + void onEnrollmentHelp() { + + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsProgressBar.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsProgressBar.java new file mode 100644 index 000000000000..84e2fab7bf6b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsProgressBar.java @@ -0,0 +1,59 @@ +/* + * 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.systemui.biometrics; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.ProgressBar; + +import com.android.systemui.R; + +/** + * A (determinate) progress bar in the form of a ring. The progress bar goes clockwise starting + * from the 12 o'clock position. This view maintain equal width and height using a strategy similar + * to "centerInside" for ImageView. + */ +public class UdfpsProgressBar extends ProgressBar { + + public UdfpsProgressBar(Context context) { + this(context, null); + } + + public UdfpsProgressBar(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public UdfpsProgressBar(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, R.style.UdfpsProgressBarStyle); + } + + public UdfpsProgressBar(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + final int measuredHeight = getMeasuredHeight(); + final int measuredWidth = getMeasuredWidth(); + + final int length = Math.min(measuredHeight, measuredWidth); + setMeasuredDimension(length, length); + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java index 7e378d3c568e..b21e1b5ebb15 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java @@ -56,6 +56,8 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin @NonNull private final RectF mSensorRect; @NonNull private final Paint mDebugTextPaint; + @Nullable private UdfpsProgressBar mProgressBar; + // Used to obtain the sensor location. @NonNull private FingerprintSensorPropertiesInternal mSensorProps; @@ -64,6 +66,7 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin private boolean mIlluminationRequested; private int mStatusBarState; private boolean mNotificationShadeExpanded; + @Nullable private UdfpsEnrollHelper mEnrollHelper; public UdfpsView(Context context, AttributeSet attrs) { super(context, attrs); @@ -110,6 +113,18 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin void setUdfpsAnimation(@Nullable UdfpsAnimation animation) { mAnimationView.setAnimation(animation); + if (animation instanceof UdfpsAnimationEnroll) { + mProgressBar.setVisibility(View.VISIBLE); + } else { + mProgressBar.setVisibility(View.GONE); + } + } + + void setEnrollHelper(@Nullable UdfpsEnrollHelper enrollHelper) { + mEnrollHelper = enrollHelper; + if (mEnrollHelper != null) { + mEnrollHelper.updateProgress(mProgressBar); + } } @Override @@ -138,6 +153,11 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin } @Override + protected void onFinishInflate() { + mProgressBar = findViewById(R.id.progress_bar); + } + + @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); mSensorRect.set(0 + mAnimationView.getPaddingX(), @@ -233,10 +253,10 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin } void onEnrollmentProgress(int remaining) { - mAnimationView.onEnrollmentProgress(remaining); + mEnrollHelper.onEnrollmentProgress(remaining, mProgressBar); } void onEnrollmentHelp() { - mAnimationView.onEnrollmentHelp(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt new file mode 100644 index 000000000000..8e878cf76ad9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt @@ -0,0 +1,90 @@ +/* + * 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.systemui.controls.ui + +import android.app.Dialog +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager + +import com.android.systemui.Interpolators +import com.android.systemui.R +import com.android.systemui.broadcast.BroadcastDispatcher + +/** + * Show the controls space inside a dialog, as from the lock screen. + */ +class ControlsDialog( + thisContext: Context, + val broadcastDispatcher: BroadcastDispatcher +) : Dialog(thisContext, R.style.Theme_SystemUI_Dialog_Control_LockScreen) { + + private val receiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val action = intent.getAction() + if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) { + dismiss() + } + } + } + + init { + window.setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG) + window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM + or WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED) + + setContentView(R.layout.controls_in_dialog) + + requireViewById<ViewGroup>(R.id.control_detail_root).apply { + setOnClickListener { dismiss() } + (getParent() as View).setOnClickListener { dismiss() } + } + } + + fun show( + controller: ControlsUiController + ): ControlsDialog { + super.show() + + val vg = requireViewById<ViewGroup>(com.android.systemui.R.id.global_actions_controls) + vg.alpha = 0f + controller.show(vg, { /* do nothing */ }) + + vg.animate() + .alpha(1f) + .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN) + .setDuration(300) + + val filter = IntentFilter() + filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS) + broadcastDispatcher.registerReceiver(receiver, filter) + + return this + } + + override fun dismiss() { + broadcastDispatcher.unregisterReceiver(receiver) + + if (!isShowing()) return + + super.dismiss() + } +} 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/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java index 9037192e2ddc..543874325254 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java @@ -187,7 +187,7 @@ public class ScreenMediaRecorder { * @param refreshRate Desired refresh rate * @return array with supported width, height, and refresh rate */ - private int[] getSupportedSize(int screenWidth, int screenHeight, int refreshRate) { + private int[] getSupportedSize(final int screenWidth, final int screenHeight, int refreshRate) { double maxScale = 0; MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS); @@ -207,25 +207,33 @@ public class ScreenMediaRecorder { int width = vc.getSupportedWidths().getUpper(); int height = vc.getSupportedHeights().getUpper(); - if (width >= screenWidth && height >= screenHeight - && vc.isSizeSupported(screenWidth, screenHeight)) { + int screenWidthAligned = screenWidth; + if (screenWidthAligned % vc.getWidthAlignment() != 0) { + screenWidthAligned -= (screenWidthAligned % vc.getWidthAlignment()); + } + int screenHeightAligned = screenHeight; + if (screenHeightAligned % vc.getHeightAlignment() != 0) { + screenHeightAligned -= (screenHeightAligned % vc.getHeightAlignment()); + } + if (width >= screenWidthAligned && height >= screenHeightAligned + && vc.isSizeSupported(screenWidthAligned, screenHeightAligned)) { // Desired size is supported, now get the rate - int maxRate = vc.getSupportedFrameRatesFor(screenWidth, screenHeight) - .getUpper().intValue(); + int maxRate = vc.getSupportedFrameRatesFor(screenWidthAligned, + screenHeightAligned).getUpper().intValue(); if (maxRate < refreshRate) { refreshRate = maxRate; } Log.d(TAG, "Screen size supported at rate " + refreshRate); - return new int[]{screenWidth, screenHeight, refreshRate}; + return new int[]{screenWidthAligned, screenHeightAligned, refreshRate}; } // Otherwise, continue searching double scale = Math.min(((double) width / screenWidth), ((double) height / screenHeight)); if (scale > maxScale) { - maxScale = scale; + maxScale = Math.min(1, scale); maxInfo = vc; } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java index 8e182b415488..c8afd0b6cfe9 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java @@ -35,7 +35,7 @@ import com.android.systemui.R; * cropped out. */ public class CropView extends View { - private enum CropBoundary { + public enum CropBoundary { NONE, TOP, BOTTOM } @@ -48,8 +48,14 @@ public class CropView extends View { private float mTopCrop = 0f; private float mBottomCrop = 1f; + // When the user is dragging a handle, these variables store the distance between the top/bottom + // crop values and + private float mTopDelta = 0f; + private float mBottomDelta = 0f; + private CropBoundary mCurrentDraggingBoundary = CropBoundary.NONE; - private float mLastY; + private float mStartingY; // y coordinate of ACTION_DOWN + private CropInteractionListener mCropInteractionListener; public CropView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); @@ -73,54 +79,84 @@ public class CropView extends View { @Override public void onDraw(Canvas canvas) { super.onDraw(canvas); - drawShade(canvas, 0, mTopCrop); - drawShade(canvas, mBottomCrop, 1f); - drawHandle(canvas, mTopCrop); - drawHandle(canvas, mBottomCrop); + float top = mTopCrop + mTopDelta; + float bottom = mBottomCrop + mBottomDelta; + drawShade(canvas, 0, top); + drawShade(canvas, bottom, 1f); + drawHandle(canvas, top); + drawHandle(canvas, bottom); } @Override public boolean onTouchEvent(MotionEvent event) { int topPx = fractionToPixels(mTopCrop); int bottomPx = fractionToPixels(mBottomCrop); - if (event.getAction() == MotionEvent.ACTION_DOWN) { - mCurrentDraggingBoundary = nearestBoundary(event, topPx, bottomPx); - if (mCurrentDraggingBoundary != CropBoundary.NONE) { - mLastY = event.getY(); - } - return true; - } - if (event.getAction() == MotionEvent.ACTION_MOVE - && mCurrentDraggingBoundary != CropBoundary.NONE) { - float delta = event.getY() - mLastY; - if (mCurrentDraggingBoundary == CropBoundary.TOP) { - mTopCrop = pixelsToFraction((int) MathUtils.constrain(topPx + delta, 0, - bottomPx - 2 * mCropTouchMargin)); - } else { // Bottom - mBottomCrop = pixelsToFraction((int) MathUtils.constrain(bottomPx + delta, - topPx + 2 * mCropTouchMargin, getMeasuredHeight())); - } - mLastY = event.getY(); - invalidate(); - return true; + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + mCurrentDraggingBoundary = nearestBoundary(event, topPx, bottomPx); + if (mCurrentDraggingBoundary != CropBoundary.NONE) { + mStartingY = event.getY(); + updateListener(event); + } + return true; + case MotionEvent.ACTION_MOVE: + if (mCurrentDraggingBoundary != CropBoundary.NONE) { + float delta = event.getY() - mStartingY; + if (mCurrentDraggingBoundary == CropBoundary.TOP) { + mTopDelta = pixelsToFraction((int) MathUtils.constrain(delta, -topPx, + bottomPx - 2 * mCropTouchMargin - topPx)); + } else { // Bottom + mBottomDelta = pixelsToFraction((int) MathUtils.constrain(delta, + topPx + 2 * mCropTouchMargin - bottomPx, + getMeasuredHeight() - bottomPx)); + } + updateListener(event); + invalidate(); + return true; + } + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + if (mCurrentDraggingBoundary != CropBoundary.NONE) { + // Commit the delta to the stored crop values. + mTopCrop += mTopDelta; + mBottomCrop += mBottomDelta; + mTopDelta = 0; + mBottomDelta = 0; + updateListener(event); + } } return super.onTouchEvent(event); } /** - * @return value [0,1] representing the position of the top crop boundary. + * @return value [0,1] representing the position of the top crop boundary. Does not reflect + * changes from any in-progress touch input. */ public float getTopBoundary() { return mTopCrop; } /** - * @return value [0,1] representing the position of the bottom crop boundary. + * @return value [0,1] representing the position of the bottom crop boundary. Does not reflect + * changes from any in-progress touch input. */ public float getBottomBoundary() { return mBottomCrop; } + public void setCropInteractionListener(CropInteractionListener listener) { + mCropInteractionListener = listener; + } + + private void updateListener(MotionEvent event) { + if (mCropInteractionListener != null) { + float boundaryPosition = (mCurrentDraggingBoundary == CropBoundary.TOP) + ? mTopCrop + mTopDelta : mBottomCrop + mBottomDelta; + mCropInteractionListener.onCropMotionEvent(event, mCurrentDraggingBoundary, + boundaryPosition, fractionToPixels(boundaryPosition)); + } + } + private void drawShade(Canvas canvas, float fracStart, float fracEnd) { canvas.drawRect(0, fractionToPixels(fracStart), getMeasuredWidth(), fractionToPixels(fracEnd), mShadePaint); @@ -148,4 +184,17 @@ public class CropView extends View { } return CropBoundary.NONE; } + + /** + * Listen for crop motion events and state. + */ + public interface CropInteractionListener { + /** + * Called whenever CropView has a MotionEvent that can impact the position of the crop + * boundaries. + */ + void onCropMotionEvent(MotionEvent event, CropBoundary boundary, float boundaryPosition, + int boundaryPositionPx); + + } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java b/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java new file mode 100644 index 000000000000..f88715164bc7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java @@ -0,0 +1,184 @@ +/* + * 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.systemui.screenshot; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; + +import androidx.annotation.Nullable; + +import com.android.systemui.R; + +/** + * MagnifierView shows a full-res cropped circular display of a given ImageTileSet, contents and + * positioning dereived from events from a CropView to which it listens. + * + * Not meant to be a general-purpose magnifier! + */ +public class MagnifierView extends View implements CropView.CropInteractionListener { + private Drawable mDrawable; + + private final Paint mShadePaint; + private final Paint mHandlePaint; + + private Path mOuterCircle; + private Path mInnerCircle; + + private Path mCheckerboard; + private Paint mCheckerboardPaint; + private final float mBorderPx; + private final int mBorderColor; + private float mCheckerboardBoxSize = 40; + + private float mLastCropPosition; + private CropView.CropBoundary mCropBoundary; + + public MagnifierView(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public MagnifierView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + TypedArray t = context.getTheme().obtainStyledAttributes( + attrs, R.styleable.MagnifierView, 0, 0); + mShadePaint = new Paint(); + mShadePaint.setColor(t.getColor(R.styleable.MagnifierView_scrimColor, Color.TRANSPARENT)); + mHandlePaint = new Paint(); + mHandlePaint.setColor(t.getColor(R.styleable.MagnifierView_handleColor, Color.BLACK)); + mHandlePaint.setStrokeWidth( + t.getDimensionPixelSize(R.styleable.MagnifierView_handleThickness, 20)); + mBorderPx = t.getDimensionPixelSize(R.styleable.MagnifierView_borderThickness, 0); + mBorderColor = t.getColor(R.styleable.MagnifierView_borderColor, Color.WHITE); + t.recycle(); + mCheckerboardPaint = new Paint(); + mCheckerboardPaint.setColor(Color.GRAY); + } + + public void setImageTileset(ImageTileSet tiles) { + if (tiles != null) { + mDrawable = tiles.getDrawable(); + mDrawable.setBounds(0, 0, tiles.getWidth(), tiles.getHeight()); + } else { + mDrawable = null; + } + invalidate(); + } + + @Override + public void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + int radius = getWidth() / 2; + mOuterCircle = new Path(); + mOuterCircle.addCircle(radius, radius, radius, Path.Direction.CW); + mInnerCircle = new Path(); + mInnerCircle.addCircle(radius, radius, radius - mBorderPx, Path.Direction.CW); + mCheckerboard = generateCheckerboard(); + } + + @Override + public void onDraw(Canvas canvas) { + super.onDraw(canvas); + + // TODO: just draw a circle at the end instead of clipping like this? + canvas.clipPath(mOuterCircle); + canvas.drawColor(mBorderColor); + canvas.clipPath(mInnerCircle); + + // Draw a checkerboard pattern for out of bounds. + canvas.drawPath(mCheckerboard, mCheckerboardPaint); + + if (mDrawable != null) { + canvas.save(); + // Translate such that the center of this view represents the center of the crop + // boundary. + canvas.translate(-mDrawable.getBounds().width() / 2 + getWidth() / 2, + -mDrawable.getBounds().height() * mLastCropPosition + getHeight() / 2); + mDrawable.draw(canvas); + canvas.restore(); + } + + Rect scrimRect = new Rect(0, 0, getWidth(), getHeight() / 2); + if (mCropBoundary == CropView.CropBoundary.BOTTOM) { + scrimRect.offset(0, getHeight() / 2); + } + canvas.drawRect(scrimRect, mShadePaint); + + canvas.drawLine(0, getHeight() / 2, getWidth(), getHeight() / 2, mHandlePaint); + } + + @Override + public void onCropMotionEvent(MotionEvent event, CropView.CropBoundary boundary, + float cropPosition, int cropPositionPx) { + mCropBoundary = boundary; + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + mLastCropPosition = cropPosition; + setTranslationY(cropPositionPx - getHeight() / 2); + setPivotX(getWidth() / 2); + setPivotY(getHeight() / 2); + setScaleX(0.2f); + setScaleY(0.2f); + setAlpha(0f); + setTranslationX((getParentWidth() - getWidth()) / 2); + setVisibility(View.VISIBLE); + animate().alpha(1f).translationX(0).scaleX(1f).scaleY(1f).start(); + break; + case MotionEvent.ACTION_MOVE: + mLastCropPosition = cropPosition; + setTranslationY(cropPositionPx - getHeight() / 2); + invalidate(); + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + animate().alpha(0).translationX((getParentWidth() - getWidth()) / 2).scaleX(0.2f) + .scaleY(0.2f).withEndAction(() -> setVisibility(View.INVISIBLE)).start(); + break; + } + } + + private Path generateCheckerboard() { + Path path = new Path(); + int checkerWidth = (int) Math.ceil(getWidth() / mCheckerboardBoxSize); + int checkerHeight = (int) Math.ceil(getHeight() / mCheckerboardBoxSize); + + for (int row = 0; row < checkerHeight; row++) { + // Alternate starting on the first and second column; + int colStart = (row % 2 == 0) ? 0 : 1; + for (int col = colStart; col < checkerWidth; col += 2) { + path.addRect(col * mCheckerboardBoxSize, + row * mCheckerboardBoxSize, + (col + 1) * mCheckerboardBoxSize, + (row + 1) * mCheckerboardBoxSize, + Path.Direction.CW); + } + } + return path; + } + + private int getParentWidth() { + return ((View) getParent()).getWidth(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java index 18c379a4650f..25438a6f57ba 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java @@ -81,6 +81,7 @@ public class ScrollCaptureController implements OnComputeInternalInsetsListener private View mEdit; private View mShare; private CropView mCropView; + private MagnifierView mMagnifierView; public ScrollCaptureController(Context context, Connection connection, Executor uiExecutor, Executor bgExecutor, ImageExporter exporter, UiEventLogger uiEventLogger) { @@ -120,13 +121,14 @@ public class ScrollCaptureController implements OnComputeInternalInsetsListener mEdit = findViewById(R.id.edit); mShare = findViewById(R.id.share); mCropView = findViewById(R.id.crop_view); + mMagnifierView = findViewById(R.id.magnifier); + mCropView.setCropInteractionListener(mMagnifierView); mSave.setOnClickListener(this::onClicked); mCancel.setOnClickListener(this::onClicked); mEdit.setOnClickListener(this::onClicked); mShare.setOnClickListener(this::onClicked); - //mPreview.setImageDrawable(mImageTileSet.getDrawable()); mConnection.start(this::startCapture); } @@ -164,6 +166,7 @@ public class ScrollCaptureController implements OnComputeInternalInsetsListener private void doFinish() { mPreview.setImageDrawable(null); + mMagnifierView.setImageTileset(null); mImageTileSet.clear(); mCallback.onFinish(); mWindow.getDecorView().getViewTreeObserver() @@ -273,6 +276,7 @@ public class ScrollCaptureController implements OnComputeInternalInsetsListener session.end(mCallback::onFinish); } else { mPreview.setImageDrawable(mImageTileSet.getDrawable()); + mMagnifierView.setImageTileset(mImageTileSet); } } } 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/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index e6efba7ca28b..5d2203b57991 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java @@ -286,8 +286,7 @@ public class StackScrollAlgorithm { float currentYPosition = -algorithmState.scrollY; int childCount = algorithmState.visibleChildren.size(); for (int i = 0; i < childCount; i++) { - currentYPosition = updateChild(i, algorithmState, ambientState, currentYPosition, - false /* reverse */); + currentYPosition = updateChild(i, algorithmState, ambientState, currentYPosition); } } @@ -301,10 +300,6 @@ public class StackScrollAlgorithm { * @param currentYPosition The Y position of the current pass of the algorithm. For a forward * pass, this should be the top of the child; for a reverse pass, the * bottom of the child. - * @param reverse Whether we're laying out children in the reverse direction (Y - * positions - * decreasing) instead of the forward direction (Y positions - * increasing). * @return The Y position after laying out the child. This will be the {@code currentYPosition} * for the next call to this method, after adjusting for any gaps between children. */ @@ -312,8 +307,7 @@ public class StackScrollAlgorithm { int i, StackScrollAlgorithmState algorithmState, AmbientState ambientState, - float currentYPosition, - boolean reverse) { + float currentYPosition) { ExpandableView child = algorithmState.visibleChildren.get(i); ExpandableView previousChild = i > 0 ? algorithmState.visibleChildren.get(i - 1) : null; final boolean applyGapHeight = @@ -323,20 +317,12 @@ public class StackScrollAlgorithm { ExpandableViewState childViewState = child.getViewState(); childViewState.location = ExpandableViewState.LOCATION_UNKNOWN; - if (applyGapHeight && !reverse) { + if (applyGapHeight) { currentYPosition += mGapHeight; } - int childHeight = getMaxAllowedChildHeight(child); - if (reverse) { - childViewState.yTranslation = currentYPosition - - (childHeight + mPaddingBetweenElements); - if (currentYPosition <= 0) { - childViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP; - } - } else { - childViewState.yTranslation = currentYPosition; - } + childViewState.yTranslation = currentYPosition; + boolean isFooterView = child instanceof FooterView; boolean isEmptyShadeView = child instanceof EmptyShadeView; @@ -362,16 +348,9 @@ public class StackScrollAlgorithm { clampPositionToShelf(child, childViewState, ambientState); } - if (reverse) { - currentYPosition = childViewState.yTranslation; - if (applyGapHeight) { - currentYPosition -= mGapHeight; - } - } else { - currentYPosition = childViewState.yTranslation + childHeight + mPaddingBetweenElements; - if (currentYPosition <= 0) { - childViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP; - } + currentYPosition = childViewState.yTranslation + childHeight + mPaddingBetweenElements; + if (currentYPosition <= 0) { + childViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP; } if (childViewState.location == ExpandableViewState.LOCATION_UNKNOWN) { Log.wtf(LOG_TAG, "Failed to assign location for child " + i); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java index 0a366c9bb380..dd1419f4ff42 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -48,6 +48,7 @@ import android.os.Messenger; import android.os.RemoteException; import android.os.UserHandle; import android.provider.MediaStore; +import android.provider.Settings; import android.service.media.CameraPrewarmService; import android.telecom.TelecomManager; import android.text.TextUtils; @@ -60,6 +61,7 @@ import android.view.WindowInsets; import android.view.WindowManager; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; +import android.widget.ImageView; import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; @@ -71,6 +73,10 @@ import com.android.systemui.Dependency; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.assist.AssistManager; +import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.controls.dagger.ControlsComponent; +import com.android.systemui.controls.ui.ControlsDialog; +import com.android.systemui.controls.ui.ControlsUiController; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.IntentButtonProvider; import com.android.systemui.plugins.IntentButtonProvider.IntentButton; @@ -117,11 +123,13 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL private static final int DOZE_ANIMATION_STAGGER_DELAY = 48; private static final int DOZE_ANIMATION_ELEMENT_DURATION = 250; + // TODO(b/179494051): May no longer be needed private final boolean mShowLeftAffordance; private final boolean mShowCameraAffordance; private KeyguardAffordanceView mRightAffordanceView; private KeyguardAffordanceView mLeftAffordanceView; + private ImageView mAltLeftButton; private ViewGroup mIndicationArea; private TextView mEnterpriseDisclosure; private TextView mIndicationText; @@ -171,6 +179,11 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL private int mBurnInYOffset; private ActivityIntentHelper mActivityIntentHelper; + private ControlsDialog mControlsDialog; + private ControlsComponent mControlsComponent; + private int mLockScreenMode; + private BroadcastDispatcher mBroadcastDispatcher; + public KeyguardBottomAreaView(Context context) { this(context, null); } @@ -236,6 +249,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL mOverlayContainer = findViewById(R.id.overlay_container); mRightAffordanceView = findViewById(R.id.camera_button); mLeftAffordanceView = findViewById(R.id.left_button); + mAltLeftButton = findViewById(R.id.alt_left_button); mIndicationArea = findViewById(R.id.keyguard_indication_area); mEnterpriseDisclosure = findViewById( R.id.keyguard_indication_enterprise_disclosure); @@ -334,6 +348,11 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL lp.height = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_height); mLeftAffordanceView.setLayoutParams(lp); updateLeftAffordanceIcon(); + + lp = mAltLeftButton.getLayoutParams(); + lp.width = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_width); + lp.height = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_height); + mAltLeftButton.setLayoutParams(lp); } private void updateRightAffordanceIcon() { @@ -392,10 +411,17 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL } private void updateLeftAffordanceIcon() { + if (mDozing) { + mAltLeftButton.setVisibility(GONE); + } else if (mAltLeftButton.getDrawable() != null) { + mAltLeftButton.setVisibility(VISIBLE); + } + if (!mShowLeftAffordance || mDozing) { mLeftAffordanceView.setVisibility(GONE); return; } + IconState state = mLeftButton.getIcon(); mLeftAffordanceView.setVisibility(state.isVisible ? View.VISIBLE : View.GONE); if (state.drawable != mLeftAffordanceView.getDrawable() @@ -669,6 +695,9 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL public void startFinishDozeAnimation() { long delay = 0; + if (mAltLeftButton.getVisibility() == View.VISIBLE) { + startFinishDozeAnimationElement(mAltLeftButton, delay); + } if (mLeftAffordanceView.getVisibility() == View.VISIBLE) { startFinishDozeAnimationElement(mLeftAffordanceView, delay); delay += DOZE_ANIMATION_STAGGER_DELAY; @@ -744,6 +773,10 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL if (dozing) { mOverlayContainer.setVisibility(INVISIBLE); + if (mControlsDialog != null) { + mControlsDialog.dismiss(); + mControlsDialog = null; + } } else { mOverlayContainer.setVisibility(VISIBLE); if (animate) { @@ -773,6 +806,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL mLeftAffordanceView.setAlpha(alpha); mRightAffordanceView.setAlpha(alpha); mIndicationArea.setAlpha(alpha); + mAltLeftButton.setAlpha(alpha); } private class DefaultLeftButton implements IntentButton { @@ -844,4 +878,54 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL } return insets; } + + /** + * Show or hide controls, depending on the lock screen mode and controls + * availability. + */ + public void setupControls(ControlsComponent component, BroadcastDispatcher dispatcher) { + mControlsComponent = component; + mBroadcastDispatcher = dispatcher; + setupControls(); + } + + private void setupControls() { + if (mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL) { + mAltLeftButton.setVisibility(View.GONE); + mAltLeftButton.setOnClickListener(null); + return; + } + + if (Settings.Global.getInt(mContext.getContentResolver(), "controls_lockscreen", 0) == 0) { + return; + } + + if (mControlsComponent.getControlsListingController().isPresent()) { + mControlsComponent.getControlsListingController().get() + .addCallback(list -> { + if (!list.isEmpty()) { + mAltLeftButton.setImageDrawable(list.get(0).loadIcon()); + mAltLeftButton.setVisibility(View.VISIBLE); + mAltLeftButton.setOnClickListener((v) -> { + ControlsUiController ui = mControlsComponent + .getControlsUiController().get(); + mControlsDialog = new ControlsDialog(mContext, mBroadcastDispatcher) + .show(ui); + }); + + } else { + mAltLeftButton.setVisibility(View.GONE); + mAltLeftButton.setOnClickListener(null); + } + }); + } + } + + /** + * Optionally add controls when in the new lockscreen mode + */ + public void onLockScreenModeChanged(int mode) { + mLockScreenMode = mode; + setupControls(); + } } 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..e0ef3b6483a5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -80,8 +80,10 @@ import com.android.systemui.DejankUtils; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.biometrics.AuthController; +import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.classifier.Classifier; import com.android.systemui.classifier.FalsingCollector; +import com.android.systemui.controls.dagger.ControlsComponent; import com.android.systemui.dagger.qualifiers.DisplayId; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.DozeLog; @@ -95,6 +97,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; @@ -228,6 +231,7 @@ public class NotificationPanelViewController extends PanelViewController { public void onLockScreenModeChanged(int mode) { mLockScreenMode = mode; mClockPositionAlgorithm.onLockScreenModeChanged(mode); + mKeyguardBottomArea.onLockScreenModeChanged(mode); } @Override @@ -292,7 +296,10 @@ 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; + private final ControlsComponent mControlsComponent; + // 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 private final int mMaxKeyguardNotifications; @@ -501,6 +508,7 @@ public class NotificationPanelViewController extends PanelViewController { private NotificationShelfController mNotificationShelfController; private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL; + private BroadcastDispatcher mBroadcastDispatcher; private View.AccessibilityDelegate mAccessibilityDelegate = new View.AccessibilityDelegate() { @Override @@ -554,7 +562,10 @@ public class NotificationPanelViewController extends PanelViewController { QSDetailDisplayer qsDetailDisplayer, ScrimController scrimController, MediaDataManager mediaDataManager, - AmbientState ambientState) { + AmbientState ambientState, + FeatureFlags featureFlags, + ControlsComponent controlsComponent, + BroadcastDispatcher broadcastDispatcher) { super(view, falsingManager, dozeLog, keyguardStateController, (SysuiStatusBarStateController) statusBarStateController, vibratorHelper, latencyTracker, flingAnimationUtilsBuilder.get(), statusBarTouchableRegionManager, @@ -571,6 +582,7 @@ public class NotificationPanelViewController extends PanelViewController { mNotificationIconAreaController = notificationIconAreaController; mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory; mQSDetailDisplayer = qsDetailDisplayer; + mFeatureFlags = featureFlags; mView.setWillNotDraw(!DEBUG); mLayoutInflater = layoutInflater; mFalsingManager = falsingManager; @@ -587,6 +599,7 @@ public class NotificationPanelViewController extends PanelViewController { mBiometricUnlockController = biometricUnlockController; mScrimController = scrimController; mMediaDataManager = mediaDataManager; + mControlsComponent = controlsComponent; pulseExpansionHandler.setPulseExpandAbortListener(() -> { if (mQs != null) { mQs.animateHeaderSlidingOut(); @@ -625,6 +638,7 @@ public class NotificationPanelViewController extends PanelViewController { mEntryManager = notificationEntryManager; mConversationNotificationManager = conversationNotificationManager; mAuthController = authController; + mBroadcastDispatcher = broadcastDispatcher; mView.setBackgroundColor(Color.TRANSPARENT); OnAttachStateChangeListener onAttachStateChangeListener = new OnAttachStateChangeListener(); @@ -768,6 +782,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() { @@ -813,6 +847,7 @@ public class NotificationPanelViewController extends PanelViewController { mKeyguardBottomArea.setAffordanceHelper(mAffordanceHelper); mKeyguardBottomArea.setStatusBar(mStatusBar); mKeyguardBottomArea.setUserSetupComplete(mUserSetupComplete); + mKeyguardBottomArea.setupControls(mControlsComponent, mBroadcastDispatcher); } private void updateMaxDisplayedNotifications(boolean recompute) { 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..c07ba723ab43 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 @@ -61,13 +61,16 @@ import com.android.keyguard.dagger.KeyguardStatusViewComponent; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; +import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.classifier.FalsingManagerFake; +import com.android.systemui.controls.dagger.ControlsComponent; import com.android.systemui.doze.DozeLog; 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 +207,14 @@ public class NotificationPanelViewTest extends SysuiTestCase { @Mock private MediaDataManager mMediaDataManager; @Mock + private FeatureFlags mFeatureFlags; + @Mock + private ControlsComponent mControlsComponent; + @Mock + private BroadcastDispatcher mBroadcastDispatcher; + @Mock + private NotificationsQuickSettingsContainer mNotificationContainerParent; + @Mock private AmbientState mAmbientState; private NotificationPanelViewController mNotificationPanelViewController; private View.AccessibilityDelegate mAccessibiltyDelegate; @@ -219,6 +230,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 +250,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 +279,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 +305,10 @@ public class NotificationPanelViewTest extends SysuiTestCase { new QSDetailDisplayer(), mScrimController, mMediaDataManager, - mAmbientState); + mAmbientState, + mFeatureFlags, + mControlsComponent, + mBroadcastDispatcher); mNotificationPanelViewController.initDependencies( mStatusBar, mNotificationShelfController); @@ -400,6 +423,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/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index f9f064c14c62..e4a86c3744ce 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -55,7 +55,6 @@ import android.content.pm.IPackageManager; import android.content.pm.LauncherApps; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManagerInternal; import android.content.pm.ParceledListSlice; import android.content.pm.ResolveInfo; @@ -66,9 +65,8 @@ import android.content.pm.UserInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; -import android.graphics.Bitmap; import android.graphics.Point; -import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Binder; import android.os.Bundle; @@ -119,7 +117,6 @@ import com.android.internal.util.DumpUtils; import com.android.internal.widget.IRemoteViewsFactory; import com.android.server.LocalServices; import com.android.server.WidgetBackupProvider; -import com.android.server.policy.IconUtilities; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -253,8 +250,6 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku private boolean mSafeMode; private int mMaxWidgetBitmapMemory; - private IconUtilities mIconUtilities; - AppWidgetServiceImpl(Context context) { mContext = context; } @@ -271,7 +266,6 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku mCallbackHandler = new CallbackHandler(mContext.getMainLooper()); mBackupRestoreController = new BackupRestoreController(); mSecurityPolicy = new SecurityPolicy(); - mIconUtilities = new IconUtilities(mContext); computeMaximumWidgetBitmapMemory(); registerBroadcastReceiver(); @@ -578,44 +572,6 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } } - private Bitmap createMaskedWidgetBitmap(String providerPackage, int providerUserId) { - final long identity = Binder.clearCallingIdentity(); - try { - // Load the unbadged application icon and pass it to the widget to appear on - // the masked view. - Context userContext = mContext.createPackageContextAsUser(providerPackage, 0, - UserHandle.of(providerUserId)); - PackageManager pm = userContext.getPackageManager(); - Drawable icon = pm.getApplicationInfo(providerPackage, 0).loadUnbadgedIcon(pm).mutate(); - // Create a bitmap of the icon which is what the widget's remoteview requires. - icon.setColorFilter(mIconUtilities.getDisabledColorFilter()); - return mIconUtilities.createIconBitmap(icon); - } catch (NameNotFoundException e) { - Slog.e(TAG, "Fail to get application icon", e); - // Provider package removed, no need to mask its views as its state will be - // purged very soon. - return null; - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - private RemoteViews createMaskedWidgetRemoteViews(Bitmap icon, boolean showBadge, - PendingIntent onClickIntent) { - RemoteViews views = new RemoteViews(mContext.getPackageName(), - R.layout.work_widget_mask_view); - if (icon != null) { - views.setImageViewBitmap(R.id.work_widget_app_icon, icon); - } - if (!showBadge) { - views.setViewVisibility(R.id.work_widget_badge_icon, View.INVISIBLE); - } - if (onClickIntent != null) { - views.setOnClickPendingIntent(R.id.work_widget_mask_frame, onClickIntent); - } - return views; - } - /** * Mask the target widget belonging to the specified provider, or all active widgets * of the provider if target widget == null. @@ -625,59 +581,63 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku if (widgetCount == 0) { return; } - final String providerPackage = provider.id.componentName.getPackageName(); - final int providerUserId = provider.getUserId(); - Bitmap iconBitmap = createMaskedWidgetBitmap(providerPackage, providerUserId); - if (iconBitmap == null) { - return; - } - final boolean showBadge; - final Intent onClickIntent; + RemoteViews views = new RemoteViews(mContext.getPackageName(), + R.layout.work_widget_mask_view); + ApplicationInfo appInfo = provider.info.providerInfo.applicationInfo; + final int appUserId = provider.getUserId(); + boolean showBadge; + final long identity = Binder.clearCallingIdentity(); try { + final Intent onClickIntent; + if (provider.maskedBySuspendedPackage) { - showBadge = mUserManager.hasBadge(providerUserId); + showBadge = mUserManager.hasBadge(appUserId); final String suspendingPackage = mPackageManagerInternal.getSuspendingPackage( - providerPackage, providerUserId); + appInfo.packageName, appUserId); if (PLATFORM_PACKAGE_NAME.equals(suspendingPackage)) { onClickIntent = mDevicePolicyManagerInternal.createShowAdminSupportIntent( - providerUserId, true); + appUserId, true); } else { final SuspendDialogInfo dialogInfo = - mPackageManagerInternal.getSuspendedDialogInfo(providerPackage, - suspendingPackage, providerUserId); + mPackageManagerInternal.getSuspendedDialogInfo( + appInfo.packageName, suspendingPackage, appUserId); // onUnsuspend is null because we don't want to start any activity on // unsuspending from a suspended widget. onClickIntent = SuspendedAppActivity.createSuspendedAppInterceptIntent( - providerPackage, suspendingPackage, dialogInfo, null, null, - providerUserId); + appInfo.packageName, suspendingPackage, dialogInfo, null, null, + appUserId); } } else if (provider.maskedByQuietProfile) { showBadge = true; - onClickIntent = UnlaunchableAppActivity.createInQuietModeDialogIntent( - providerUserId); + onClickIntent = UnlaunchableAppActivity.createInQuietModeDialogIntent(appUserId); } else /* provider.maskedByLockedProfile */ { showBadge = true; - onClickIntent = mKeyguardManager.createConfirmDeviceCredentialIntent(null, null, - providerUserId); + onClickIntent = mKeyguardManager + .createConfirmDeviceCredentialIntent(null, null, appUserId); if (onClickIntent != null) { - onClickIntent.setFlags(FLAG_ACTIVITY_NEW_TASK - | FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + onClickIntent.setFlags( + FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); } } + + if (onClickIntent != null) { + views.setOnClickPendingIntent(R.id.work_widget_mask_frame, + PendingIntent.getActivity(mContext, 0, onClickIntent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE)); + } + + Icon icon = appInfo.icon != 0 + ? Icon.createWithResource(appInfo.packageName, appInfo.icon) + : Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon); + views.setImageViewIcon(R.id.work_widget_app_icon, icon); + if (!showBadge) { + views.setViewVisibility(R.id.work_widget_badge_icon, View.INVISIBLE); + } + for (int j = 0; j < widgetCount; j++) { Widget widget = provider.widgets.get(j); if (targetWidget != null && targetWidget != widget) continue; - PendingIntent intent = null; - if (onClickIntent != null) { - // Rare informational activity click is okay being - // immutable; the tradeoff is more security in exchange for - // losing bounds-based window animations - intent = PendingIntent.getActivity(mContext, widget.appWidgetId, - onClickIntent, - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); - } - RemoteViews views = createMaskedWidgetRemoteViews(iconBitmap, showBadge, intent); if (widget.replaceWithMaskedViewsLocked(views)) { scheduleNotifyUpdateAppWidgetLocked(widget, widget.getEffectiveViewsLocked()); } diff --git a/services/core/Android.bp b/services/core/Android.bp index e01c4df42ff5..37d2cdc16926 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -100,10 +100,10 @@ java_library_static { libs: [ "services.net", "android.hardware.light-V2.0-java", - "android.hardware.gnss-java", - "android.hardware.power-java", + "android.hardware.gnss-V1-java", + "android.hardware.power-V1-java", "android.hardware.power-V1.0-java", - "android.hardware.vibrator-java", + "android.hardware.vibrator-V1-java", "android.net.ipsec.ike.stubs.module_lib", "app-compat-annotations", "framework-tethering.stubs.module_lib", @@ -128,22 +128,22 @@ java_library_static { "android.hardware.health-V1.0-java", "android.hardware.health-V2.0-java", "android.hardware.health-V2.1-java", - "android.hardware.light-java", + "android.hardware.light-V1-java", "android.hardware.tv.cec-V1.0-java", "android.hardware.weaver-V1.0-java", "android.hardware.biometrics.face-V1.1-java", - "android.hardware.biometrics.face-java", + "android.hardware.biometrics.face-V1-java", "android.hardware.biometrics.fingerprint-V2.3-java", - "android.hardware.biometrics.fingerprint-java", + "android.hardware.biometrics.fingerprint-V1-java", "android.hardware.oemlock-V1.0-java", "android.hardware.configstore-V1.0-java", "android.hardware.contexthub-V1.0-java", - "android.hardware.rebootescrow-java", + "android.hardware.rebootescrow-V1-java", "android.hardware.soundtrigger-V2.3-java", - "android.hardware.power.stats-java", + "android.hardware.power.stats-V1-java", "android.hidl.manager-V1.2-java", "capture_state_listener-aidl-java", - "dnsresolver_aidl_interface-java", + "dnsresolver_aidl_interface-V7-java", "icu4j_calendar_astronomer", "netd-client", "overlayable_policy_aidl-java", diff --git a/services/core/java/android/content/pm/OWNERS b/services/core/java/android/content/pm/OWNERS new file mode 100644 index 000000000000..5eed0b509688 --- /dev/null +++ b/services/core/java/android/content/pm/OWNERS @@ -0,0 +1 @@ +include /core/java/android/content/pm/OWNERS
\ No newline at end of file diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index dad8bd826e3d..6886cdefc28a 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -997,28 +997,6 @@ public abstract class PackageManagerInternal { public abstract boolean isSuspendingAnyPackages(String suspendingPackage, int userId); /** - * Register to listen for loading progress of an installed package. - * @param packageName The name of the installed package - * @param callback To loading reporting progress - * @param userId The user under which to check. - * @return Whether the registration was successful. It can fail if the package has not been - * installed yet. - */ - public abstract boolean registerInstalledLoadingProgressCallback(@NonNull String packageName, - @NonNull InstalledLoadingProgressCallback callback, int userId); - - /** - * Unregister to stop listening to loading progress of an installed package - * @param packageName The name of the installed package - * @param callback To unregister - * @return True if the callback is removed from registered callback list. False is the callback - * does not exist on the registered callback list, which can happen if the callback has - * already been unregistered. - */ - public abstract boolean unregisterInstalledLoadingProgressCallback(@NonNull String packageName, - @NonNull InstalledLoadingProgressCallback callback); - - /** * Returns the string representation of a known package. For example, * {@link #PACKAGE_SETUP_WIZARD} is represented by the string Setup Wizard. * @@ -1137,7 +1115,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/android/os/OWNERS b/services/core/java/android/os/OWNERS new file mode 100644 index 000000000000..d0a2daf0905c --- /dev/null +++ b/services/core/java/android/os/OWNERS @@ -0,0 +1 @@ +per-file BatteryStats* = file:/BATTERY_STATS_OWNERS 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 c21a78a5b16e..13f6fe6a6aa0 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -120,6 +120,7 @@ import android.net.NetworkState; import android.net.NetworkTestResultParcelable; import android.net.NetworkUtils; import android.net.NetworkWatchlistManager; +import android.net.OemNetworkPreferences; import android.net.PrivateDnsConfigParcel; import android.net.ProxyInfo; import android.net.QosCallbackException; @@ -171,7 +172,6 @@ import android.security.KeyStore; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.ArraySet; -import android.util.LocalLog; import android.util.Log; import android.util.Pair; import android.util.SparseArray; @@ -192,6 +192,7 @@ import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.LocationPermissionChecker; import com.android.internal.util.MessageUtils; import com.android.modules.utils.BasicShellCommandHandler; +import com.android.modules.utils.LocalLog; import com.android.net.module.util.LinkPropertiesUtils.CompareOrUpdateResult; import com.android.net.module.util.LinkPropertiesUtils.CompareResult; import com.android.server.am.BatteryStatsService; @@ -2030,7 +2031,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mHandler.sendMessage(mHandler.obtainMessage( EVENT_PRIVATE_DNS_VALIDATION_UPDATE, new PrivateDnsValidationUpdate(netId, - InetAddress.parseNumericAddress(ipAddress), + InetAddresses.parseNumericAddress(ipAddress), hostname, validated))); } catch (IllegalArgumentException e) { loge("Error parsing ip address in validation event"); @@ -2142,8 +2143,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); } /** @@ -2726,9 +2727,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(); } @@ -3323,27 +3324,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; } @@ -3357,7 +3358,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"); @@ -3482,7 +3482,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. @@ -3824,7 +3824,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)) { @@ -5443,27 +5443,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)); @@ -5474,38 +5468,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(); } } } @@ -6000,15 +5975,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. @@ -6022,10 +5988,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 @@ -6044,11 +6007,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) { @@ -7281,7 +7239,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. @@ -7694,7 +7652,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); } } @@ -7721,7 +7679,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. // @@ -7729,7 +7687,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 { @@ -8006,7 +7964,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); } @@ -9143,6 +9101,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } } } + /** * Registers {@link QosSocketFilter} with {@link IQosCallback}. * @@ -9192,4 +9151,10 @@ public class ConnectivityService extends IConnectivityManager.Stub public void unregisterQosCallback(@NonNull final IQosCallback callback) { mQosCallbackTracker.unregisterCallback(callback); } + + @Override + public void setOemNetworkPreference(@NonNull final OemNetworkPreferences preference) { + // TODO http://b/176495594 track multiple default networks with networkPreferences + if (DBG) log("setOemNetworkPreference() called with: " + preference.toString()); + } } diff --git a/services/core/java/com/android/server/TestNetworkService.java b/services/core/java/com/android/server/TestNetworkService.java index e96fd390f15a..96f832d26816 100644 --- a/services/core/java/com/android/server/TestNetworkService.java +++ b/services/core/java/com/android/server/TestNetworkService.java @@ -50,6 +50,7 @@ import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.net.module.util.NetdUtils; +import com.android.net.module.util.NetworkStackConstants; import java.io.UncheckedIOException; import java.net.Inet4Address; @@ -280,10 +281,12 @@ class TestNetworkService extends ITestNetworkManager.Stub { // Add global routes (but as non-default, non-internet providing network) if (allowIPv4) { - lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null, iface)); + lp.addRoute(new RouteInfo(new IpPrefix( + NetworkStackConstants.IPV4_ADDR_ANY, 0), null, iface)); } if (allowIPv6) { - lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null, iface)); + lp.addRoute(new RouteInfo(new IpPrefix( + NetworkStackConstants.IPV6_ADDR_ANY, 0), null, iface)); } final TestNetworkAgent agent = new TestNetworkAgent(context, looper, nc, lp, 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/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index ecb915f49bc3..6ae064435da3 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -5632,7 +5632,7 @@ public class ActivityManagerService extends IActivityManager.Stub final int modeFlags, int userId) { enforceNotIsolatedCaller("grantUriPermission"); GrantUri grantUri = new GrantUri(userId, uri, modeFlags); - synchronized (mProcLock) { + synchronized (this) { final ProcessRecord r = getRecordForAppLOSP(caller); if (r == null) { throw new SecurityException("Unable to find app for caller " @@ -5666,7 +5666,7 @@ public class ActivityManagerService extends IActivityManager.Stub public void revokeUriPermission(IApplicationThread caller, String targetPackage, Uri uri, final int modeFlags, int userId) { enforceNotIsolatedCaller("revokeUriPermission"); - synchronized (mProcLock) { + synchronized (this) { final ProcessRecord r = getRecordForAppLOSP(caller); if (r == null) { throw new SecurityException("Unable to find app for caller " diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 1529d71ad0c7..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; @@ -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/biometrics/sensors/face/aidl/AidlConversionUtils.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlConversionUtils.java new file mode 100644 index 000000000000..769c47a94ae3 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlConversionUtils.java @@ -0,0 +1,103 @@ +/* + * 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.biometrics.sensors.face.aidl; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.hardware.biometrics.face.AuthenticationFrame; +import android.hardware.biometrics.face.BaseFrame; +import android.hardware.biometrics.face.Cell; +import android.hardware.biometrics.face.EnrollmentFrame; +import android.hardware.face.FaceAuthenticationFrame; +import android.hardware.face.FaceDataFrame; +import android.hardware.face.FaceEnrollCell; +import android.hardware.face.FaceEnrollFrame; + +/** + * Utilities for converting between hardware and framework-defined AIDL models. + */ +final class AidlConversionUtils { + // Prevent instantiation. + private AidlConversionUtils() {} + + @NonNull + public static FaceAuthenticationFrame convert(@NonNull AuthenticationFrame frame) { + return new FaceAuthenticationFrame(convert(frame.data)); + } + + @NonNull + public static AuthenticationFrame convert(@NonNull FaceAuthenticationFrame frame) { + final AuthenticationFrame convertedFrame = new AuthenticationFrame(); + convertedFrame.data = convert(frame.getData()); + return convertedFrame; + } + + @NonNull + public static FaceEnrollFrame convert(@NonNull EnrollmentFrame frame) { + return new FaceEnrollFrame(convert(frame.cell), frame.stage, convert(frame.data)); + } + + @NonNull + public static EnrollmentFrame convert(@NonNull FaceEnrollFrame frame) { + final EnrollmentFrame convertedFrame = new EnrollmentFrame(); + convertedFrame.cell = convert(frame.getCell()); + convertedFrame.stage = (byte) frame.getStage(); + convertedFrame.data = convert(frame.getData()); + return convertedFrame; + } + + @NonNull + public static FaceDataFrame convert(@NonNull BaseFrame frame) { + return new FaceDataFrame( + frame.acquiredInfo, + frame.vendorCode, + frame.pan, + frame.tilt, + frame.distance, + frame.isCancellable); + } + + @NonNull + public static BaseFrame convert(@NonNull FaceDataFrame frame) { + final BaseFrame convertedFrame = new BaseFrame(); + convertedFrame.acquiredInfo = (byte) frame.getAcquiredInfo(); + convertedFrame.vendorCode = frame.getVendorCode(); + convertedFrame.pan = frame.getPan(); + convertedFrame.tilt = frame.getTilt(); + convertedFrame.distance = frame.getDistance(); + convertedFrame.isCancellable = frame.isCancellable(); + return convertedFrame; + } + + @Nullable + public static FaceEnrollCell convert(@Nullable Cell cell) { + return cell == null ? null : new FaceEnrollCell(cell.x, cell.y, cell.z); + } + + @Nullable + public static Cell convert(@Nullable FaceEnrollCell cell) { + if (cell == null) { + return null; + } + + final Cell convertedCell = new Cell(); + convertedCell.x = cell.getX(); + convertedCell.y = cell.getY(); + convertedCell.z = cell.getZ(); + return convertedCell; + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java index a7bfc4b16dc8..30577667e5e4 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java @@ -28,6 +28,8 @@ import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.common.ICancellationSignal; import android.hardware.biometrics.face.IFace; import android.hardware.biometrics.face.ISession; +import android.hardware.face.FaceAuthenticationFrame; +import android.hardware.face.FaceDataFrame; import android.hardware.face.FaceManager; import android.os.IBinder; import android.os.RemoteException; @@ -193,6 +195,17 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements onAcquiredInternal(acquireInfo, vendorCode, shouldSend); } + /** + * Called each time a new frame is received during face authentication. + * + * @param frame Information about the current frame. + */ + public void onAuthenticationFrame(@NonNull FaceAuthenticationFrame frame) { + // TODO(b/178414967): Send additional frame data to the client callback. + final FaceDataFrame data = frame.getData(); + onAcquired(data.getAcquiredInfo(), data.getVendorCode()); + } + @Override public void onLockoutTimed(long durationMillis) { mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_TIMED); // Lockout metrics are logged as an error code. diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java index afc7f6485bc9..da657b96afd5 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java @@ -27,6 +27,8 @@ import android.hardware.biometrics.face.Feature; import android.hardware.biometrics.face.IFace; import android.hardware.biometrics.face.ISession; import android.hardware.face.Face; +import android.hardware.face.FaceDataFrame; +import android.hardware.face.FaceEnrollFrame; import android.hardware.face.FaceManager; import android.os.IBinder; import android.os.NativeHandle; @@ -110,6 +112,17 @@ public class FaceEnrollClient extends EnrollClient<ISession> { onAcquiredInternal(acquireInfo, vendorCode, shouldSend); } + /** + * Called each time a new frame is received during face enrollment. + * + * @param frame Information about the current frame. + */ + public void onEnrollmentFrame(@NonNull FaceEnrollFrame frame) { + // TODO(b/178414967): Send additional frame data to the client callback. + final FaceDataFrame data = frame.getData(); + onAcquired(data.getAcquiredInfo(), data.getVendorCode()); + } + @Override protected void startHalOperation() { final ArrayList<Byte> token = new ArrayList<>(); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java index f49601ab3fdc..640838c6ee04 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java @@ -45,7 +45,6 @@ import com.android.server.biometrics.SensorServiceStateProto; import com.android.server.biometrics.SensorStateProto; import com.android.server.biometrics.UserStateProto; import com.android.server.biometrics.Utils; -import com.android.server.biometrics.sensors.AcquisitionClient; import com.android.server.biometrics.sensors.AuthenticationConsumer; import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BiometricScheduler; @@ -170,33 +169,39 @@ public class Sensor implements IBinder.DeathRecipient { @Override public void onAuthenticationFrame(AuthenticationFrame frame) { - // TODO(b/174619156): propagate the frame to an AuthenticationClient mHandler.post(() -> { final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof AcquisitionClient)) { - Slog.e(mTag, "onAcquired for non-acquisition client: " + if (!(client instanceof FaceAuthenticationClient)) { + Slog.e(mTag, "onAuthenticationFrame for incompatible client: " + Utils.getClientName(client)); return; - } - final AcquisitionClient<?> acquisitionClient = (AcquisitionClient<?>) client; - acquisitionClient.onAcquired(frame.data.acquiredInfo, frame.data.vendorCode); + } + if (frame == null) { + Slog.e(mTag, "Received null authentication frame for client: " + + Utils.getClientName(client)); + return; + } + ((FaceAuthenticationClient) client).onAuthenticationFrame( + AidlConversionUtils.convert(frame)); }); } @Override public void onEnrollmentFrame(EnrollmentFrame frame) { - // TODO(b/174619156): propagate the frame to an EnrollmentClient mHandler.post(() -> { final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (!(client instanceof AcquisitionClient)) { - Slog.e(mTag, "onAcquired for non-acquisition client: " + if (!(client instanceof FaceEnrollClient)) { + Slog.e(mTag, "onEnrollmentFrame for incompatible client: " + Utils.getClientName(client)); return; } - - final AcquisitionClient<?> acquisitionClient = (AcquisitionClient<?>) client; - acquisitionClient.onAcquired(frame.data.acquiredInfo, frame.data.vendorCode); + if (frame == null) { + Slog.e(mTag, "Received null enrollment frame for client: " + + Utils.getClientName(client)); + return; + } + ((FaceEnrollClient) client).onEnrollmentFrame(AidlConversionUtils.convert(frame)); }); } diff --git a/services/core/java/com/android/server/connectivity/DnsManager.java b/services/core/java/com/android/server/connectivity/DnsManager.java index c70bb080b0b1..43d9ade67a11 100644 --- a/services/core/java/com/android/server/connectivity/DnsManager.java +++ b/services/core/java/com/android/server/connectivity/DnsManager.java @@ -32,6 +32,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.net.IDnsResolver; +import android.net.InetAddresses; import android.net.LinkProperties; import android.net.Network; import android.net.ResolverOptionsParcel; @@ -190,7 +191,7 @@ public class DnsManager { for (String ipAddress : ipAddresses) { try { latestDnses.add(new Pair(hostname, - InetAddress.parseNumericAddress(ipAddress))); + InetAddresses.parseNumericAddress(ipAddress))); } catch (IllegalArgumentException e) {} } // Remove <hostname, ipAddress> pairs that should not be tracked. diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java index 952193b77681..46c49e7fc28c 100644 --- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java +++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java @@ -34,9 +34,9 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; +import com.android.net.module.util.NetworkStackConstants; import com.android.server.net.BaseNetworkObserver; -import java.net.Inet4Address; import java.net.Inet6Address; import java.util.Objects; @@ -433,7 +433,7 @@ public class Nat464Xlat extends BaseNetworkObserver { // clat IPv4 address itself (for those apps, it doesn't matter what // the IP of the gateway is, only that there is one). RouteInfo ipv4Default = new RouteInfo( - new LinkAddress(Inet4Address.ANY, 0), + new LinkAddress(NetworkStackConstants.IPV4_ADDR_ANY, 0), clatAddress.getAddress(), mIface); stacked.addRoute(ipv4Default); stacked.addLinkAddress(clatAddress); 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..fc2c7e01efde 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -51,6 +51,7 @@ import android.net.DnsResolver; import android.net.INetd; import android.net.INetworkManagementEventObserver; import android.net.Ikev2VpnProfile; +import android.net.InetAddresses; import android.net.IpPrefix; import android.net.IpSecManager; import android.net.IpSecManager.IpSecTunnelInterface; @@ -111,6 +112,7 @@ import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.net.LegacyVpnInfo; import com.android.internal.net.VpnConfig; import com.android.internal.net.VpnProfile; +import com.android.net.module.util.NetworkStackConstants; import com.android.server.DeviceIdleInternal; import com.android.server.LocalServices; import com.android.server.net.BaseNetworkObserver; @@ -203,6 +205,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 +280,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 { @@ -327,7 +334,7 @@ public class Vpn { public InetAddress resolve(final String endpoint) throws ExecutionException, InterruptedException { try { - return InetAddress.parseNumericAddress(endpoint); + return InetAddresses.parseNumericAddress(endpoint); } catch (IllegalArgumentException e) { // Endpoint is not numeric : fall through and resolve } @@ -405,6 +412,7 @@ public class Vpn { mLooper = looper; mSystemServices = systemServices; mIkev2SessionCreator = ikev2SessionCreator; + mUserManager = mContext.getSystemService(UserManager.class); mPackage = VpnConfig.LEGACY_VPN; mOwnerUID = getAppUid(mPackage, mUserId); @@ -1119,7 +1127,7 @@ public class Vpn { if (mConfig.dnsServers != null) { for (String dnsServer : mConfig.dnsServers) { - InetAddress address = InetAddress.parseNumericAddress(dnsServer); + InetAddress address = InetAddresses.parseNumericAddress(dnsServer); lp.addDnsServer(address); allowIPv4 |= address instanceof Inet4Address; allowIPv6 |= address instanceof Inet6Address; @@ -1129,10 +1137,12 @@ public class Vpn { lp.setHttpProxy(mConfig.proxyInfo); if (!allowIPv4) { - lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE)); + lp.addRoute(new RouteInfo(new IpPrefix( + NetworkStackConstants.IPV4_ADDR_ANY, 0), RTN_UNREACHABLE)); } if (!allowIPv6) { - lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE)); + lp.addRoute(new RouteInfo(new IpPrefix( + NetworkStackConstants.IPV6_ADDR_ANY, 0), RTN_UNREACHABLE)); } // Concatenate search domains into a string. @@ -1431,7 +1441,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 +1525,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 +1553,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 +1778,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 +1978,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 +2013,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/devicestate/DeviceState.java b/services/core/java/com/android/server/devicestate/DeviceState.java index 802472fcfba8..e496d77deaf5 100644 --- a/services/core/java/com/android/server/devicestate/DeviceState.java +++ b/services/core/java/com/android/server/devicestate/DeviceState.java @@ -16,8 +16,6 @@ package com.android.server.devicestate; -import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE; - import android.annotation.IntRange; import android.annotation.NonNull; @@ -37,16 +35,16 @@ import java.util.Objects; */ public final class DeviceState { /** Unique identifier for the device state. */ - @IntRange(from = INVALID_DEVICE_STATE) + @IntRange(from = 0) private final int mIdentifier; /** String description of the device state. */ @NonNull private final String mName; - public DeviceState(@IntRange(from = INVALID_DEVICE_STATE) int identifier, + public DeviceState(@IntRange(from = 0) int identifier, @NonNull String name) { - if (identifier != INVALID_DEVICE_STATE && identifier < 0) { + if (identifier < 0) { throw new IllegalArgumentException("Identifier must be greater than or equal to zero."); } mIdentifier = identifier; @@ -54,7 +52,7 @@ public final class DeviceState { } /** Returns the unique identifier for the device state. */ - @IntRange(from = INVALID_DEVICE_STATE) + @IntRange(from = 0) public int getIdentifier() { return mIdentifier; } diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java index 375ec3a0f95f..984a17694e07 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java @@ -17,13 +17,13 @@ package com.android.server.devicestate; import static android.Manifest.permission.CONTROL_DEVICE_STATE; -import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE; +import static android.hardware.devicestate.DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; -import android.content.pm.PackageManager; +import android.hardware.devicestate.DeviceStateManager; import android.hardware.devicestate.IDeviceStateManager; import android.hardware.devicestate.IDeviceStateManagerCallback; import android.os.Binder; @@ -31,6 +31,8 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; +import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Slog; import android.util.SparseArray; @@ -62,8 +64,12 @@ import java.util.Optional; * the {@link DeviceStateProvider} to modify the current device state and communicating with the * {@link DeviceStatePolicy policy} to ensure the system is configured to match the requested state. * </p> + * The service also provides the {@link DeviceStateManager} API allowing clients to listen for + * changes in device state and submit requests to override the device state provided by the + * {@link DeviceStateProvider}. * * @see DeviceStatePolicy + * @see DeviceStateManager */ public final class DeviceStateManagerService extends SystemService { private static final String TAG = "DeviceStateManagerService"; @@ -79,11 +85,11 @@ public final class DeviceStateManagerService extends SystemService { @GuardedBy("mLock") private SparseArray<DeviceState> mDeviceStates = new SparseArray<>(); - // The current committed device state. The default of INVALID_DEVICE_STATE will be replaced by + // The current committed device state. The default of UNSET will be replaced by // the current state after the initial callback from the DeviceStateProvider. @GuardedBy("mLock") @NonNull - private DeviceState mCommittedState = new DeviceState(INVALID_DEVICE_STATE, "INVALID"); + private DeviceState mCommittedState = new DeviceState(0, "UNSET"); // The device state that is currently awaiting callback from the policy to be committed. @GuardedBy("mLock") @NonNull @@ -91,19 +97,23 @@ public final class DeviceStateManagerService extends SystemService { // Whether or not the policy is currently waiting to be notified of the current pending state. @GuardedBy("mLock") private boolean mIsPolicyWaitingForState = false; - // The device state that is currently requested and is next to be configured and committed. - // Can be overwritten by an override state value if requested. - @GuardedBy("mLock") - @NonNull - private Optional<DeviceState> mRequestedState = Optional.empty(); - // The most recently requested override state, or empty if no override is requested. + + // The device state that is set by the device state provider. @GuardedBy("mLock") @NonNull - private Optional<DeviceState> mRequestedOverrideState = Optional.empty(); + private Optional<DeviceState> mBaseState = Optional.empty(); - // List of registered callbacks indexed by process id. + // List of processes registered to receive notifications about changes to device state and + // request status indexed by process id. @GuardedBy("mLock") - private final SparseArray<CallbackRecord> mCallbacks = new SparseArray<>(); + private final SparseArray<ProcessRecord> mProcessRecords = new SparseArray<>(); + // List of override requests with the highest precedence request at the end. + @GuardedBy("mLock") + private final ArrayList<OverrideRequestRecord> mRequestRecords = new ArrayList<>(); + // Set of override requests that are pending a call to notifyStatusIfNeeded() to be notified + // of a change in status. + @GuardedBy("mLock") + private final ArraySet<OverrideRequestRecord> mRequestsPendingStatusChange = new ArraySet<>(); public DeviceStateManagerService(@NonNull Context context) { this(context, new DeviceStatePolicyImpl(context)); @@ -148,55 +158,32 @@ public final class DeviceStateManagerService extends SystemService { } /** - * Returns the requested state. The service will configure the device to match the requested - * state when possible. - */ - @NonNull - Optional<DeviceState> getRequestedState() { - synchronized (mLock) { - return mRequestedState; - } - } - - /** - * Overrides the current device state with the provided state. + * Returns the base state. The service will configure the device to match the base state when + * there is no active request to override the base state. * - * @return {@code true} if the override state is valid and supported, {@code false} otherwise. + * @see #getOverrideState() */ - boolean setOverrideState(int overrideState) { - if (getContext().checkCallingOrSelfPermission(CONTROL_DEVICE_STATE) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Must hold permission " + CONTROL_DEVICE_STATE); - } - + @NonNull + Optional<DeviceState> getBaseState() { synchronized (mLock) { - if (overrideState != INVALID_DEVICE_STATE && !isSupportedStateLocked(overrideState)) { - return false; - } - - mRequestedOverrideState = getStateLocked(overrideState); - updatePendingStateLocked(); + return mBaseState; } - - notifyPolicyIfNeeded(); - return true; - } - - /** - * Clears an override state set with {@link #setOverrideState(int)}. - */ - void clearOverrideState() { - setOverrideState(INVALID_DEVICE_STATE); } /** - * Returns the current requested override state, or {@link Optional#empty()} if no override - * state is requested. + * Returns the current override state, or {@link Optional#empty()} if no override state is + * requested. If an override states is present, the returned state will take precedence over + * the base state returned from {@link #getBaseState()}. */ @NonNull Optional<DeviceState> getOverrideState() { synchronized (mLock) { - return mRequestedOverrideState; + if (mRequestRecords.isEmpty()) { + return Optional.empty(); + } + + OverrideRequestRecord topRequest = mRequestRecords.get(mRequestRecords.size() - 1); + return Optional.of(topRequest.mRequestedState); } } @@ -211,6 +198,17 @@ public final class DeviceStateManagerService extends SystemService { } } + /** Returns the list of currently supported device state identifiers. */ + private int[] getSupportedStateIdentifiers() { + synchronized (mLock) { + int[] supportedStates = new int[mDeviceStates.size()]; + for (int i = 0; i < supportedStates.length; i++) { + supportedStates[i] = mDeviceStates.valueAt(i).getIdentifier(); + } + return supportedStates; + } + } + @VisibleForTesting IDeviceStateManager getBinderService() { return mBinderService; @@ -224,22 +222,26 @@ public final class DeviceStateManagerService extends SystemService { mDeviceStates.put(state.getIdentifier(), state); } - if (mRequestedState.isPresent() - && !isSupportedStateLocked(mRequestedState.get().getIdentifier())) { - // The current requested state is no longer valid. We'll clear it here, though + if (mBaseState.isPresent() + && !isSupportedStateLocked(mBaseState.get().getIdentifier())) { + // The current base state is no longer valid. We'll clear it here, though // we won't actually update the current state until a callback comes from the // provider with the most recent state. - mRequestedState = Optional.empty(); + mBaseState = Optional.empty(); } - if (mRequestedOverrideState.isPresent() - && !isSupportedStateLocked(mRequestedOverrideState.get().getIdentifier())) { - // The current override state is no longer valid. We'll clear it here and update - // the committed state if necessary. - mRequestedOverrideState = Optional.empty(); + + final int requestSize = mRequestRecords.size(); + for (int i = 0; i < requestSize; i++) { + OverrideRequestRecord request = mRequestRecords.get(i); + if (!isSupportedStateLocked(request.mRequestedState.getIdentifier())) { + request.setStatusLocked(OverrideRequestRecord.STATUS_CANCELED); + } } + updatePendingStateLocked(); } + notifyRequestsOfStatusChangeIfNeeded(); notifyPolicyIfNeeded(); } @@ -261,20 +263,37 @@ public final class DeviceStateManagerService extends SystemService { } /** - * Requests that the system enter the provided {@code state}. The request may not be honored - * under certain conditions, for example if the provided state is not supported. + * Requests to set the base state. The request may not be honored under certain conditions, for + * example if the provided state is not supported. * * @see #isSupportedStateLocked(int) */ - private void requestState(int identifier) { + private void setBaseState(int identifier) { synchronized (mLock) { - final Optional<DeviceState> requestedState = getStateLocked(identifier); - if (requestedState.isPresent()) { - mRequestedState = requestedState; + if (mBaseState.isPresent() && mBaseState.get().getIdentifier() == identifier) { + // Base state hasn't changed. Nothing to do. + return; + } + + final Optional<DeviceState> baseState = getStateLocked(identifier); + if (!baseState.isPresent()) { + throw new IllegalArgumentException("Base state is not supported"); } + + mBaseState = baseState; + + final int requestSize = mRequestRecords.size(); + for (int i = 0; i < requestSize; i++) { + OverrideRequestRecord request = mRequestRecords.get(i); + if ((request.mFlags & FLAG_CANCEL_WHEN_BASE_CHANGES) > 0) { + request.setStatusLocked(OverrideRequestRecord.STATUS_CANCELED); + } + } + updatePendingStateLocked(); } + notifyRequestsOfStatusChangeIfNeeded(); notifyPolicyIfNeeded(); } @@ -290,10 +309,10 @@ public final class DeviceStateManagerService extends SystemService { } final DeviceState stateToConfigure; - if (mRequestedOverrideState.isPresent()) { - stateToConfigure = mRequestedOverrideState.get(); + if (!mRequestRecords.isEmpty()) { + stateToConfigure = mRequestRecords.get(mRequestRecords.size() - 1).mRequestedState; } else { - stateToConfigure = mRequestedState.orElse(null); + stateToConfigure = mBaseState.orElse(null); } if (stateToConfigure == null) { @@ -360,6 +379,13 @@ public final class DeviceStateManagerService extends SystemService { } mCommittedState = mPendingState.get(); newState = mCommittedState.getIdentifier(); + + if (!mRequestRecords.isEmpty()) { + final OverrideRequestRecord topRequest = + mRequestRecords.get(mRequestRecords.size() - 1); + topRequest.setStatusLocked(OverrideRequestRecord.STATUS_ACTIVE); + } + mPendingState = Optional.empty(); updatePendingStateLocked(); } @@ -367,6 +393,9 @@ public final class DeviceStateManagerService extends SystemService { // Notify callbacks of a change. notifyDeviceStateChanged(newState); + // Notify the top request that it's active. + notifyRequestsOfStatusChangeIfNeeded(); + // Try to configure the next state if needed. notifyPolicyIfNeeded(); } @@ -377,43 +406,69 @@ public final class DeviceStateManagerService extends SystemService { "Attempting to notify callbacks with service lock held."); } - // Grab the lock and copy the callbacks. - ArrayList<CallbackRecord> callbacks; + // Grab the lock and copy the process records. + ArrayList<ProcessRecord> registeredProcesses; synchronized (mLock) { - if (mCallbacks.size() == 0) { + if (mProcessRecords.size() == 0) { return; } - callbacks = new ArrayList<>(); - for (int i = 0; i < mCallbacks.size(); i++) { - callbacks.add(mCallbacks.valueAt(i)); + registeredProcesses = new ArrayList<>(); + for (int i = 0; i < mProcessRecords.size(); i++) { + registeredProcesses.add(mProcessRecords.valueAt(i)); + } + } + + // After releasing the lock, send the notifications out. + for (int i = 0; i < registeredProcesses.size(); i++) { + registeredProcesses.get(i).notifyDeviceStateAsync(deviceState); + } + } + + /** + * Notifies all dirty requests (requests that have a change in status, but have not yet been + * notified) that their status has changed. + */ + private void notifyRequestsOfStatusChangeIfNeeded() { + if (Thread.holdsLock(mLock)) { + throw new IllegalStateException( + "Attempting to notify requests with service lock held."); + } + + ArraySet<OverrideRequestRecord> dirtyRequests; + synchronized (mLock) { + if (mRequestsPendingStatusChange.isEmpty()) { + return; } + + dirtyRequests = new ArraySet<>(mRequestsPendingStatusChange); + mRequestsPendingStatusChange.clear(); } // After releasing the lock, send the notifications out. - for (int i = 0; i < callbacks.size(); i++) { - callbacks.get(i).notifyDeviceStateAsync(deviceState); + for (int i = 0; i < dirtyRequests.size(); i++) { + dirtyRequests.valueAt(i).notifyStatusIfNeeded(); } } - private void registerCallbackInternal(IDeviceStateManagerCallback callback, int callingPid) { + private void registerProcess(int pid, IDeviceStateManagerCallback callback) { int currentState; - CallbackRecord record; + ProcessRecord record; // Grab the lock to register the callback and get the current state. synchronized (mLock) { - if (mCallbacks.contains(callingPid)) { + if (mProcessRecords.contains(pid)) { throw new SecurityException("The calling process has already registered an" + " IDeviceStateManagerCallback."); } - record = new CallbackRecord(callback, callingPid); + record = new ProcessRecord(callback, pid); try { callback.asBinder().linkToDeath(record, 0); } catch (RemoteException ex) { throw new RuntimeException(ex); } - mCallbacks.put(callingPid, record); + mProcessRecords.put(pid, record); currentState = mCommittedState.getIdentifier(); } @@ -421,10 +476,86 @@ public final class DeviceStateManagerService extends SystemService { record.notifyDeviceStateAsync(currentState); } - private void unregisterCallbackInternal(CallbackRecord record) { + private void handleProcessDied(ProcessRecord processRecord) { + synchronized (mLock) { + // Cancel all requests from this process. + final int requestCount = processRecord.mRequestRecords.size(); + for (int i = 0; i < requestCount; i++) { + final OverrideRequestRecord request = processRecord.mRequestRecords.valueAt(i); + // Cancel the request but don't mark it as dirty since there's no need to send + // notifications if the process has died. + request.setStatusLocked(OverrideRequestRecord.STATUS_CANCELED, + false /* markDirty */); + } + + mProcessRecords.remove(processRecord.mPid); + + updatePendingStateLocked(); + } + + notifyPolicyIfNeeded(); + } + + private void requestStateInternal(int state, int flags, int callingPid, + @NonNull IBinder token) { + synchronized (mLock) { + final ProcessRecord processRecord = mProcessRecords.get(callingPid); + if (processRecord == null) { + throw new IllegalStateException("Process " + callingPid + + " has no registered callback."); + } + + if (processRecord.mRequestRecords.get(token) != null) { + throw new IllegalStateException("Request has already been made for the supplied" + + " token: " + token); + } + + final Optional<DeviceState> deviceState = getStateLocked(state); + if (!deviceState.isPresent()) { + throw new IllegalArgumentException("Requested state: " + state + + " is not supported."); + } + + OverrideRequestRecord topRecord = mRequestRecords.isEmpty() + ? null : mRequestRecords.get(mRequestRecords.size() - 1); + if (topRecord != null) { + topRecord.setStatusLocked(OverrideRequestRecord.STATUS_SUSPENDED); + } + + final OverrideRequestRecord request = + new OverrideRequestRecord(processRecord, token, deviceState.get(), flags); + mRequestRecords.add(request); + processRecord.mRequestRecords.put(request.mToken, request); + // We don't set the status of the new request to ACTIVE here as it will be set in + // commitPendingState(). + + updatePendingStateLocked(); + } + + notifyRequestsOfStatusChangeIfNeeded(); + notifyPolicyIfNeeded(); + } + + private void cancelRequestInternal(int callingPid, @NonNull IBinder token) { synchronized (mLock) { - mCallbacks.remove(record.mPid); + final ProcessRecord processRecord = mProcessRecords.get(callingPid); + if (processRecord == null) { + throw new IllegalStateException("Process " + callingPid + + " has no registered callback."); + } + + OverrideRequestRecord request = processRecord.mRequestRecords.get(token); + if (request == null) { + throw new IllegalStateException("No known request for the given token"); + } + + request.setStatusLocked(OverrideRequestRecord.STATUS_CANCELED); + + updatePendingStateLocked(); } + + notifyRequestsOfStatusChangeIfNeeded(); + notifyPolicyIfNeeded(); } private void dumpInternal(PrintWriter pw) { @@ -433,15 +564,26 @@ public final class DeviceStateManagerService extends SystemService { synchronized (mLock) { pw.println(" mCommittedState=" + mCommittedState); pw.println(" mPendingState=" + mPendingState); - pw.println(" mRequestedState=" + mRequestedState); - pw.println(" mRequestedOverrideState=" + mRequestedOverrideState); + pw.println(" mBaseState=" + mBaseState); + pw.println(" mOverrideState=" + getOverrideState()); - final int callbackCount = mCallbacks.size(); + final int processCount = mProcessRecords.size(); pw.println(); - pw.println("Callbacks: size=" + callbackCount); - for (int i = 0; i < callbackCount; i++) { - CallbackRecord callback = mCallbacks.valueAt(i); - pw.println(" " + i + ": mPid=" + callback.mPid); + pw.println("Registered processes: size=" + processCount); + for (int i = 0; i < processCount; i++) { + ProcessRecord processRecord = mProcessRecords.valueAt(i); + pw.println(" " + i + ": mPid=" + processRecord.mPid); + } + + final int requestCount = mRequestRecords.size(); + pw.println(); + pw.println("Override requests: size=" + requestCount); + for (int i = 0; i < requestCount; i++) { + OverrideRequestRecord requestRecord = mRequestRecords.get(i); + pw.println(" " + i + ": mPid=" + requestRecord.mProcessRecord.mPid + + ", mRequestedState=" + requestRecord.mRequestedState + + ", mFlags=" + requestRecord.mFlags + + ", mStatus=" + requestRecord.statusToString(requestRecord.mStatus)); } } } @@ -452,12 +594,6 @@ public final class DeviceStateManagerService extends SystemService { if (newDeviceStates.length == 0) { throw new IllegalArgumentException("Supported device states must not be empty"); } - for (int i = 0; i < newDeviceStates.length; i++) { - if (newDeviceStates[i].getIdentifier() == INVALID_DEVICE_STATE) { - throw new IllegalArgumentException( - "Supported device states includes INVALID_DEVICE_STATE identifier"); - } - } updateSupportedStates(newDeviceStates); } @@ -467,22 +603,24 @@ public final class DeviceStateManagerService extends SystemService { throw new IllegalArgumentException("Invalid identifier: " + identifier); } - requestState(identifier); + setBaseState(identifier); } } - private final class CallbackRecord implements IBinder.DeathRecipient { + private final class ProcessRecord implements IBinder.DeathRecipient { private final IDeviceStateManagerCallback mCallback; private final int mPid; - CallbackRecord(IDeviceStateManagerCallback callback, int pid) { + private final ArrayMap<IBinder, OverrideRequestRecord> mRequestRecords = new ArrayMap<>(); + + ProcessRecord(IDeviceStateManagerCallback callback, int pid) { mCallback = callback; mPid = pid; } @Override public void binderDied() { - unregisterCallbackInternal(this); + handleProcessDied(this); } public void notifyDeviceStateAsync(int devicestate) { @@ -493,6 +631,119 @@ public final class DeviceStateManagerService extends SystemService { ex); } } + + public void notifyRequestActiveAsync(OverrideRequestRecord request) { + try { + mCallback.onRequestActive(request.mToken); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to notify process " + mPid + " that request state changed.", + ex); + } + } + + public void notifyRequestSuspendedAsync(OverrideRequestRecord request) { + try { + mCallback.onRequestSuspended(request.mToken); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to notify process " + mPid + " that request state changed.", + ex); + } + } + + public void notifyRequestCanceledAsync(OverrideRequestRecord request) { + try { + mCallback.onRequestCanceled(request.mToken); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to notify process " + mPid + " that request state changed.", + ex); + } + } + } + + /** A record describing a request to override the state of the device. */ + private final class OverrideRequestRecord { + public static final int STATUS_UNKNOWN = 0; + public static final int STATUS_ACTIVE = 1; + public static final int STATUS_SUSPENDED = 2; + public static final int STATUS_CANCELED = 3; + + @Nullable + public String statusToString(int status) { + switch (status) { + case STATUS_ACTIVE: + return "ACTIVE"; + case STATUS_SUSPENDED: + return "SUSPENDED"; + case STATUS_CANCELED: + return "CANCELED"; + case STATUS_UNKNOWN: + return "UNKNOWN"; + default: + return null; + } + } + + private final ProcessRecord mProcessRecord; + @NonNull + private final IBinder mToken; + @NonNull + private final DeviceState mRequestedState; + private final int mFlags; + + private int mStatus = STATUS_UNKNOWN; + private int mLastNotifiedStatus = STATUS_UNKNOWN; + + OverrideRequestRecord(@NonNull ProcessRecord processRecord, @NonNull IBinder token, + @NonNull DeviceState requestedState, int flags) { + mProcessRecord = processRecord; + mToken = token; + mRequestedState = requestedState; + mFlags = flags; + } + + public void setStatusLocked(int status) { + setStatusLocked(status, true /* markDirty */); + } + + public void setStatusLocked(int status, boolean markDirty) { + if (mStatus != status) { + if (mStatus == STATUS_CANCELED) { + throw new IllegalStateException( + "Can not alter the status of a request after set to CANCELED."); + } + + mStatus = status; + + if (mStatus == STATUS_CANCELED) { + mRequestRecords.remove(this); + mProcessRecord.mRequestRecords.remove(mToken); + } + + if (markDirty) { + mRequestsPendingStatusChange.add(this); + } + } + } + + public void notifyStatusIfNeeded() { + int stateToReport; + synchronized (mLock) { + if (mLastNotifiedStatus == mStatus) { + return; + } + + stateToReport = mStatus; + mLastNotifiedStatus = mStatus; + } + + if (stateToReport == STATUS_ACTIVE) { + mProcessRecord.notifyRequestActiveAsync(this); + } else if (stateToReport == STATUS_SUSPENDED) { + mProcessRecord.notifyRequestSuspendedAsync(this); + } else if (stateToReport == STATUS_CANCELED) { + mProcessRecord.notifyRequestCanceledAsync(this); + } + } } /** Implementation of {@link IDeviceStateManager} published as a binder service. */ @@ -506,13 +757,59 @@ public final class DeviceStateManagerService extends SystemService { final int callingPid = Binder.getCallingPid(); final long token = Binder.clearCallingIdentity(); try { - registerCallbackInternal(callback, callingPid); + registerProcess(callingPid, callback); } finally { Binder.restoreCallingIdentity(token); } } @Override // Binder call + public int[] getSupportedDeviceStates() { + final long token = Binder.clearCallingIdentity(); + try { + return getSupportedStateIdentifiers(); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override // Binder call + public void requestState(IBinder token, int state, int flags) { + getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE, + "Permission required to request device state."); + + if (token == null) { + throw new IllegalArgumentException("Request token must not be null."); + } + + final int callingPid = Binder.getCallingPid(); + final long callingIdentity = Binder.clearCallingIdentity(); + try { + requestStateInternal(state, flags, callingPid, token); + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + } + + @Override // Binder call + public void cancelRequest(IBinder token) { + getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE, + "Permission required to clear requested device state."); + + if (token == null) { + throw new IllegalArgumentException("Request token must not be null."); + } + + final int callingPid = Binder.getCallingPid(); + final long callingIdentity = Binder.clearCallingIdentity(); + try { + cancelRequestInternal(callingPid, token); + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + } + + @Override // Binder call public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver result) { new DeviceStateManagerShellCommand(DeviceStateManagerService.this) diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java index 7914531f9910..6cc55a6c4774 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java @@ -16,6 +16,13 @@ package com.android.server.devicestate; +import static android.Manifest.permission.CONTROL_DEVICE_STATE; + +import android.annotation.Nullable; +import android.content.Context; +import android.hardware.devicestate.DeviceStateManager; +import android.hardware.devicestate.DeviceStateRequest; +import android.os.Binder; import android.os.ShellCommand; import java.io.PrintWriter; @@ -27,10 +34,15 @@ import java.util.Optional; * Use with {@code adb shell cmd device_state ...}. */ public class DeviceStateManagerShellCommand extends ShellCommand { - private final DeviceStateManagerService mInternal; + @Nullable + private static DeviceStateRequest sLastRequest; + + private final DeviceStateManagerService mService; + private final DeviceStateManager mClient; public DeviceStateManagerShellCommand(DeviceStateManagerService service) { - mInternal = service; + mService = service; + mClient = service.getContext().getSystemService(DeviceStateManager.class); } @Override @@ -51,15 +63,15 @@ public class DeviceStateManagerShellCommand extends ShellCommand { } private void printState(PrintWriter pw) { - DeviceState committedState = mInternal.getCommittedState(); - Optional<DeviceState> requestedState = mInternal.getRequestedState(); - Optional<DeviceState> requestedOverrideState = mInternal.getOverrideState(); + DeviceState committedState = mService.getCommittedState(); + Optional<DeviceState> baseState = mService.getBaseState(); + Optional<DeviceState> overrideState = mService.getOverrideState(); pw.println("Committed state: " + committedState); - if (requestedOverrideState.isPresent()) { + if (overrideState.isPresent()) { pw.println("----------------------"); - pw.println("Base state: " + requestedState.orElse(null)); - pw.println("Override state: " + requestedOverrideState.get()); + pw.println("Base state: " + baseState.orElse(null)); + pw.println("Override state: " + overrideState.get()); } } @@ -67,32 +79,51 @@ public class DeviceStateManagerShellCommand extends ShellCommand { final String nextArg = getNextArg(); if (nextArg == null) { printState(pw); - } else if ("reset".equals(nextArg)) { - mInternal.clearOverrideState(); - } else { - int requestedState; - try { - requestedState = Integer.parseInt(nextArg); - } catch (NumberFormatException e) { - getErrPrintWriter().println("Error: requested state should be an integer"); - return -1; - } + } - boolean success = mInternal.setOverrideState(requestedState); - if (!success) { - getErrPrintWriter().println("Error: failed to set override state. Run:"); - getErrPrintWriter().println(""); - getErrPrintWriter().println(" print-states"); - getErrPrintWriter().println(""); - getErrPrintWriter().println("to get the list of currently supported device states"); - return -1; + final Context context = mService.getContext(); + context.enforceCallingOrSelfPermission( + CONTROL_DEVICE_STATE, + "Permission required to request device state."); + final long callingIdentity = Binder.clearCallingIdentity(); + try { + if ("reset".equals(nextArg)) { + if (sLastRequest != null) { + mClient.cancelRequest(sLastRequest); + sLastRequest = null; + } + } else { + int requestedState = Integer.parseInt(nextArg); + DeviceStateRequest request = DeviceStateRequest.newBuilder(requestedState).build(); + + mClient.requestState(request, null /* executor */, null /* callback */); + if (sLastRequest != null) { + mClient.cancelRequest(sLastRequest); + } + + sLastRequest = request; } + } catch (NumberFormatException e) { + getErrPrintWriter().println("Error: requested state should be an integer"); + return -1; + } catch (IllegalArgumentException e) { + getErrPrintWriter().println("Error: " + e.getMessage()); + getErrPrintWriter().println("-------------------"); + getErrPrintWriter().println("Run:"); + getErrPrintWriter().println(""); + getErrPrintWriter().println(" print-states"); + getErrPrintWriter().println(""); + getErrPrintWriter().println("to get the list of currently supported device states"); + return -1; + } finally { + Binder.restoreCallingIdentity(callingIdentity); } + return 0; } private int runPrintStates(PrintWriter pw) { - DeviceState[] states = mInternal.getSupportedStates(); + DeviceState[] states = mService.getSupportedStates(); pw.print("Supported states: [\n"); for (int i = 0; i < states.length; i++) { pw.print(" " + states[i] + ",\n"); diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index e5151d84c33e..0950d5dd076f 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -514,8 +514,8 @@ public final class DisplayManagerService extends SystemService { DeviceStateManager deviceStateManager = mContext.getSystemService(DeviceStateManager.class); - deviceStateManager.registerDeviceStateListener(new DeviceStateListener(), - new HandlerExecutor(mHandler)); + deviceStateManager.addDeviceStateListener(new HandlerExecutor(mHandler), + new DeviceStateListener()); scheduleTraversalLocked(false); } 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/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/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java new file mode 100644 index 000000000000..a83edb75badb --- /dev/null +++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java @@ -0,0 +1,241 @@ +/* + * 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.os; + +import static android.os.ParcelFileDescriptor.MODE_READ_WRITE; +import static android.os.Process.THREAD_PRIORITY_BACKGROUND; + +import android.annotation.AppIdInt; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.content.Context; +import android.os.FileObserver; +import android.os.Handler; +import android.os.ParcelFileDescriptor; +import android.os.UserHandle; +import android.util.Slog; +import android.util.SparseArray; +import android.util.proto.ProtoInputStream; + +import com.android.internal.annotations.GuardedBy; +import com.android.server.BootReceiver; +import com.android.server.ServiceThread; +import com.android.server.os.TombstoneProtos.Tombstone; + +import libcore.io.IoUtils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Optional; + +/** + * A class to manage native tombstones. + */ +public final class NativeTombstoneManager { + private static final String TAG = NativeTombstoneManager.class.getSimpleName(); + + private static final File TOMBSTONE_DIR = new File("/data/tombstones"); + + private final Context mContext; + private final Handler mHandler; + private final TombstoneWatcher mWatcher; + + private final Object mLock = new Object(); + + @GuardedBy("mLock") + private final SparseArray<TombstoneFile> mTombstones; + + NativeTombstoneManager(Context context) { + mTombstones = new SparseArray<TombstoneFile>(); + mContext = context; + + final ServiceThread thread = new ServiceThread(TAG + ":tombstoneWatcher", + THREAD_PRIORITY_BACKGROUND, true /* allowIo */); + thread.start(); + mHandler = thread.getThreadHandler(); + + mWatcher = new TombstoneWatcher(); + mWatcher.startWatching(); + } + + void onSystemReady() { + // Scan existing tombstones. + mHandler.post(() -> { + final File[] tombstoneFiles = TOMBSTONE_DIR.listFiles(); + for (int i = 0; tombstoneFiles != null && i < tombstoneFiles.length; i++) { + if (tombstoneFiles[i].isFile()) { + handleTombstone(tombstoneFiles[i]); + } + } + }); + } + + private void handleTombstone(File path) { + final String filename = path.getName(); + if (!filename.startsWith("tombstone_")) { + return; + } + + if (filename.endsWith(".pb")) { + handleProtoTombstone(path); + } else { + BootReceiver.addTombstoneToDropBox(mContext, path); + } + } + + private void handleProtoTombstone(File path) { + final String filename = path.getName(); + if (!filename.endsWith(".pb")) { + Slog.w(TAG, "unexpected tombstone name: " + path); + return; + } + + final String suffix = filename.substring("tombstone_".length()); + final String numberStr = suffix.substring(0, suffix.length() - 3); + + int number; + try { + number = Integer.parseInt(numberStr); + if (number < 0 || number > 99) { + Slog.w(TAG, "unexpected tombstone name: " + path); + return; + } + } catch (NumberFormatException ex) { + Slog.w(TAG, "unexpected tombstone name: " + path); + return; + } + + ParcelFileDescriptor pfd; + try { + pfd = ParcelFileDescriptor.open(path, MODE_READ_WRITE); + } catch (FileNotFoundException ex) { + Slog.w(TAG, "failed to open " + path, ex); + return; + } + + final Optional<TombstoneFile> parsedTombstone = TombstoneFile.parse(pfd); + if (!parsedTombstone.isPresent()) { + IoUtils.closeQuietly(pfd); + return; + } + + synchronized (mLock) { + TombstoneFile previous = mTombstones.get(number); + if (previous != null) { + previous.dispose(); + } + + mTombstones.put(number, parsedTombstone.get()); + } + } + + static class TombstoneFile { + final ParcelFileDescriptor mPfd; + + final @UserIdInt int mUserId; + final @AppIdInt int mAppId; + + boolean mPurged = false; + + TombstoneFile(ParcelFileDescriptor pfd, @UserIdInt int userId, @AppIdInt int appId) { + mPfd = pfd; + mUserId = userId; + mAppId = appId; + } + + public boolean matches(Optional<Integer> userId, Optional<Integer> appId) { + if (mPurged) { + return false; + } + + if (userId.isPresent() && userId.get() != mUserId) { + return false; + } + + if (appId.isPresent() && appId.get() != mAppId) { + return false; + } + + return true; + } + + public void dispose() { + IoUtils.closeQuietly(mPfd); + } + + static Optional<TombstoneFile> parse(ParcelFileDescriptor pfd) { + final FileInputStream is = new FileInputStream(pfd.getFileDescriptor()); + final ProtoInputStream stream = new ProtoInputStream(is); + + int uid = 0; + String selinuxLabel = ""; + + try { + while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (stream.getFieldNumber()) { + case (int) Tombstone.UID: + uid = stream.readInt(Tombstone.UID); + break; + + case (int) Tombstone.SELINUX_LABEL: + selinuxLabel = stream.readString(Tombstone.SELINUX_LABEL); + break; + + default: + break; + } + } + } catch (IOException ex) { + Slog.e(TAG, "Failed to parse tombstone", ex); + return Optional.empty(); + } + + if (!UserHandle.isApp(uid)) { + Slog.e(TAG, "Tombstone's UID (" + uid + ") not an app, ignoring"); + return Optional.empty(); + } + + final int userId = UserHandle.getUserId(uid); + final int appId = UserHandle.getAppId(uid); + + if (!selinuxLabel.startsWith("u:r:untrusted_app")) { + Slog.e(TAG, "Tombstone has invalid selinux label (" + selinuxLabel + "), ignoring"); + return Optional.empty(); + } + + return Optional.of(new TombstoneFile(pfd, userId, appId)); + } + } + + class TombstoneWatcher extends FileObserver { + TombstoneWatcher() { + // Tombstones can be created either by linking an O_TMPFILE temporary file (CREATE), + // or by moving a named temporary file in the same directory on kernels where O_TMPFILE + // isn't supported (MOVED_TO). + super(TOMBSTONE_DIR, FileObserver.CREATE | FileObserver.MOVED_TO); + } + + @Override + public void onEvent(int event, @Nullable String path) { + mHandler.post(() -> { + handleTombstone(new File(TOMBSTONE_DIR, path)); + }); + } + } +} diff --git a/services/core/java/com/android/server/os/NativeTombstoneManagerService.java b/services/core/java/com/android/server/os/NativeTombstoneManagerService.java new file mode 100644 index 000000000000..cb3c7ff0c07d --- /dev/null +++ b/services/core/java/com/android/server/os/NativeTombstoneManagerService.java @@ -0,0 +1,50 @@ +/* + * 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.os; + +import android.content.Context; + +import com.android.server.LocalServices; +import com.android.server.SystemService; + +/** + * Service that tracks and manages native tombstones. + * + * @hide + */ +public class NativeTombstoneManagerService extends SystemService { + private static final String TAG = "NativeTombstoneManagerService"; + + private NativeTombstoneManager mManager; + + public NativeTombstoneManagerService(Context context) { + super(context); + } + + @Override + public void onStart() { + mManager = new NativeTombstoneManager(getContext()); + LocalServices.addService(NativeTombstoneManager.class, mManager); + } + + @Override + public void onBootPhase(int phase) { + if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) { + mManager.onSystemReady(); + } + } +} 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/IncrementalStates.java b/services/core/java/com/android/server/pm/IncrementalStates.java index f5ec595cc45a..ecafdfdbd6f1 100644 --- a/services/core/java/com/android/server/pm/IncrementalStates.java +++ b/services/core/java/com/android/server/pm/IncrementalStates.java @@ -249,24 +249,6 @@ public final class IncrementalStates { } /** - * @return the current startable state. - */ - public boolean isStartable() { - synchronized (mLock) { - return mStartableState.isStartable(); - } - } - - /** - * @return Whether the package is still being loaded or has been fully loaded. - */ - public boolean isLoading() { - synchronized (mLock) { - return mLoadingState.isLoading(); - } - } - - /** * @return all current states in a Parcelable. */ public IncrementalStatesInfo getIncrementalStatesInfo() { diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index c4a23f961072..f240d85572c4 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -1316,10 +1316,6 @@ public class LauncherAppsService extends SystemService { } finally { mListeners.finishBroadcast(); } - PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class); - pmi.registerInstalledLoadingProgressCallback(packageName, - new PackageLoadingProgressCallback(packageName, user), - user.getIdentifier()); super.onPackageAdded(packageName, uid); } @@ -1542,38 +1538,5 @@ public class LauncherAppsService extends SystemService { checkCallbackCount(); } } - - class PackageLoadingProgressCallback extends - PackageManagerInternal.InstalledLoadingProgressCallback { - private String mPackageName; - private UserHandle mUser; - - PackageLoadingProgressCallback(String packageName, UserHandle user) { - super(mCallbackHandler); - mPackageName = packageName; - mUser = user; - } - - @Override - public void onLoadingProgressChanged(float progress) { - final int n = mListeners.beginBroadcast(); - try { - for (int i = 0; i < n; i++) { - IOnAppsChangedListener listener = mListeners.getBroadcastItem(i); - BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i); - if (!isEnabledProfileOf(cookie.user, mUser, "onLoadingProgressChanged")) { - continue; - } - try { - listener.onPackageLoadingProgressChanged(mUser, mPackageName, progress); - } catch (RemoteException re) { - Slog.d(TAG, "Callback failed ", re); - } - } - } finally { - mListeners.finishBroadcast(); - } - } - } } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index f444ed6df2a3..5d4fa1e1c123 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -24,7 +24,6 @@ import static android.Manifest.permission.SET_HARMFUL_APP_WARNINGS; import static android.app.AppOpsManager.MODE_DEFAULT; import static android.app.AppOpsManager.MODE_IGNORED; import static android.content.Intent.ACTION_MAIN; -import static android.content.Intent.CATEGORY_BROWSABLE; import static android.content.Intent.CATEGORY_DEFAULT; import static android.content.Intent.CATEGORY_HOME; import static android.content.Intent.EXTRA_LONG_VERSION_CODE; @@ -68,11 +67,7 @@ import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_RESTORE; import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_SETUP; import static android.content.pm.PackageManager.INSTALL_STAGED; import static android.content.pm.PackageManager.INSTALL_SUCCEEDED; -import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS; -import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK; -import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK; import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER; -import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED; import static android.content.pm.PackageManager.MATCH_ALL; import static android.content.pm.PackageManager.MATCH_ANY_USER; import static android.content.pm.PackageManager.MATCH_APEX; @@ -177,6 +172,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; @@ -377,11 +373,6 @@ import com.android.server.pm.dex.DexManager; import com.android.server.pm.dex.DexoptOptions; import com.android.server.pm.dex.PackageDexUsage; import com.android.server.pm.dex.ViewCompiler; -import com.android.server.pm.verify.domain.DomainVerificationManagerInternal; -import com.android.server.pm.verify.domain.DomainVerificationService; -import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy; -import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyV1; -import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyV2; import com.android.server.pm.parsing.PackageCacher; import com.android.server.pm.parsing.PackageInfoUtils; import com.android.server.pm.parsing.PackageParser2; @@ -395,6 +386,11 @@ import com.android.server.pm.permission.LegacyPermissionManagerService; import com.android.server.pm.permission.Permission; import com.android.server.pm.permission.PermissionManagerService; import com.android.server.pm.permission.PermissionManagerServiceInternal; +import com.android.server.pm.verify.domain.DomainVerificationManagerInternal; +import com.android.server.pm.verify.domain.DomainVerificationService; +import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy; +import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyV1; +import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyV2; import com.android.server.rollback.RollbackManagerInternal; import com.android.server.security.VerityUtils; import com.android.server.storage.DeviceStorageMonitorInternal; @@ -404,7 +400,6 @@ import com.android.server.utils.Watchable; import com.android.server.utils.Watched; import com.android.server.utils.WatchedArrayMap; import com.android.server.utils.WatchedSparseBooleanArray; -import com.android.server.utils.WatchedSparseIntArray; import com.android.server.utils.Watcher; import com.android.server.wm.ActivityTaskManagerInternal; @@ -466,7 +461,6 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Predicate; -import java.util.function.Supplier; /** * Keep track of all those APKs everywhere. @@ -5523,19 +5517,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 +5565,7 @@ public class PackageManagerService extends IPackageManager.Stub () -> mInjector.getIncrementalManager(), () -> mPmInternal); ApkChecksums.getChecksums(filesToChecksum, optional, required, installerPackageName, - trustedCerts, statusReceiver, injector); + trustedCerts, onChecksumsReadyListener, injector); }); } @@ -8948,6 +8942,7 @@ public class PackageManagerService extends IPackageManager.Stub @Override public List<String> getAllPackages() { + enforceSystemOrRootOrShell("getAllPackages is limited to privileged callers"); final int callingUid = Binder.getCallingUid(); final int callingUserId = UserHandle.getUserId(callingUid); synchronized (mLock) { @@ -19168,6 +19163,8 @@ public class PackageManagerService extends IPackageManager.Stub extras, 0 /*flags*/, null /*targetPackage*/, null /*finishedReceiver*/, mInstalledUserIds, null /* instantUserIds */, newBroadcastAllowList, null); + // Unregister progress listener + mIncrementalManager.unregisterLoadingProgressCallbacks(codePath); // Unregister health listener as it will always be healthy from now mIncrementalManager.unregisterHealthListener(codePath); } @@ -27045,47 +27042,6 @@ public class PackageManagerService extends IPackageManager.Stub } @Override - public boolean registerInstalledLoadingProgressCallback(String packageName, - PackageManagerInternal.InstalledLoadingProgressCallback callback, int userId) { - final PackageSetting ps = getPackageSettingForUser(packageName, Binder.getCallingUid(), - userId); - if (ps == null) { - return false; - } - if (!ps.isPackageLoading()) { - Slog.w(TAG, - "Failed registering loading progress callback. Package is fully loaded."); - return false; - } - if (mIncrementalManager == null) { - Slog.w(TAG, - "Failed registering loading progress callback. Incremental is not enabled"); - return false; - } - return mIncrementalManager.registerLoadingProgressCallback(ps.getPathString(), - (IPackageLoadingProgressCallback) callback.getBinder()); - } - - @Override - public boolean unregisterInstalledLoadingProgressCallback(String packageName, - PackageManagerInternal.InstalledLoadingProgressCallback callback) { - final PackageSetting ps; - synchronized (mLock) { - ps = mSettings.getPackageLPr(packageName); - if (ps == null) { - Slog.w(TAG, "Failed unregistering loading progress callback. Package " - + packageName + " is not installed"); - return false; - } - } - if (mIncrementalManager == null) { - return false; - } - return mIncrementalManager.unregisterLoadingProgressCallback(ps.getPathString(), - (IPackageLoadingProgressCallback) callback.getBinder()); - } - - @Override public IncrementalStatesInfo getIncrementalStatesInfo( @NonNull String packageName, int filterCallingUid, int userId) { final PackageSetting ps = getPackageSettingForUser(packageName, filterCallingUid, @@ -27110,12 +27066,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/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index 69e84b536004..ca5d2b49b99d 100644 --- a/services/core/java/com/android/server/pm/PackageSetting.java +++ b/services/core/java/com/android/server/pm/PackageSetting.java @@ -344,8 +344,8 @@ public class PackageSetting extends PackageSettingBase { installSource.originatingPackageName); proto.end(sourceToken); } - proto.write(PackageProto.StatesProto.IS_STARTABLE, incrementalStates.isStartable()); - proto.write(PackageProto.StatesProto.IS_LOADING, incrementalStates.isLoading()); + proto.write(PackageProto.StatesProto.IS_STARTABLE, isPackageStartable()); + proto.write(PackageProto.StatesProto.IS_LOADING, isPackageLoading()); writeUsersInfoToProto(proto, PackageProto.USERS); writePackageUserPermissionsProto(proto, PackageProto.USER_PERMISSIONS, users, dataProvider); proto.end(packageToken); diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java index 8aa553d68b98..3a142837e063 100644 --- a/services/core/java/com/android/server/pm/PackageSettingBase.java +++ b/services/core/java/com/android/server/pm/PackageSettingBase.java @@ -26,8 +26,6 @@ import android.annotation.UserIdInt; import android.content.ComponentName; import android.content.pm.ApplicationInfo; import android.content.pm.IncrementalStatesInfo; -import android.content.pm.IntentFilterVerificationInfo; -import android.content.pm.PackageManager; import android.content.pm.PackageManager.UninstallReason; import android.content.pm.PackageParser; import android.content.pm.PackageUserState; @@ -42,8 +40,6 @@ import android.util.SparseArray; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; -import com.android.server.pm.verify.domain.DomainVerificationManagerInternal; -import com.android.server.pm.verify.domain.DomainVerificationService; import com.android.server.pm.parsing.pkg.AndroidPackage; import java.io.File; @@ -731,14 +727,14 @@ public abstract class PackageSettingBase extends SettingBase { * @return True if package is startable, false otherwise. */ public boolean isPackageStartable() { - return incrementalStates.isStartable(); + return getIncrementalStates().isStartable(); } /** * @return True if package is still being loaded, false if the package is fully loaded. */ public boolean isPackageLoading() { - return incrementalStates.isLoading(); + return getIncrementalStates().isLoading(); } /** 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/DisplayFoldController.java b/services/core/java/com/android/server/policy/DisplayFoldController.java index c10e828d8c3d..82fc22c51afc 100644 --- a/services/core/java/com/android/server/policy/DisplayFoldController.java +++ b/services/core/java/com/android/server/policy/DisplayFoldController.java @@ -74,8 +74,8 @@ class DisplayFoldController { mHandler = handler; DeviceStateManager deviceStateManager = context.getSystemService(DeviceStateManager.class); - deviceStateManager.registerDeviceStateListener(new DeviceStateListener(context), - new HandlerExecutor(handler)); + deviceStateManager.addDeviceStateListener(new HandlerExecutor(handler), + new DeviceStateListener(context)); } void finishedGoingToSleep() { diff --git a/services/core/java/com/android/server/policy/IconUtilities.java b/services/core/java/com/android/server/policy/IconUtilities.java deleted file mode 100644 index 884d7d497a8f..000000000000 --- a/services/core/java/com/android/server/policy/IconUtilities.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (C) 2008 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.policy; - -import android.graphics.ColorFilter; -import android.graphics.ColorMatrixColorFilter; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.PaintDrawable; -import android.graphics.drawable.StateListDrawable; -import android.graphics.Bitmap; -import android.graphics.BlurMaskFilter; -import android.graphics.Canvas; -import android.graphics.ColorMatrix; -import android.graphics.Paint; -import android.graphics.PaintFlagsDrawFilter; -import android.graphics.PorterDuff; -import android.graphics.Rect; -import android.graphics.TableMaskFilter; -import android.util.DisplayMetrics; -import android.util.TypedValue; -import android.content.res.Resources; -import android.content.Context; - -/** - * Various utilities shared amongst the Launcher's classes. - */ -public final class IconUtilities { - - private int mIconWidth = -1; - private int mIconHeight = -1; - private int mIconTextureWidth = -1; - private int mIconTextureHeight = -1; - - private final Rect mOldBounds = new Rect(); - private final Canvas mCanvas = new Canvas(); - private final DisplayMetrics mDisplayMetrics; - - private ColorFilter mDisabledColorFilter; - - public IconUtilities(Context context) { - final Resources resources = context.getResources(); - DisplayMetrics metrics = mDisplayMetrics = resources.getDisplayMetrics(); - final float density = metrics.density; - final float blurPx = 5 * density; - - mIconWidth = mIconHeight = (int) resources.getDimension(android.R.dimen.app_icon_size); - mIconTextureWidth = mIconTextureHeight = mIconWidth + (int)(blurPx*2); - mCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG, - Paint.FILTER_BITMAP_FLAG)); - } - - /** - * Returns a bitmap suitable for the all apps view. The bitmap will be a power - * of two sized ARGB_8888 bitmap that can be used as a gl texture. - */ - public Bitmap createIconBitmap(Drawable icon) { - int width = mIconWidth; - int height = mIconHeight; - - if (icon instanceof PaintDrawable) { - PaintDrawable painter = (PaintDrawable) icon; - painter.setIntrinsicWidth(width); - painter.setIntrinsicHeight(height); - } else if (icon instanceof BitmapDrawable) { - // Ensure the bitmap has a density. - BitmapDrawable bitmapDrawable = (BitmapDrawable) icon; - Bitmap bitmap = bitmapDrawable.getBitmap(); - if (bitmap.getDensity() == Bitmap.DENSITY_NONE) { - bitmapDrawable.setTargetDensity(mDisplayMetrics); - } - } - int sourceWidth = icon.getIntrinsicWidth(); - int sourceHeight = icon.getIntrinsicHeight(); - - if (sourceWidth > 0 && sourceHeight > 0) { - // There are intrinsic sizes. - if (width < sourceWidth || height < sourceHeight) { - // It's too big, scale it down. - final float ratio = (float) sourceWidth / sourceHeight; - if (sourceWidth > sourceHeight) { - height = (int) (width / ratio); - } else if (sourceHeight > sourceWidth) { - width = (int) (height * ratio); - } - } else if (sourceWidth < width && sourceHeight < height) { - // It's small, use the size they gave us. - width = sourceWidth; - height = sourceHeight; - } - } - - // no intrinsic size --> use default size - int textureWidth = mIconTextureWidth; - int textureHeight = mIconTextureHeight; - - final Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight, - Bitmap.Config.ARGB_8888); - final Canvas canvas = mCanvas; - canvas.setBitmap(bitmap); - - final int left = (textureWidth-width) / 2; - final int top = (textureHeight-height) / 2; - - mOldBounds.set(icon.getBounds()); - icon.setBounds(left, top, left+width, top+height); - icon.draw(canvas); - icon.setBounds(mOldBounds); - - return bitmap; - } - - public ColorFilter getDisabledColorFilter() { - if (mDisabledColorFilter != null) { - return mDisabledColorFilter; - } - ColorMatrix brightnessMatrix = new ColorMatrix(); - float brightnessF = 0.5f; - int brightnessI = (int) (255 * brightnessF); - // Brightness: C-new = C-old*(1-amount) + amount - float scale = 1f - brightnessF; - float[] mat = brightnessMatrix.getArray(); - mat[0] = scale; - mat[6] = scale; - mat[12] = scale; - mat[4] = brightnessI; - mat[9] = brightnessI; - mat[14] = brightnessI; - - ColorMatrix filterMatrix = new ColorMatrix(); - filterMatrix.setSaturation(0); - filterMatrix.preConcat(brightnessMatrix); - - mDisabledColorFilter = new ColorMatrixColorFilter(filterMatrix); - return mDisabledColorFilter; - } -} 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..f29b57ff9305 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 @@ -1365,7 +1362,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return; } final boolean surfaceReady = w.isDrawn() // Regular case - || w.mWinAnimator.mSurfaceDestroyDeferred // The preserved surface is still ready. || w.isDragResizeChanged(); // Waiting for relayoutWindow to call preserveSurface. final boolean needsLetterbox = surfaceReady && isLetterboxed(w); updateRoundedCorners(w); @@ -1627,6 +1623,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (rotationAnimation >= 0) { mRotationAnimationHint = rotationAnimation; } + + mOverrideTaskTransition = options.getOverrideTaskTransition(); } ColorDisplayService.ColorDisplayServiceInternal cds = LocalServices.getService( @@ -3997,7 +3995,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 +6774,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 +6957,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/AlertWindowNotification.java b/services/core/java/com/android/server/wm/AlertWindowNotification.java index fde036950245..c589feae56ca 100644 --- a/services/core/java/com/android/server/wm/AlertWindowNotification.java +++ b/services/core/java/com/android/server/wm/AlertWindowNotification.java @@ -39,7 +39,7 @@ import android.net.Uri; import android.os.Bundle; import com.android.internal.R; -import com.android.server.policy.IconUtilities; +import com.android.internal.util.ImageUtils; /** Displays an ongoing notification for a process displaying an alert window */ class AlertWindowNotification { @@ -54,7 +54,6 @@ class AlertWindowNotification { private final NotificationManager mNotificationManager; private final String mPackageName; private boolean mPosted; - private IconUtilities mIconUtilities; AlertWindowNotification(WindowManagerService service, String packageName) { mService = service; @@ -63,7 +62,6 @@ class AlertWindowNotification { (NotificationManager) mService.mContext.getSystemService(NOTIFICATION_SERVICE); mNotificationTag = CHANNEL_PREFIX + mPackageName; mRequestCode = sNextRequestCode++; - mIconUtilities = new IconUtilities(mService.mContext); } void post() { @@ -126,8 +124,9 @@ class AlertWindowNotification { if (aInfo != null) { final Drawable drawable = pm.getApplicationIcon(aInfo); - if (drawable != null) { - final Bitmap bitmap = mIconUtilities.createIconBitmap(drawable); + int size = context.getResources().getDimensionPixelSize(android.R.dimen.app_icon_size); + final Bitmap bitmap = ImageUtils.buildScaledBitmap(drawable, size, size); + if (bitmap != null) { builder.setLargeIcon(bitmap); } } 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 10a44987fe7d..9ca7eca07dc6 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/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 61fec0d0ead9..2c97f783e14d 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -847,8 +847,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> Slog.i(TAG, ">>> OPEN TRANSACTION performLayoutAndPlaceSurfaces"); } - // Send any pending task-info changes that were queued-up during a layout deferment - mWmService.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "applySurfaceChanges"); mWmService.openSurfaceTransaction(); try { @@ -865,6 +863,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } } + // Send any pending task-info changes that were queued-up during a layout deferment + mWmService.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); mWmService.mAnimator.executeAfterPrepareSurfacesRunnables(); checkAppTransitionReady(surfacePlacer); @@ -933,7 +933,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; } win.destroySurfaceUnchecked(); - win.mWinAnimator.destroyPreservedSurfaceLocked(win.getSyncTransaction()); } while (i > 0); mWmService.mDestroySurface.clear(); } 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/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java index 2a5e969f9256..91a6664e0b36 100644 --- a/services/core/java/com/android/server/wm/WindowAnimator.java +++ b/services/core/java/com/android/server/wm/WindowAnimator.java @@ -220,8 +220,6 @@ public class WindowAnimator { mRemoveReplacedWindows = false; } - mService.destroyPreservedSurfaceLocked(); - mService.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); executeAfterPrepareSurfacesRunnables(); diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index a33b8becb488..2c92fd035def 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; @@ -189,6 +191,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 { @@ -412,6 +429,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 4ff13cd30398..0894b2660769 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -83,7 +83,6 @@ import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_RELAUNCH; import static android.view.WindowManagerGlobal.ADD_OKAY; import static android.view.WindowManagerGlobal.ADD_TOO_MANY_TOKENS; -import static android.view.WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY; import static android.view.WindowManagerGlobal.RELAYOUT_RES_BLAST_SYNC; import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED; import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID; @@ -405,6 +404,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 +414,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 +421,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"; @@ -626,13 +639,6 @@ public class WindowManagerService extends IWindowManager.Stub final ArrayList<WindowState> mDestroySurface = new ArrayList<>(); /** - * Windows with a preserved surface waiting to be destroyed. These windows - * are going through a surface change. We keep the old surface around until - * the first frame on the new surface finishes drawing. - */ - final ArrayList<WindowState> mDestroyPreservedSurface = new ArrayList<>(); - - /** * This is set when we have run out of memory, and will either be an empty * list or contain windows that need to be force removed. */ @@ -2330,7 +2336,6 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_LAYOUT) Slog.v(TAG_WM, "Relayout " + win + ": viewVisibility=" + viewVisibility + " req=" + requestedWidth + "x" + requestedHeight + " " + win.mAttrs); - winAnimator.mSurfaceDestroyDeferred = (flags & RELAYOUT_DEFER_SURFACE_DESTROY) != 0; if ((attrChanges & WindowManager.LayoutParams.ALPHA_CHANGED) != 0) { winAnimator.mAlpha = attrs.alpha; } @@ -5505,14 +5510,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - void destroyPreservedSurfaceLocked() { - for (int i = mDestroyPreservedSurface.size() - 1; i >= 0 ; i--) { - final WindowState w = mDestroyPreservedSurface.get(i); - w.mWinAnimator.destroyPreservedSurfaceLocked(w.getSyncTransaction()); - } - mDestroyPreservedSurface.clear(); - } - // ------------------------------------------------------------- // IWindowManager API // ------------------------------------------------------------- @@ -7770,6 +7767,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/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 298e31421461..c1f2d02804ea 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -104,7 +104,6 @@ import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType; import static android.view.WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_DOCKED; import static android.view.WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_FREEFORM; import static android.view.WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME; -import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS; @@ -2243,7 +2242,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP disposeInputChannel(); - mWinAnimator.destroyDeferredSurfaceLocked(mTmpTransaction); mWinAnimator.destroySurfaceLocked(mTmpTransaction); mTmpTransaction.apply(); mSession.windowRemovedLocked(); @@ -3309,11 +3307,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return destroyedSomething; } - if (appStopped || mWindowRemovalAllowed) { - mWinAnimator.destroyPreservedSurfaceLocked(mTmpTransaction); - mTmpTransaction.apply(); - } - if (mDestroying) { ProtoLog.e(WM_DEBUG_ADD_REMOVE, "win=%s" + " destroySurfaces: appStopped=%b" @@ -5001,23 +4994,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } - // When we change the Surface size, in scenarios which may require changing - // the surface position in sync with the resize, we use a preserved surface - // so we can freeze it while waiting for the client to report draw on the newly - // sized surface. At the moment this logic is only in place for switching - // in and out of the big surface for split screen resize. if (isDragResizeChanged()) { setDragResizing(); - // We can only change top level windows to the full-screen surface when - // resizing (as we only have one full-screen surface). So there is no need - // to preserve and destroy windows which are attached to another, they - // will keep their surface and its size may change over time. - if (mHasSurface && !isChildWindow()) { - mWinAnimator.preserveSurfaceLocked(getSyncTransaction()); - result |= RELAYOUT_RES_SURFACE_CHANGED | - RELAYOUT_RES_FIRST_TIME; - scheduleAnimation(); - } } final boolean freeformResizing = isDragResizing() && getResizeMode() == DRAG_RESIZE_MODE_FREEFORM; diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 95660ed572d9..2da3dda831e1 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -116,15 +116,7 @@ class WindowStateAnimator { boolean mAnimationIsEntrance; WindowSurfaceController mSurfaceController; - private WindowSurfaceController mPendingDestroySurface; - /** - * Set if the client has asked that the destroy of its surface be delayed - * until it explicitly says it is okay. - */ - boolean mSurfaceDestroyDeferred; - - private boolean mDestroyPreservedSurfaceUponRedraw; float mShownAlpha = 0; float mAlpha = 0; float mLastAlpha = 0; @@ -257,11 +249,6 @@ class WindowStateAnimator { //dump(); mLastHidden = true; - // We may have a preserved surface which we no longer need. If there was a quick - // VISIBLE, GONE, VISIBLE, GONE sequence, the surface may never draw, so we don't mark - // it to be destroyed in prepareSurfaceLocked. - markPreservedSurfaceForDestroy(); - if (mSurfaceController != null) { mSurfaceController.hide(transaction, reason); } @@ -323,70 +310,6 @@ class WindowStateAnimator { return result; } - void preserveSurfaceLocked(SurfaceControl.Transaction t) { - if (mDestroyPreservedSurfaceUponRedraw) { - // This could happen when switching the surface mode very fast. For example, - // we preserved a surface when dragResizing changed to true. Then before the - // preserved surface is removed, dragResizing changed to false again. - // In this case, we need to leave the preserved surface alone, and destroy - // the actual surface, so that the createSurface call could create a surface - // of the proper size. The preserved surface will still be removed when client - // finishes drawing to the new surface. - mSurfaceDestroyDeferred = false; - - // Make sure to reparent any children of the new surface back to the preserved - // surface before destroying it. - if (mSurfaceController != null && mPendingDestroySurface != null) { - mPostDrawTransaction.reparentChildren( - mSurfaceController.mSurfaceControl, - mPendingDestroySurface.mSurfaceControl).apply(); - } - destroySurfaceLocked(t); - mSurfaceDestroyDeferred = true; - return; - } - ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE SET FREEZE LAYER: %s", mWin); - if (mSurfaceController != null) { - // Our SurfaceControl is always at layer 0 within the parent Surface managed by - // window-state. We want this old Surface to stay on top of the new one - // until we do the swap, so we place it at a positive layer. - t.setLayer(mSurfaceController.mSurfaceControl, PRESERVED_SURFACE_LAYER); - } - mDestroyPreservedSurfaceUponRedraw = true; - mSurfaceDestroyDeferred = true; - destroySurfaceLocked(t); - } - - void destroyPreservedSurfaceLocked(SurfaceControl.Transaction t) { - if (!mDestroyPreservedSurfaceUponRedraw) { - return; - } - - // If we are preserving a surface but we aren't relaunching that means - // we are just doing an in-place switch. In that case any SurfaceFlinger side - // child layers need to be reparented to the new surface to make this - // transparent to the app. - // If the children are detached, we don't want to reparent them to the new surface. - // Instead let the children get removed when the old surface is deleted. - if (mSurfaceController != null && mPendingDestroySurface != null - && !mPendingDestroySurface.mChildrenDetached - && (mWin.mActivityRecord == null || !mWin.mActivityRecord.isRelaunching())) { - mPostDrawTransaction.reparentChildren( - mPendingDestroySurface.mSurfaceControl, - mSurfaceController.mSurfaceControl).apply(); - } - - destroyDeferredSurfaceLocked(t); - mDestroyPreservedSurfaceUponRedraw = false; - } - - private void markPreservedSurfaceForDestroy() { - if (mDestroyPreservedSurfaceUponRedraw - && !mService.mDestroyPreservedSurface.contains(mWin)) { - mService.mDestroyPreservedSurface.add(mWin); - } - } - void resetDrawState() { mDrawState = DRAW_PENDING; @@ -508,39 +431,23 @@ class WindowStateAnimator { return; } - // When destroying a surface we want to make sure child windows are hidden. If we are - // preserving the surface until redraw though we intend to swap it out with another surface - // for resizing. In this case the window always remains visible to the user and the child - // windows should likewise remain visible. - if (!mDestroyPreservedSurfaceUponRedraw) { - mWin.mHidden = true; - } + mWin.mHidden = true; try { - if (DEBUG_VISIBILITY) logWithStack(TAG, "Window " + this + " destroying surface " - + mSurfaceController + ", session " + mSession); - if (mSurfaceDestroyDeferred) { - if (mSurfaceController != null && mPendingDestroySurface != mSurfaceController) { - if (mPendingDestroySurface != null) { - ProtoLog.i(WM_SHOW_SURFACE_ALLOC, "SURFACE DESTROY PENDING: %s. %s", - mWin, new RuntimeException().fillInStackTrace()); - mPendingDestroySurface.destroy(t); - } - mPendingDestroySurface = mSurfaceController; - } - } else { - ProtoLog.i(WM_SHOW_SURFACE_ALLOC, "SURFACE DESTROY: %s. %s", - mWin, new RuntimeException().fillInStackTrace()); - destroySurface(t); + if (DEBUG_VISIBILITY) { + logWithStack(TAG, "Window " + this + " destroying surface " + + mSurfaceController + ", session " + mSession); } + ProtoLog.i(WM_SHOW_SURFACE_ALLOC, "SURFACE DESTROY: %s. %s", + mWin, new RuntimeException().fillInStackTrace()); + destroySurface(t); // Don't hide wallpaper if we're deferring the surface destroy // because of a surface change. - if (!mDestroyPreservedSurfaceUponRedraw) { - mWallpaperControllerLocked.hideWallpapers(mWin); - } + mWallpaperControllerLocked.hideWallpapers(mWin); } catch (RuntimeException e) { Slog.w(TAG, "Exception thrown when destroying Window " + this - + " surface " + mSurfaceController + " session " + mSession + ": " + e.toString()); + + " surface " + mSurfaceController + " session " + mSession + ": " + + e.toString()); } // Whether the surface was preserved (and copied to mPendingDestroySurface) or not, it @@ -554,27 +461,6 @@ class WindowStateAnimator { mDrawState = NO_SURFACE; } - void destroyDeferredSurfaceLocked(SurfaceControl.Transaction t) { - try { - if (mPendingDestroySurface != null) { - ProtoLog.i(WM_SHOW_SURFACE_ALLOC, "SURFACE DESTROY PENDING: %s. %s", - mWin, new RuntimeException().fillInStackTrace()); - mPendingDestroySurface.destroy(t); - // Don't hide wallpaper if we're destroying a deferred surface - // after a surface mode change. - if (!mDestroyPreservedSurfaceUponRedraw) { - mWallpaperControllerLocked.hideWallpapers(mWin); - } - } - } catch (RuntimeException e) { - Slog.w(TAG, "Exception thrown when destroying Window " - + this + " surface " + mPendingDestroySurface - + " session " + mSession + ": " + e.toString()); - } - mSurfaceDestroyDeferred = false; - mPendingDestroySurface = null; - } - void computeShownFrameLocked() { if (mIsWallpaper && mService.mRoot.mWallpaperActionPending) { return; @@ -744,7 +630,6 @@ class WindowStateAnimator { if (prepared && mDrawState == HAS_DRAWN) { if (mLastHidden) { if (showSurfaceRobustlyLocked(t)) { - markPreservedSurfaceForDestroy(); mAnimator.requestRemovalOfReplacedWindows(w); mLastHidden = false; if (mIsWallpaper) { @@ -905,20 +790,6 @@ class WindowStateAnimator { if (!shown) return false; - // If we had a preserved surface it's no longer needed, and it may be harmful - // if we are transparent. - if (mPendingDestroySurface != null && mDestroyPreservedSurfaceUponRedraw) { - final SurfaceControl pendingSurfaceControl = mPendingDestroySurface.mSurfaceControl; - mPostDrawTransaction.reparent(pendingSurfaceControl, null); - // If the children are detached, we don't want to reparent them to the new surface. - // Instead let the children get removed when the old surface is deleted. - if (!mPendingDestroySurface.mChildrenDetached) { - mPostDrawTransaction.reparentChildren( - mPendingDestroySurface.mSurfaceControl, - mSurfaceController.mSurfaceControl); - } - } - t.merge(mPostDrawTransaction); return true; } @@ -1058,13 +929,6 @@ class WindowStateAnimator { pw.println(); } - if (mPendingDestroySurface != null) { - pw.print(prefix); pw.print("mPendingDestroySurface="); - pw.println(mPendingDestroySurface); - } - if (mSurfaceDestroyDeferred) { - pw.print(" mSurfaceDestroyDeferred="); pw.println(mSurfaceDestroyDeferred); - } if (mShownAlpha != 1 || mAlpha != 1 || mLastAlpha != 1) { pw.print(prefix); pw.print("mShownAlpha="); pw.print(mShownAlpha); pw.print(" mAlpha="); pw.print(mAlpha); diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java index 5f4477df021c..82ba3c188b76 100644 --- a/services/core/java/com/android/server/wm/WindowSurfaceController.java +++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java @@ -55,8 +55,6 @@ class WindowSurfaceController { private boolean mSurfaceShown = false; private float mSurfaceX = 0; private float mSurfaceY = 0; - private int mSurfaceW = 0; - private int mSurfaceH = 0; // Initialize to the identity matrix. private float mLastDsdx = 1; @@ -82,9 +80,6 @@ class WindowSurfaceController { int flags, WindowStateAnimator animator, int windowType) { mAnimator = animator; - mSurfaceW = w; - mSurfaceH = h; - title = name; mService = animator.mService; @@ -104,8 +99,8 @@ class WindowSurfaceController { .setMetadata(METADATA_OWNER_PID, mWindowSession.mPid) .setCallsite("WindowSurfaceController"); - final boolean useBLAST = mService.mUseBLAST && ((win.getAttrs().privateFlags & - WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST) != 0); + final boolean useBLAST = mService.mUseBLAST && ((win.getAttrs().privateFlags + & WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST) != 0); if (useBLAST) { b.setBLASTLayer(); @@ -119,7 +114,6 @@ class WindowSurfaceController { void hide(SurfaceControl.Transaction transaction, String reason) { ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE HIDE ( %s ): %s", reason, title); - mAnimator.destroyPreservedSurfaceLocked(transaction); if (mSurfaceShown) { hideSurface(transaction); } @@ -335,9 +329,7 @@ class WindowSurfaceController { pw.print(" layer="); pw.print(mSurfaceLayer); pw.print(" alpha="); pw.print(mSurfaceAlpha); pw.print(" rect=("); pw.print(mSurfaceX); - pw.print(","); pw.print(mSurfaceY); - pw.print(") "); pw.print(mSurfaceW); - pw.print(" x "); pw.print(mSurfaceH); + pw.print(","); pw.print(mSurfaceY); pw.print(") "); pw.print(" transform=("); pw.print(mLastDsdx); pw.print(", "); pw.print(mLastDtdx); pw.print(", "); pw.print(mLastDsdy); pw.print(", "); pw.print(mLastDtdy); pw.println(")"); diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index cc7e00a43a6e..91be0564a26f 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -134,7 +134,7 @@ cc_defaults { "android.hardware.broadcastradio@1.0", "android.hardware.broadcastradio@1.1", "android.hardware.contexthub@1.0", - "android.hardware.gnss-cpp", + "android.hardware.gnss-V1-cpp", "android.hardware.gnss@1.0", "android.hardware.gnss@1.1", "android.hardware.gnss@2.0", @@ -147,12 +147,12 @@ cc_defaults { "android.hardware.light@2.0", "android.hardware.power@1.0", "android.hardware.power@1.1", - "android.hardware.power-cpp", + "android.hardware.power-V1-cpp", "android.hardware.power.stats@1.0", "android.hardware.power.stats-ndk_platform", "android.hardware.thermal@1.0", "android.hardware.tv.input@1.0", - "android.hardware.vibrator-unstable-cpp", + "android.hardware.vibrator-V2-cpp", "android.hardware.vibrator@1.0", "android.hardware.vibrator@1.1", "android.hardware.vibrator@1.2", @@ -162,7 +162,7 @@ cc_defaults { "android.frameworks.schedulerservice@1.0", "android.frameworks.sensorservice@1.0", "android.frameworks.stats@1.0", - "android.system.suspend.control-cpp", + "android.system.suspend.control-V1-cpp", "android.system.suspend.control.internal-cpp", "android.system.suspend@1.0", "service.incremental", 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..06d737fa7ca8 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -313,6 +313,7 @@ import com.android.server.PersistentDataBlockManagerInternal; import com.android.server.SystemServerInitThreadPool; import com.android.server.SystemService; import com.android.server.devicepolicy.ActiveAdmin.TrustAgentInfo; +import com.android.server.devicepolicy.Owners.OwnerDto; import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.net.NetworkPolicyManagerInternal; import com.android.server.pm.RestrictionsSet; @@ -910,12 +911,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 +934,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)); } } } @@ -1116,6 +1131,22 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mSafetyChecker = new OneTimeSafetyChecker(this, operation, reason); } + // Used by DevicePolicyManagerServiceShellCommand + List<OwnerDto> listAllOwners() { + Preconditions.checkCallAuthorization( + hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS)); + + List<OwnerDto> owners = mOwners.listAllOwners(); + synchronized (getLockObject()) { + for (int i = 0; i < owners.size(); i++) { + OwnerDto owner = owners.get(i); + owner.isAffiliated = isUserAffiliatedWithDeviceLocked(owner.userId); + } + } + + return owners; + } + /** * Unit test will subclass it to inject mocks. */ @@ -1621,7 +1652,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(); @@ -13451,15 +13483,15 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (!mOwners.hasDeviceOwner()) { return false; } - if (userId == mOwners.getDeviceOwnerUserId()) { - // The user that the DO is installed on is always affiliated with the device. - return true; - } if (userId == UserHandle.USER_SYSTEM) { // The system user is always affiliated in a DO device, // even if in headless system user mode. return true; } + if (userId == mOwners.getDeviceOwnerUserId()) { + // The user that the DO is installed on is always affiliated with the device. + return true; + } final ComponentName profileOwner = getProfileOwnerAsUser(userId); if (profileOwner == null) { @@ -16008,19 +16040,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 +16272,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 +16334,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 +16432,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/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java index fc1d83158801..222c987d906f 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java @@ -18,13 +18,17 @@ package com.android.server.devicepolicy; import android.app.admin.DevicePolicyManager; import android.os.ShellCommand; +import com.android.server.devicepolicy.Owners.OwnerDto; + import java.io.PrintWriter; +import java.util.List; import java.util.Objects; final class DevicePolicyManagerServiceShellCommand extends ShellCommand { private static final String CMD_IS_SAFE_OPERATION = "is-operation-safe"; private static final String CMD_SET_SAFE_OPERATION = "set-operation-safe"; + private static final String CMD_LIST_OWNERS = "list-owners"; private final DevicePolicyManagerService mService; @@ -51,6 +55,8 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand { return runIsSafeOperation(pw); case CMD_SET_SAFE_OPERATION: return runSetSafeOperation(pw); + case CMD_LIST_OWNERS: + return runListOwners(pw); default: return onInvalidCommand(pw, cmd); } @@ -76,6 +82,8 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand { pw.printf(" %s <OPERATION_ID> <REASON_ID>\n", CMD_SET_SAFE_OPERATION); pw.printf(" Emulates the result of the next call to check if the given operation is safe" + " \n\n"); + pw.printf(" %s\n", CMD_LIST_OWNERS); + pw.printf(" Lists the device / profile owners per user \n\n"); } private int runIsSafeOperation(PrintWriter pw) { @@ -97,4 +105,36 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand { DevicePolicyManager.unsafeOperationReasonToString(reason)); return 0; } + + private int runListOwners(PrintWriter pw) { + List<OwnerDto> owners = mService.listAllOwners(); + if (owners.isEmpty()) { + pw.println("none"); + return 0; + } + int size = owners.size(); + if (size == 1) { + pw.println("1 owner:"); + } else { + pw.printf("%d owners:\n", size); + } + + for (int i = 0; i < size; i++) { + OwnerDto owner = owners.get(i); + pw.printf("User %2d: admin=%s", owner.userId, owner.admin.flattenToShortString()); + if (owner.isDeviceOwner) { + pw.print(",DeviceOwner"); + } + if (owner.isProfileOwner) { + pw.print(",ProfileOwner"); + } + if (owner.isAffiliated) { + pw.print(",Affiliated"); + } + pw.println(); + } + + return 0; + } + } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java index 809afe01da2d..1e70d59a5fd5 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java @@ -17,6 +17,7 @@ package com.android.server.devicepolicy; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.app.ActivityManagerInternal; import android.app.AppOpsManagerInternal; import android.app.admin.SystemUpdateInfo; @@ -57,6 +58,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.time.LocalDate; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; @@ -433,6 +435,23 @@ class Owners { } } + List<OwnerDto> listAllOwners() { + List<OwnerDto> owners = new ArrayList<>(); + synchronized (mLock) { + if (mDeviceOwner != null) { + owners.add(new OwnerDto(mDeviceOwnerUserId, mDeviceOwner.admin, + /* isDeviceOwner= */ true)); + } + for (int i = 0; i < mProfileOwners.size(); i++) { + int userId = mProfileOwners.keyAt(i); + OwnerInfo info = mProfileOwners.valueAt(i); + owners.add(new OwnerDto(userId, info.admin, /* isDeviceOwner= */ false)); + } + } + return owners; + } + + SystemUpdatePolicy getSystemUpdatePolicy() { synchronized (mLock) { return mSystemUpdatePolicy; @@ -1076,6 +1095,24 @@ class Owners { } } + /** + * Data-transfer object used by {@link DevicePolicyManagerServiceShellCommand}. + */ + static final class OwnerDto { + public final @UserIdInt int userId; + public final ComponentName admin; + public final boolean isDeviceOwner; + public final boolean isProfileOwner; + public boolean isAffiliated; + + private OwnerDto(@UserIdInt int userId, ComponentName admin, boolean isDeviceOwner) { + this.userId = userId; + this.admin = Objects.requireNonNull(admin, "admin must not be null"); + this.isDeviceOwner = isDeviceOwner; + this.isProfileOwner = !isDeviceOwner; + } + } + public void dump(IndentingPrintWriter pw) { boolean needBlank = false; if (mDeviceOwner != null) { diff --git a/services/incremental/Android.bp b/services/incremental/Android.bp index e978ed4000e0..7534c7c40a3d 100644 --- a/services/incremental/Android.bp +++ b/services/incremental/Android.bp @@ -51,9 +51,9 @@ cc_defaults { static_libs: [ "libbase", "libext2_uuid", - "libdataloader_aidl-unstable-cpp", - "libincremental_aidl-unstable-cpp", - "libincremental_manager_aidl-unstable-cpp", + "libdataloader_aidl-cpp", + "libincremental_aidl-cpp", + "libincremental_manager_aidl-cpp", "libprotobuf-cpp-lite", "service.incremental.proto", "libutils", diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 50cb00f1887f..7a4c611c57b0 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -147,6 +147,7 @@ import com.android.server.oemlock.OemLockService; import com.android.server.om.OverlayManagerService; import com.android.server.os.BugreportManagerService; import com.android.server.os.DeviceIdentifiersPolicyService; +import com.android.server.os.NativeTombstoneManagerService; import com.android.server.os.SchedulingPolicyService; import com.android.server.people.PeopleService; import com.android.server.pm.BackgroundDexOptService; @@ -207,12 +208,15 @@ import java.io.File; import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; +import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Timer; +import java.util.TreeSet; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; @@ -481,6 +485,50 @@ public final class SystemServer implements Dumpable { private static native void fdtrackAbort(); + private static final File HEAP_DUMP_PATH = new File("/data/system/heapdump/"); + private static final int MAX_HEAP_DUMPS = 2; + + /** + * Dump system_server's heap. + * + * For privacy reasons, these aren't automatically pulled into bugreports: + * they must be manually pulled by the user. + */ + private static void dumpHprof() { + // hprof dumps are rather large, so ensure we don't fill the disk by generating + // hundreds of these that will live forever. + TreeSet<File> existingTombstones = new TreeSet<>(); + for (File file : HEAP_DUMP_PATH.listFiles()) { + if (!file.isFile()) { + continue; + } + if (!file.getName().startsWith("fdtrack-")) { + continue; + } + existingTombstones.add(file); + } + if (existingTombstones.size() >= MAX_HEAP_DUMPS) { + for (int i = 0; i < MAX_HEAP_DUMPS - 1; ++i) { + // Leave the newest `MAX_HEAP_DUMPS - 1` tombstones in place. + existingTombstones.pollLast(); + } + for (File file : existingTombstones) { + if (!file.delete()) { + Slog.w("System", "Failed to clean up hprof " + file); + } + } + } + + try { + String date = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date()); + String filename = "/data/system/heapdump/fdtrack-" + date + ".hprof"; + Debug.dumpHprofData(filename); + } catch (IOException ex) { + Slog.e("System", "Failed to dump fdtrack hprof"); + ex.printStackTrace(); + } + } + /** * Spawn a thread that monitors for fd leaks. */ @@ -505,6 +553,7 @@ public final class SystemServer implements Dumpable { enabled = true; } else if (maxFd > abortThreshold) { Slog.i("System", "fdtrack abort threshold reached, dumping and aborting"); + dumpHprof(); fdtrackAbort(); } @@ -1217,6 +1266,11 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startService(ROLLBACK_MANAGER_SERVICE_CLASS); t.traceEnd(); + // Tracks native tombstones. + t.traceBegin("StartNativeTombstoneManagerService"); + mSystemServiceManager.startService(NativeTombstoneManagerService.class); + t.traceEnd(); + // Service to capture bugreports. t.traceBegin("StartBugreportManagerService"); mSystemServiceManager.startService(BugreportManagerService.class); diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index 6daa381f526e..7a0cb8e5dead 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -59,9 +59,9 @@ android_test { }, libs: [ - "android.hardware.power-java", + "android.hardware.power-V1-java", "android.hardware.tv.cec-V1.0-java", - "android.hardware.vibrator-java", + "android.hardware.vibrator-V1-java", "android.hidl.manager-V1.0-java", "android.test.mock", "android.test.base", @@ -88,7 +88,7 @@ android_test { "libui", "libunwindstack", "libutils", - "netd_aidl_interface-cpp", + "netd_aidl_interface-V5-cpp", ], dxflags: ["--multi-dex"], 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/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java index 54da6436ad89..1a2266139405 100644 --- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java @@ -19,10 +19,14 @@ package com.android.server.devicestate; import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertThrows; +import android.hardware.devicestate.DeviceStateRequest; import android.hardware.devicestate.IDeviceStateManagerCallback; +import android.os.Binder; +import android.os.IBinder; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; @@ -33,6 +37,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.HashMap; import java.util.Optional; import javax.annotation.Nullable; @@ -61,87 +66,69 @@ public final class DeviceStateManagerServiceTest { } @Test - public void requestStateChange() { + public void baseStateChanged() { assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE); assertEquals(mService.getPendingState(), Optional.empty()); - assertEquals(mService.getRequestedState().get(), DEFAULT_DEVICE_STATE); + assertEquals(mService.getBaseState().get(), DEFAULT_DEVICE_STATE); assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), DEFAULT_DEVICE_STATE.getIdentifier()); - mProvider.notifyRequestState(OTHER_DEVICE_STATE.getIdentifier()); + mProvider.setState(OTHER_DEVICE_STATE.getIdentifier()); assertEquals(mService.getCommittedState(), OTHER_DEVICE_STATE); assertEquals(mService.getPendingState(), Optional.empty()); - assertEquals(mService.getRequestedState().get(), OTHER_DEVICE_STATE); + assertEquals(mService.getBaseState().get(), OTHER_DEVICE_STATE); assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), OTHER_DEVICE_STATE.getIdentifier()); } @Test - public void requestStateChange_pendingState() { + public void baseStateChanged_withStatePendingPolicyCallback() { mPolicy.blockConfigure(); - mProvider.notifyRequestState(OTHER_DEVICE_STATE.getIdentifier()); + mProvider.setState(OTHER_DEVICE_STATE.getIdentifier()); assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE); assertEquals(mService.getPendingState().get(), OTHER_DEVICE_STATE); - assertEquals(mService.getRequestedState().get(), OTHER_DEVICE_STATE); + assertEquals(mService.getBaseState().get(), OTHER_DEVICE_STATE); assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), OTHER_DEVICE_STATE.getIdentifier()); - mProvider.notifyRequestState(DEFAULT_DEVICE_STATE.getIdentifier()); + mProvider.setState(DEFAULT_DEVICE_STATE.getIdentifier()); assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE); assertEquals(mService.getPendingState().get(), OTHER_DEVICE_STATE); - assertEquals(mService.getRequestedState().get(), DEFAULT_DEVICE_STATE); + assertEquals(mService.getBaseState().get(), DEFAULT_DEVICE_STATE); assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), OTHER_DEVICE_STATE.getIdentifier()); mPolicy.resumeConfigure(); assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE); assertEquals(mService.getPendingState(), Optional.empty()); - assertEquals(mService.getRequestedState().get(), DEFAULT_DEVICE_STATE); + assertEquals(mService.getBaseState().get(), DEFAULT_DEVICE_STATE); assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), DEFAULT_DEVICE_STATE.getIdentifier()); } @Test - public void requestStateChange_unsupportedState() { - mProvider.notifyRequestState(UNSUPPORTED_DEVICE_STATE.getIdentifier()); + public void baseStateChanged_unsupportedState() { + assertThrows(IllegalArgumentException.class, () -> { + mProvider.setState(UNSUPPORTED_DEVICE_STATE.getIdentifier()); + }); + assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE); assertEquals(mService.getPendingState(), Optional.empty()); - assertEquals(mService.getRequestedState().get(), DEFAULT_DEVICE_STATE); + assertEquals(mService.getBaseState().get(), DEFAULT_DEVICE_STATE); assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), DEFAULT_DEVICE_STATE.getIdentifier()); } @Test - public void requestStateChange_invalidState() { + public void baseStateChanged_invalidState() { assertThrows(IllegalArgumentException.class, () -> { - mProvider.notifyRequestState(INVALID_DEVICE_STATE); + mProvider.setState(INVALID_DEVICE_STATE); }); - } - - @Test - public void requestOverrideState() { - mService.setOverrideState(OTHER_DEVICE_STATE.getIdentifier()); - // Committed state changes as there is a requested override. - assertEquals(mService.getCommittedState(), OTHER_DEVICE_STATE); - assertEquals(mService.getRequestedState().get(), DEFAULT_DEVICE_STATE); - assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), - OTHER_DEVICE_STATE.getIdentifier()); - - // Committed state is set back to the requested state once the override is cleared. - mService.clearOverrideState(); - assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE); - assertEquals(mService.getRequestedState().get(), DEFAULT_DEVICE_STATE); - assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), - DEFAULT_DEVICE_STATE.getIdentifier()); - } - @Test - public void requestOverrideState_unsupportedState() { - mService.setOverrideState(UNSUPPORTED_DEVICE_STATE.getIdentifier()); - // Committed state remains the same as the override state is unsupported. assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE); - assertEquals(mService.getRequestedState().get(), DEFAULT_DEVICE_STATE); + assertEquals(mService.getPendingState(), Optional.empty()); + assertEquals(mService.getBaseState().get(), DEFAULT_DEVICE_STATE); assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), DEFAULT_DEVICE_STATE.getIdentifier()); } @@ -150,7 +137,7 @@ public final class DeviceStateManagerServiceTest { public void supportedStatesChanged() { assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE); assertEquals(mService.getPendingState(), Optional.empty()); - assertEquals(mService.getRequestedState().get(), DEFAULT_DEVICE_STATE); + assertEquals(mService.getBaseState().get(), DEFAULT_DEVICE_STATE); mProvider.notifySupportedDeviceStates(new DeviceState[]{ DEFAULT_DEVICE_STATE }); @@ -158,46 +145,27 @@ public final class DeviceStateManagerServiceTest { // supported. assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE); assertEquals(mService.getPendingState(), Optional.empty()); - assertEquals(mService.getRequestedState().get(), DEFAULT_DEVICE_STATE); + assertEquals(mService.getBaseState().get(), DEFAULT_DEVICE_STATE); } @Test - public void supportedStatesChanged_unsupportedRequestedState() { + public void supportedStatesChanged_unsupportedBaseState() { assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE); assertEquals(mService.getPendingState(), Optional.empty()); - assertEquals(mService.getRequestedState().get(), DEFAULT_DEVICE_STATE); + assertEquals(mService.getBaseState().get(), DEFAULT_DEVICE_STATE); mProvider.notifySupportedDeviceStates(new DeviceState[]{ OTHER_DEVICE_STATE }); // The current requested state is cleared because it is no longer supported. assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE); assertEquals(mService.getPendingState(), Optional.empty()); - assertEquals(mService.getRequestedState(), Optional.empty()); + assertEquals(mService.getBaseState(), Optional.empty()); - mProvider.notifyRequestState(OTHER_DEVICE_STATE.getIdentifier()); + mProvider.setState(OTHER_DEVICE_STATE.getIdentifier()); assertEquals(mService.getCommittedState(), OTHER_DEVICE_STATE); assertEquals(mService.getPendingState(), Optional.empty()); - assertEquals(mService.getRequestedState().get(), OTHER_DEVICE_STATE); - } - - @Test - public void supportedStatesChanged_unsupportedOverrideState() { - mService.setOverrideState(OTHER_DEVICE_STATE.getIdentifier()); - // Committed state changes as there is a requested override. - assertEquals(mService.getCommittedState(), OTHER_DEVICE_STATE); - assertEquals(mService.getRequestedState().get(), DEFAULT_DEVICE_STATE); - assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), - OTHER_DEVICE_STATE.getIdentifier()); - - mProvider.notifySupportedDeviceStates(new DeviceState[]{ DEFAULT_DEVICE_STATE }); - - // Committed state is set back to the requested state as the override state is no longer - // supported. - assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE); - assertEquals(mService.getRequestedState().get(), DEFAULT_DEVICE_STATE); - assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), - DEFAULT_DEVICE_STATE.getIdentifier()); + assertEquals(mService.getBaseState().get(), OTHER_DEVICE_STATE); } @Test @@ -205,17 +173,17 @@ public final class DeviceStateManagerServiceTest { TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback(); mService.getBinderService().registerCallback(callback); - mProvider.notifyRequestState(OTHER_DEVICE_STATE.getIdentifier()); + mProvider.setState(OTHER_DEVICE_STATE.getIdentifier()); assertNotNull(callback.getLastNotifiedValue()); assertEquals(callback.getLastNotifiedValue().intValue(), OTHER_DEVICE_STATE.getIdentifier()); - mProvider.notifyRequestState(DEFAULT_DEVICE_STATE.getIdentifier()); + mProvider.setState(DEFAULT_DEVICE_STATE.getIdentifier()); assertEquals(callback.getLastNotifiedValue().intValue(), DEFAULT_DEVICE_STATE.getIdentifier()); mPolicy.blockConfigure(); - mProvider.notifyRequestState(OTHER_DEVICE_STATE.getIdentifier()); + mProvider.setState(OTHER_DEVICE_STATE.getIdentifier()); // The callback should not have been notified of the state change as the policy is still // pending callback. assertEquals(callback.getLastNotifiedValue().intValue(), @@ -237,6 +205,148 @@ public final class DeviceStateManagerServiceTest { DEFAULT_DEVICE_STATE.getIdentifier()); } + @Test + public void getSupportedDeviceStates() throws RemoteException { + final int[] expectedStates = new int[] { 0, 1 }; + assertEquals(mService.getBinderService().getSupportedDeviceStates(), expectedStates); + } + + @Test + public void requestState() throws RemoteException { + TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback(); + mService.getBinderService().registerCallback(callback); + + final IBinder token = new Binder(); + assertEquals(callback.getLastNotifiedStatus(token), + TestDeviceStateManagerCallback.STATUS_UNKNOWN); + + mService.getBinderService().requestState(token, OTHER_DEVICE_STATE.getIdentifier(), + 0 /* flags */); + + assertEquals(callback.getLastNotifiedStatus(token), + TestDeviceStateManagerCallback.STATUS_ACTIVE); + // Committed state changes as there is a requested override. + assertEquals(mService.getCommittedState(), OTHER_DEVICE_STATE); + assertEquals(mService.getBaseState().get(), DEFAULT_DEVICE_STATE); + assertEquals(mService.getOverrideState().get(), OTHER_DEVICE_STATE); + assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), + OTHER_DEVICE_STATE.getIdentifier()); + + + mService.getBinderService().cancelRequest(token); + + assertEquals(callback.getLastNotifiedStatus(token), + TestDeviceStateManagerCallback.STATUS_CANCELED); + // Committed state is set back to the requested state once the override is cleared. + assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE); + assertEquals(mService.getBaseState().get(), DEFAULT_DEVICE_STATE); + assertFalse(mService.getOverrideState().isPresent()); + assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), + DEFAULT_DEVICE_STATE.getIdentifier()); + } + + @Test + public void requestState_flagCancelWhenBaseChanges() throws RemoteException { + TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback(); + mService.getBinderService().registerCallback(callback); + + final IBinder token = new Binder(); + assertEquals(callback.getLastNotifiedStatus(token), + TestDeviceStateManagerCallback.STATUS_UNKNOWN); + + mService.getBinderService().requestState(token, OTHER_DEVICE_STATE.getIdentifier(), + DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES); + + assertEquals(callback.getLastNotifiedStatus(token), + TestDeviceStateManagerCallback.STATUS_ACTIVE); + + // Committed state changes as there is a requested override. + assertEquals(mService.getCommittedState(), OTHER_DEVICE_STATE); + assertEquals(mService.getBaseState().get(), DEFAULT_DEVICE_STATE); + assertEquals(mService.getOverrideState().get(), OTHER_DEVICE_STATE); + assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), + OTHER_DEVICE_STATE.getIdentifier()); + + mProvider.setState(OTHER_DEVICE_STATE.getIdentifier()); + + // Request is canceled because the base state changed. + assertEquals(callback.getLastNotifiedStatus(token), + TestDeviceStateManagerCallback.STATUS_CANCELED); + // Committed state is set back to the requested state once the override is cleared. + assertEquals(mService.getCommittedState(), OTHER_DEVICE_STATE); + assertEquals(mService.getBaseState().get(), OTHER_DEVICE_STATE); + assertFalse(mService.getOverrideState().isPresent()); + assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), + OTHER_DEVICE_STATE.getIdentifier()); + } + + @Test + public void requestState_becomesUnsupported() throws RemoteException { + TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback(); + mService.getBinderService().registerCallback(callback); + + final IBinder token = new Binder(); + assertEquals(callback.getLastNotifiedStatus(token), + TestDeviceStateManagerCallback.STATUS_UNKNOWN); + + mService.getBinderService().requestState(token, OTHER_DEVICE_STATE.getIdentifier(), + 0 /* flags */); + + assertEquals(callback.getLastNotifiedStatus(token), + TestDeviceStateManagerCallback.STATUS_ACTIVE); + // Committed state changes as there is a requested override. + assertEquals(mService.getCommittedState(), OTHER_DEVICE_STATE); + assertEquals(mService.getBaseState().get(), DEFAULT_DEVICE_STATE); + assertEquals(mService.getOverrideState().get(), OTHER_DEVICE_STATE); + assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), + OTHER_DEVICE_STATE.getIdentifier()); + + mProvider.notifySupportedDeviceStates(new DeviceState[]{ DEFAULT_DEVICE_STATE }); + + // Request is canceled because the state is no longer supported. + assertEquals(callback.getLastNotifiedStatus(token), + TestDeviceStateManagerCallback.STATUS_CANCELED); + // Committed state is set back to the requested state as the override state is no longer + // supported. + assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE); + assertEquals(mService.getBaseState().get(), DEFAULT_DEVICE_STATE); + assertFalse(mService.getOverrideState().isPresent()); + assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), + DEFAULT_DEVICE_STATE.getIdentifier()); + } + + @Test + public void requestState_unsupportedState() throws RemoteException { + TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback(); + mService.getBinderService().registerCallback(callback); + + assertThrows(IllegalArgumentException.class, () -> { + final IBinder token = new Binder(); + mService.getBinderService().requestState(token, + UNSUPPORTED_DEVICE_STATE.getIdentifier(), 0 /* flags */); + }); + } + + @Test + public void requestState_invalidState() throws RemoteException { + TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback(); + mService.getBinderService().registerCallback(callback); + + assertThrows(IllegalArgumentException.class, () -> { + final IBinder token = new Binder(); + mService.getBinderService().requestState(token, INVALID_DEVICE_STATE, 0 /* flags */); + }); + } + + @Test + public void requestState_beforeRegisteringCallback() { + assertThrows(IllegalStateException.class, () -> { + final IBinder token = new Binder(); + mService.getBinderService().requestState(token, DEFAULT_DEVICE_STATE.getIdentifier(), + 0 /* flags */); + }); + } + private static final class TestDeviceStatePolicy implements DeviceStatePolicy { private final DeviceStateProvider mProvider; private int mLastDeviceStateRequestedToConfigure = INVALID_DEVICE_STATE; @@ -306,23 +416,48 @@ public final class DeviceStateManagerServiceTest { mListener.onSupportedDeviceStatesChanged(supportedDeviceStates); } - public void notifyRequestState(int identifier) { + public void setState(int identifier) { mListener.onStateChanged(identifier); } } private static final class TestDeviceStateManagerCallback extends IDeviceStateManagerCallback.Stub { - Integer mLastNotifiedValue; + public static final int STATUS_UNKNOWN = 0; + public static final int STATUS_ACTIVE = 1; + public static final int STATUS_SUSPENDED = 2; + public static final int STATUS_CANCELED = 3; + + private Integer mLastNotifiedValue; + private final HashMap<IBinder, Integer> mLastNotifiedStatus = new HashMap<>(); @Override public void onDeviceStateChanged(int deviceState) { mLastNotifiedValue = deviceState; } + @Override + public void onRequestActive(IBinder token) { + mLastNotifiedStatus.put(token, STATUS_ACTIVE); + } + + @Override + public void onRequestSuspended(IBinder token) { + mLastNotifiedStatus.put(token, STATUS_SUSPENDED); + } + + @Override + public void onRequestCanceled(IBinder token) { + mLastNotifiedStatus.put(token, STATUS_CANCELED); + } + @Nullable Integer getLastNotifiedValue() { return mLastNotifiedValue; } + + int getLastNotifiedStatus(IBinder requestToken) { + return mLastNotifiedStatus.getOrDefault(requestToken, STATUS_UNKNOWN); + } } } 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/IncrementalStatesTest.java b/services/tests/servicestests/src/com/android/server/pm/IncrementalStatesTest.java index 9ba096766be2..7b9a00d582be 100644 --- a/services/tests/servicestests/src/com/android/server/pm/IncrementalStatesTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/IncrementalStatesTest.java @@ -71,12 +71,12 @@ public class IncrementalStatesTest { @Before public void setUp() { mIncrementalStates = new IncrementalStates(); - assertFalse(mIncrementalStates.isStartable()); + assertFalse(mIncrementalStates.getIncrementalStatesInfo().isStartable()); mIncrementalStates.setCallback(mCallback); mIncrementalStates.onCommit(true); // Test that package is now startable and loading - assertTrue(mIncrementalStates.isStartable()); - assertTrue(mIncrementalStates.isLoading()); + assertTrue(mIncrementalStates.getIncrementalStatesInfo().isStartable()); + assertTrue(mIncrementalStates.getIncrementalStatesInfo().isLoading()); mUnstartableCalled.close(); mFullyLoadedCalled.close(); } @@ -90,7 +90,7 @@ public class IncrementalStatesTest { IStorageHealthListener.HEALTH_STATUS_UNHEALTHY); // Test that package is still startable assertFalse(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS)); - assertTrue(mIncrementalStates.isStartable()); + assertTrue(mIncrementalStates.getIncrementalStatesInfo().isStartable()); assertEquals(PackageManager.UNSTARTABLE_REASON_UNKNOWN, mUnstartableReason.get()); } @@ -104,7 +104,7 @@ public class IncrementalStatesTest { IStorageHealthListener.HEALTH_STATUS_READS_PENDING); // Test that package is still startable assertFalse(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS)); - assertTrue(mIncrementalStates.isStartable()); + assertTrue(mIncrementalStates.getIncrementalStatesInfo().isStartable()); } /** @@ -116,7 +116,7 @@ public class IncrementalStatesTest { IStorageHealthListener.HEALTH_STATUS_UNHEALTHY_STORAGE); // Test that package is still startable assertFalse(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS)); - assertTrue(mIncrementalStates.isStartable()); + assertTrue(mIncrementalStates.getIncrementalStatesInfo().isStartable()); assertEquals(PackageManager.UNSTARTABLE_REASON_UNKNOWN, mUnstartableReason.get()); } @@ -130,7 +130,7 @@ public class IncrementalStatesTest { IStorageHealthListener.HEALTH_STATUS_UNHEALTHY_TRANSPORT); // Test that package is still startable assertFalse(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS)); - assertTrue(mIncrementalStates.isStartable()); + assertTrue(mIncrementalStates.getIncrementalStatesInfo().isStartable()); assertEquals(PackageManager.UNSTARTABLE_REASON_UNKNOWN, mUnstartableReason.get()); } @@ -145,12 +145,12 @@ public class IncrementalStatesTest { mIncrementalStates.setProgress(1.0f); // Test that package is now fully loaded assertTrue(mFullyLoadedCalled.block(WAIT_TIMEOUT_MILLIS)); - assertFalse(mIncrementalStates.isLoading()); + assertFalse(mIncrementalStates.getIncrementalStatesInfo().isLoading()); mIncrementalStates.onStorageHealthStatusChanged( IStorageHealthListener.HEALTH_STATUS_UNHEALTHY); // Test that package is still startable assertFalse(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS)); - assertTrue(mIncrementalStates.isStartable()); + assertTrue(mIncrementalStates.getIncrementalStatesInfo().isStartable()); } /** @@ -159,6 +159,6 @@ public class IncrementalStatesTest { @Test public void testStartableTransition_AppCrashOrAnr() { mIncrementalStates.onCrashOrAnr(); - assertTrue(mIncrementalStates.isStartable()); + assertTrue(mIncrementalStates.getIncrementalStatesInfo().isStartable()); } } 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/Android.bp b/services/tests/uiservicestests/Android.bp index e5646db7731f..1dd42127ec06 100644 --- a/services/tests/uiservicestests/Android.bp +++ b/services/tests/uiservicestests/Android.bp @@ -60,6 +60,6 @@ android_test { "libui", "libunwindstack", "libutils", - "netd_aidl_interface-cpp", + "netd_aidl_interface-V5-cpp", ], } diff --git a/services/tests/wmtests/Android.bp b/services/tests/wmtests/Android.bp index cf977b4a18db..ddf2844012e0 100644 --- a/services/tests/wmtests/Android.bp +++ b/services/tests/wmtests/Android.bp @@ -51,7 +51,7 @@ android_test { ], libs: [ - "android.hardware.power-java", + "android.hardware.power-V1-java", "android.test.mock", "android.test.base", "android.test.runner", 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/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/StubTransaction.java b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java index b8d44f605bca..6c722499da4b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java +++ b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java @@ -152,12 +152,6 @@ public class StubTransaction extends SurfaceControl.Transaction { } @Override - public SurfaceControl.Transaction reparentChildren(SurfaceControl sc, - SurfaceControl newParent) { - return this; - } - - @Override public SurfaceControl.Transaction reparent(SurfaceControl sc, SurfaceControl newParent) { return this; } 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/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java index 9e8f8eaa855c..04dea3f7a2c6 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java @@ -163,8 +163,13 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne mValid = true; mSessionComponentName = new ComponentName(service.getPackageName(), mInfo.getSessionService()); - // TODO : Need to get the hotword detection service from the xml metadata - mHotwordDetectionComponentName = null; + final String hotwordDetectionServiceName = mInfo.getHotwordDetectionService(); + if (hotwordDetectionServiceName != null) { + mHotwordDetectionComponentName = new ComponentName(service.getPackageName(), + hotwordDetectionServiceName); + } else { + mHotwordDetectionComponentName = null; + } mIWindowManager = IWindowManager.Stub.asInterface( ServiceManager.getService(Context.WINDOW_SERVICE)); IntentFilter filter = new IntentFilter(); @@ -388,7 +393,11 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne if (DEBUG) { Slog.d(TAG, "setHotwordDetectionConfigLocked"); } - + if (mHotwordDetectionComponentName == null) { + Slog.e(TAG, "Calling setHotwordDetectionConfigLocked, but hotword detection service" + + " name not found"); + return VoiceInteractionService.HOTWORD_CONFIG_FAILURE; + } if (!isIsolatedProcessLocked(mHotwordDetectionComponentName)) { return VoiceInteractionService.HOTWORD_CONFIG_FAILURE; } 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/tests/net/common/java/android/net/OemNetworkPreferencesTest.java b/tests/net/common/java/android/net/OemNetworkPreferencesTest.java index cade5ba3771f..d232a507454d 100644 --- a/tests/net/common/java/android/net/OemNetworkPreferencesTest.java +++ b/tests/net/common/java/android/net/OemNetworkPreferencesTest.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. @@ -20,22 +20,20 @@ import static com.android.testutils.MiscAsserts.assertThrows; import static com.android.testutils.ParcelUtils.assertParcelSane; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.os.Build; -import android.util.SparseArray; import androidx.test.filters.SmallTest; import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; import com.android.testutils.DevSdkIgnoreRunner; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.ArrayList; -import java.util.List; +import java.util.Map; @IgnoreUpTo(Build.VERSION_CODES.R) @RunWith(DevSdkIgnoreRunner.class) @@ -45,51 +43,51 @@ public class OemNetworkPreferencesTest { private static final int TEST_PREF = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_DEFAULT; private static final String TEST_PACKAGE = "com.google.apps.contacts"; - private final List<String> mPackages = new ArrayList<>(); private final OemNetworkPreferences.Builder mBuilder = new OemNetworkPreferences.Builder(); - @Before - public void beforeEachTestMethod() { - mPackages.add(TEST_PACKAGE); + @Test + public void testBuilderAddNetworkPreferenceRequiresNonNullPackageName() { + assertThrows(NullPointerException.class, + () -> mBuilder.addNetworkPreference(null, TEST_PREF)); } @Test - public void builderAddNetworkPreferenceRequiresNonNullPackages() { + public void testBuilderRemoveNetworkPreferenceRequiresNonNullPackageName() { assertThrows(NullPointerException.class, - () -> mBuilder.addNetworkPreference(TEST_PREF, null)); + () -> mBuilder.removeNetworkPreference(null)); } @Test - public void getNetworkPreferencesReturnsCorrectValue() { + public void testGetNetworkPreferenceReturnsCorrectValue() { final int expectedNumberOfMappings = 1; - mBuilder.addNetworkPreference(TEST_PREF, mPackages); + mBuilder.addNetworkPreference(TEST_PACKAGE, TEST_PREF); - final SparseArray<List<String>> networkPreferences = + final Map<String, Integer> networkPreferences = mBuilder.build().getNetworkPreferences(); assertEquals(expectedNumberOfMappings, networkPreferences.size()); - assertEquals(mPackages.size(), networkPreferences.get(TEST_PREF).size()); - assertEquals(mPackages.get(0), networkPreferences.get(TEST_PREF).get(0)); + assertTrue(networkPreferences.containsKey(TEST_PACKAGE)); } @Test - public void getNetworkPreferencesReturnsUnmodifiableValue() { + public void testGetNetworkPreferenceReturnsUnmodifiableValue() { final String newPackage = "new.com.google.apps.contacts"; - mBuilder.addNetworkPreference(TEST_PREF, mPackages); + mBuilder.addNetworkPreference(TEST_PACKAGE, TEST_PREF); - final SparseArray<List<String>> networkPreferences = + final Map<String, Integer> networkPreferences = mBuilder.build().getNetworkPreferences(); assertThrows(UnsupportedOperationException.class, - () -> networkPreferences.get(TEST_PREF).set(mPackages.size() - 1, newPackage)); + () -> networkPreferences.put(newPackage, TEST_PREF)); assertThrows(UnsupportedOperationException.class, - () -> networkPreferences.get(TEST_PREF).add(newPackage)); + () -> networkPreferences.remove(TEST_PACKAGE)); + } @Test - public void toStringReturnsCorrectValue() { - mBuilder.addNetworkPreference(TEST_PREF, mPackages); + public void testToStringReturnsCorrectValue() { + mBuilder.addNetworkPreference(TEST_PACKAGE, TEST_PREF); final String networkPreferencesString = mBuilder.build().getNetworkPreferences().toString(); @@ -99,10 +97,56 @@ public class OemNetworkPreferencesTest { @Test public void testOemNetworkPreferencesParcelable() { - mBuilder.addNetworkPreference(TEST_PREF, mPackages); + mBuilder.addNetworkPreference(TEST_PACKAGE, TEST_PREF); final OemNetworkPreferences prefs = mBuilder.build(); assertParcelSane(prefs, 1 /* fieldCount */); } + + @Test + public void testAddNetworkPreferenceOverwritesPriorPreference() { + final int newPref = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID; + mBuilder.addNetworkPreference(TEST_PACKAGE, TEST_PREF); + Map<String, Integer> networkPreferences = + mBuilder.build().getNetworkPreferences(); + + assertTrue(networkPreferences.containsKey(TEST_PACKAGE)); + assertEquals(networkPreferences.get(TEST_PACKAGE).intValue(), TEST_PREF); + + mBuilder.addNetworkPreference(TEST_PACKAGE, newPref); + networkPreferences = mBuilder.build().getNetworkPreferences(); + + assertTrue(networkPreferences.containsKey(TEST_PACKAGE)); + assertEquals(networkPreferences.get(TEST_PACKAGE).intValue(), newPref); + } + + @Test + public void testRemoveNetworkPreferenceRemovesValue() { + mBuilder.addNetworkPreference(TEST_PACKAGE, TEST_PREF); + Map<String, Integer> networkPreferences = + mBuilder.build().getNetworkPreferences(); + + assertTrue(networkPreferences.containsKey(TEST_PACKAGE)); + + mBuilder.removeNetworkPreference(TEST_PACKAGE); + networkPreferences = mBuilder.build().getNetworkPreferences(); + + assertFalse(networkPreferences.containsKey(TEST_PACKAGE)); + } + + @Test + public void testConstructorByOemNetworkPreferencesSetsValue() { + mBuilder.addNetworkPreference(TEST_PACKAGE, TEST_PREF); + OemNetworkPreferences networkPreference = mBuilder.build(); + + final Map<String, Integer> networkPreferences = + new OemNetworkPreferences + .Builder(networkPreference) + .build() + .getNetworkPreferences(); + + assertTrue(networkPreferences.containsKey(TEST_PACKAGE)); + assertEquals(networkPreferences.get(TEST_PACKAGE).intValue(), TEST_PREF); + } } 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(); + } } |