diff options
108 files changed, 3765 insertions, 1618 deletions
diff --git a/StubLibraries.bp b/StubLibraries.bp index bff222e7d42f..c61f7c684efd 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -437,10 +437,6 @@ droidstubs { name: "hwbinder-stubs-docs", srcs: [ "core/java/android/os/HidlSupport.java", - "core/java/android/annotation/IntDef.java", - "core/java/android/annotation/IntRange.java", - "core/java/android/annotation/NonNull.java", - "core/java/android/annotation/SystemApi.java", "core/java/android/os/HidlMemory.java", "core/java/android/os/HwBinder.java", "core/java/android/os/HwBlob.java", @@ -466,6 +462,7 @@ droidstubs { java_library_static { name: "hwbinder.stubs", sdk_version: "core_current", + libs: ["stub-annotations"], srcs: [ ":hwbinder-stubs-docs", ], diff --git a/core/api/current.txt b/core/api/current.txt index 2b5196b3b5f7..18c049fe2141 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -8257,13 +8257,13 @@ package android.app.usage { } public class NetworkStatsManager { - method public android.app.usage.NetworkStats queryDetails(int, String, long, long) throws android.os.RemoteException, java.lang.SecurityException; - method public android.app.usage.NetworkStats queryDetailsForUid(int, String, long, long, int) throws java.lang.SecurityException; - method public android.app.usage.NetworkStats queryDetailsForUidTag(int, String, long, long, int, int) throws java.lang.SecurityException; - method public android.app.usage.NetworkStats queryDetailsForUidTagState(int, String, long, long, int, int, int) throws java.lang.SecurityException; - method public android.app.usage.NetworkStats querySummary(int, String, long, long) throws android.os.RemoteException, java.lang.SecurityException; - method public android.app.usage.NetworkStats.Bucket querySummaryForDevice(int, String, long, long) throws android.os.RemoteException, java.lang.SecurityException; - method public android.app.usage.NetworkStats.Bucket querySummaryForUser(int, String, long, long) throws android.os.RemoteException, java.lang.SecurityException; + method @WorkerThread public android.app.usage.NetworkStats queryDetails(int, String, long, long) throws android.os.RemoteException, java.lang.SecurityException; + method @WorkerThread public android.app.usage.NetworkStats queryDetailsForUid(int, String, long, long, int) throws java.lang.SecurityException; + method @WorkerThread public android.app.usage.NetworkStats queryDetailsForUidTag(int, String, long, long, int, int) throws java.lang.SecurityException; + method @WorkerThread public android.app.usage.NetworkStats queryDetailsForUidTagState(int, String, long, long, int, int, int) throws java.lang.SecurityException; + method @WorkerThread public android.app.usage.NetworkStats querySummary(int, String, long, long) throws android.os.RemoteException, java.lang.SecurityException; + method @WorkerThread public android.app.usage.NetworkStats.Bucket querySummaryForDevice(int, String, long, long) throws android.os.RemoteException, java.lang.SecurityException; + method @WorkerThread public android.app.usage.NetworkStats.Bucket querySummaryForUser(int, String, long, long) throws android.os.RemoteException, java.lang.SecurityException; method public void registerUsageCallback(int, String, long, android.app.usage.NetworkStatsManager.UsageCallback); method public void registerUsageCallback(int, String, long, android.app.usage.NetworkStatsManager.UsageCallback, @Nullable android.os.Handler); method public void unregisterUsageCallback(android.app.usage.NetworkStatsManager.UsageCallback); @@ -22318,6 +22318,8 @@ package android.media { field public static final String MIMETYPE_AUDIO_G711_ALAW = "audio/g711-alaw"; field public static final String MIMETYPE_AUDIO_G711_MLAW = "audio/g711-mlaw"; field public static final String MIMETYPE_AUDIO_MPEG = "audio/mpeg"; + field public static final String MIMETYPE_AUDIO_MPEGH_MHA1 = "audio/mha1"; + field public static final String MIMETYPE_AUDIO_MPEGH_MHM1 = "audio/mhm1"; field public static final String MIMETYPE_AUDIO_MSGSM = "audio/gsm"; field public static final String MIMETYPE_AUDIO_OPUS = "audio/opus"; field public static final String MIMETYPE_AUDIO_QCELP = "audio/qcelp"; @@ -40528,6 +40530,7 @@ package android.telephony { field public static final String KEY_RTT_SUPPORTED_FOR_VT_BOOL = "rtt_supported_for_vt_bool"; field public static final String KEY_RTT_SUPPORTED_WHILE_ROAMING_BOOL = "rtt_supported_while_roaming_bool"; field public static final String KEY_RTT_UPGRADE_SUPPORTED_BOOL = "rtt_upgrade_supported_bool"; + field public static final String KEY_RTT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_VT_CALL_BOOL = "rtt_upgrade_supported_for_downgraded_vt_call"; field public static final String KEY_SHOW_4G_FOR_3G_DATA_ICON_BOOL = "show_4g_for_3g_data_icon_bool"; field public static final String KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL = "show_4g_for_lte_data_icon_bool"; field public static final String KEY_SHOW_APN_SETTING_CDMA_BOOL = "show_apn_setting_cdma_bool"; @@ -40573,6 +40576,7 @@ package android.telephony { field public static final String KEY_VOICEMAIL_NOTIFICATION_PERSISTENT_BOOL = "voicemail_notification_persistent_bool"; field public static final String KEY_VOICE_PRIVACY_DISABLE_UI_BOOL = "voice_privacy_disable_ui_bool"; field public static final String KEY_VOLTE_REPLACEMENT_RAT_INT = "volte_replacement_rat_int"; + field public static final String KEY_VT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_RTT_CALL_BOOL = "vt_upgrade_supported_for_downgraded_rtt_call"; field public static final String KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL = "vvm_cellular_data_required_bool"; field public static final String KEY_VVM_CLIENT_PREFIX_STRING = "vvm_client_prefix_string"; field public static final String KEY_VVM_DESTINATION_NUMBER_STRING = "vvm_destination_number_string"; @@ -42181,6 +42185,10 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void onMessageWaitingIndicatorChanged(boolean); } + public static interface TelephonyCallback.PhysicalChannelConfigListener { + method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onPhysicalChannelConfigChanged(@NonNull java.util.List<android.telephony.PhysicalChannelConfig>); + } + public static interface TelephonyCallback.PreciseDataConnectionStateListener { method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onPreciseDataConnectionStateChanged(@NonNull android.telephony.PreciseDataConnectionState); } @@ -52403,6 +52411,17 @@ package android.view.translation { method @Nullable @WorkerThread public android.view.translation.TranslationResponse translate(@NonNull android.view.translation.TranslationRequest); } + public final class UiTranslationManager { + method public void registerUiTranslationStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.view.translation.UiTranslationStateCallback); + method public void unregisterUiTranslationStateCallback(@NonNull android.view.translation.UiTranslationStateCallback); + } + + public interface UiTranslationStateCallback { + method public void onFinished(); + method public void onPaused(); + method public void onStarted(@NonNull String, @NonNull String); + } + public final class ViewTranslationRequest implements android.os.Parcelable { method public int describeContents(); method @NonNull public android.view.autofill.AutofillId getAutofillId(); diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index a140e8ad095e..51b6967609af 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -258,6 +258,10 @@ package android.os { method public default int getStability(); } + public class Process { + field public static final int VPN_UID = 1016; // 0x3f8 + } + public class StatsServiceManager { method @NonNull public android.os.StatsServiceManager.ServiceRegisterer getStatsCompanionServiceRegisterer(); method @NonNull public android.os.StatsServiceManager.ServiceRegisterer getStatsManagerServiceRegisterer(); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 0eb5553e11fa..2a99aaa94d2a 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -2837,21 +2837,29 @@ package android.content.pm.verify.domain { method @NonNull public String getPackageName(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.domain.DomainVerificationInfo> CREATOR; + field public static final int STATE_FIRST_VERIFIER_DEFINED = 1024; // 0x400 + field public static final int STATE_MODIFIABLE_UNVERIFIED = 3; // 0x3 + field public static final int STATE_MODIFIABLE_VERIFIED = 4; // 0x4 + field public static final int STATE_NO_RESPONSE = 0; // 0x0 + field public static final int STATE_SUCCESS = 1; // 0x1 + field public static final int STATE_UNMODIFIABLE = 2; // 0x2 } public final class DomainVerificationManager { method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.DOMAIN_VERIFICATION_AGENT, android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION}) public android.content.pm.verify.domain.DomainVerificationInfo getDomainVerificationInfo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public java.util.List<android.content.pm.verify.domain.DomainOwner> getOwnersForDomain(@NonNull String); - method public static boolean isStateModifiable(int); - method public static boolean isStateVerified(int); method @NonNull @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public java.util.List<java.lang.String> queryValidVerificationPackageNames(); method @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public void setDomainVerificationLinkHandlingAllowed(@NonNull String, boolean) throws android.content.pm.PackageManager.NameNotFoundException; - method @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public void setDomainVerificationStatus(@NonNull java.util.UUID, @NonNull java.util.Set<java.lang.String>, int) throws android.content.pm.PackageManager.NameNotFoundException; - method @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public void setDomainVerificationUserSelection(@NonNull java.util.UUID, @NonNull java.util.Set<java.lang.String>, boolean) throws android.content.pm.PackageManager.NameNotFoundException; + method @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public int setDomainVerificationStatus(@NonNull java.util.UUID, @NonNull java.util.Set<java.lang.String>, int) throws android.content.pm.PackageManager.NameNotFoundException; + method @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public int setDomainVerificationUserSelection(@NonNull java.util.UUID, @NonNull java.util.Set<java.lang.String>, boolean) throws android.content.pm.PackageManager.NameNotFoundException; + field public static final int ERROR_DOMAIN_SET_ID_INVALID = 1; // 0x1 + field public static final int ERROR_DOMAIN_SET_ID_NULL = 2; // 0x2 + field public static final int ERROR_DOMAIN_SET_NULL_OR_EMPTY = 3; // 0x3 + field public static final int ERROR_INVALID_STATE_CODE = 6; // 0x6 + field public static final int ERROR_UNABLE_TO_APPROVE = 5; // 0x5 + field public static final int ERROR_UNKNOWN_DOMAIN = 4; // 0x4 field public static final String EXTRA_VERIFICATION_REQUEST = "android.content.pm.verify.domain.extra.VERIFICATION_REQUEST"; - field public static final int STATE_FIRST_VERIFIER_DEFINED = 1024; // 0x400 - field public static final int STATE_NO_RESPONSE = 0; // 0x0 - field public static final int STATE_SUCCESS = 1; // 0x1 + field public static final int STATUS_OK = 0; // 0x0 } public final class DomainVerificationRequest implements android.os.Parcelable { @@ -11609,10 +11617,6 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void onPhoneCapabilityChanged(@NonNull android.telephony.PhoneCapability); } - public static interface TelephonyCallback.PhysicalChannelConfigListener { - method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onPhysicalChannelConfigChanged(@NonNull java.util.List<android.telephony.PhysicalChannelConfig>); - } - public static interface TelephonyCallback.PreciseCallStateListener { method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onPreciseCallStateChanged(@NonNull android.telephony.PreciseCallState); } diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 60765fe948ff..27b19bcd31a1 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -9148,7 +9148,7 @@ public class AppOpsManager { try { sFullLog = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, FULL_LOG, false); - } catch (SecurityException e) { + } catch (Exception e) { // This should not happen, but it may, in rare cases sFullLog = false; } diff --git a/core/java/android/app/usage/NetworkStatsManager.java b/core/java/android/app/usage/NetworkStatsManager.java index 098d8b6c6058..9f1132b605ef 100644 --- a/core/java/android/app/usage/NetworkStatsManager.java +++ b/core/java/android/app/usage/NetworkStatsManager.java @@ -24,6 +24,7 @@ import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; +import android.annotation.WorkerThread; import android.app.usage.NetworkStats.Bucket; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; @@ -201,6 +202,7 @@ public class NetworkStatsManager { * default network {@link NetworkStats.Bucket#DEFAULT_NETWORK_ALL}, * metered {@link NetworkStats.Bucket#METERED_ALL}, * and roaming {@link NetworkStats.Bucket#ROAMING_ALL}. + * This may take a long time, and apps should avoid calling this on their main thread. * * @param networkType As defined in {@link ConnectivityManager}, e.g. * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI} @@ -219,6 +221,7 @@ public class NetworkStatsManager { * @return Bucket object or null if permissions are insufficient or error happened during * statistics collection. */ + @WorkerThread public Bucket querySummaryForDevice(int networkType, String subscriberId, long startTime, long endTime) throws SecurityException, RemoteException { NetworkTemplate template; @@ -240,6 +243,7 @@ public class NetworkStatsManager { * uid {@link NetworkStats.Bucket#UID_ALL}, tag {@link NetworkStats.Bucket#TAG_NONE}, * metered {@link NetworkStats.Bucket#METERED_ALL}, and roaming * {@link NetworkStats.Bucket#ROAMING_ALL}. + * This may take a long time, and apps should avoid calling this on their main thread. * * @param networkType As defined in {@link ConnectivityManager}, e.g. * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI} @@ -258,6 +262,7 @@ public class NetworkStatsManager { * @return Bucket object or null if permissions are insufficient or error happened during * statistics collection. */ + @WorkerThread public Bucket querySummaryForUser(int networkType, String subscriberId, long startTime, long endTime) throws SecurityException, RemoteException { NetworkTemplate template; @@ -283,6 +288,7 @@ public class NetworkStatsManager { * means buckets' start and end timestamps are going to be the same as the 'startTime' and * 'endTime' parameters. State, uid, metered, and roaming are going to vary, and tag is going to * be the same. + * This may take a long time, and apps should avoid calling this on their main thread. * * @param networkType As defined in {@link ConnectivityManager}, e.g. * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI} @@ -301,6 +307,7 @@ public class NetworkStatsManager { * @return Statistics object or null if permissions are insufficient or error happened during * statistics collection. */ + @WorkerThread public NetworkStats querySummary(int networkType, String subscriberId, long startTime, long endTime) throws SecurityException, RemoteException { NetworkTemplate template; @@ -326,9 +333,11 @@ public class NetworkStatsManager { /** * Query network usage statistics details for a given uid. + * This may take a long time, and apps should avoid calling this on their main thread. * * @see #queryDetailsForUidTagState(int, String, long, long, int, int, int) */ + @WorkerThread public NetworkStats queryDetailsForUid(int networkType, String subscriberId, long startTime, long endTime, int uid) throws SecurityException { return queryDetailsForUidTagState(networkType, subscriberId, startTime, endTime, uid, @@ -344,9 +353,11 @@ public class NetworkStatsManager { /** * Query network usage statistics details for a given uid and tag. + * This may take a long time, and apps should avoid calling this on their main thread. * * @see #queryDetailsForUidTagState(int, String, long, long, int, int, int) */ + @WorkerThread public NetworkStats queryDetailsForUidTag(int networkType, String subscriberId, long startTime, long endTime, int uid, int tag) throws SecurityException { return queryDetailsForUidTagState(networkType, subscriberId, startTime, endTime, uid, @@ -365,6 +376,7 @@ public class NetworkStatsManager { * <p>Only includes buckets that atomically occur in the inclusive time range. Doesn't * interpolate across partial buckets. Since bucket length is in the order of hours, this * method cannot be used to measure data usage on a fine grained time scale. + * This may take a long time, and apps should avoid calling this on their main thread. * * @param networkType As defined in {@link ConnectivityManager}, e.g. * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI} @@ -387,6 +399,7 @@ public class NetworkStatsManager { * @return Statistics object or null if an error happened during statistics collection. * @throws SecurityException if permissions are insufficient to read network statistics. */ + @WorkerThread public NetworkStats queryDetailsForUidTagState(int networkType, String subscriberId, long startTime, long endTime, int uid, int tag, int state) throws SecurityException { NetworkTemplate template; @@ -425,6 +438,7 @@ public class NetworkStatsManager { * <p>Only includes buckets that atomically occur in the inclusive time range. Doesn't * interpolate across partial buckets. Since bucket length is in the order of hours, this * method cannot be used to measure data usage on a fine grained time scale. + * This may take a long time, and apps should avoid calling this on their main thread. * * @param networkType As defined in {@link ConnectivityManager}, e.g. * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI} @@ -443,6 +457,7 @@ public class NetworkStatsManager { * @return Statistics object or null if permissions are insufficient or error happened during * statistics collection. */ + @WorkerThread public NetworkStats queryDetails(int networkType, String subscriberId, long startTime, long endTime) throws SecurityException, RemoteException { NetworkTemplate template; diff --git a/core/java/android/content/pm/ResolveInfo.java b/core/java/android/content/pm/ResolveInfo.java index fe8e4d7b1bb2..6f07dd7a24e8 100644 --- a/core/java/android/content/pm/ResolveInfo.java +++ b/core/java/android/content/pm/ResolveInfo.java @@ -19,6 +19,7 @@ package android.content.pm; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; +import android.content.Intent; import android.content.IntentFilter; import android.graphics.drawable.Drawable; import android.os.Build; @@ -183,6 +184,17 @@ public class ResolveInfo implements Parcelable { @SystemApi public boolean handleAllWebDataURI; + /** + * Whether the resolved {@link IntentFilter} declares {@link Intent#CATEGORY_BROWSABLE} and is + * thus allowed to automatically resolve an {@link Intent} as it's assumed the action is safe + * for the user. + * + * Note that the above doesn't apply when this is the only result is returned in the candidate + * set, as the system will not prompt before opening the result. It only applies when there are + * multiple candidates. + */ + private final boolean mAutoResolutionAllowed; + /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public ComponentInfo getComponentInfo() { @@ -364,8 +376,26 @@ public class ResolveInfo implements Parcelable { && INTENT_FORWARDER_ACTIVITY.equals(activityInfo.targetActivity); } + /** + * @see #mAutoResolutionAllowed + * @hide + */ + public boolean isAutoResolutionAllowed() { + return mAutoResolutionAllowed; + } + public ResolveInfo() { targetUserId = UserHandle.USER_CURRENT; + + // It's safer to assume that an unaware caller that constructs a ResolveInfo doesn't + // accidentally mark a result as auto resolveable. + mAutoResolutionAllowed = false; + } + + /** @hide */ + public ResolveInfo(boolean autoResolutionAllowed) { + targetUserId = UserHandle.USER_CURRENT; + mAutoResolutionAllowed = autoResolutionAllowed; } public ResolveInfo(ResolveInfo orig) { @@ -386,6 +416,7 @@ public class ResolveInfo implements Parcelable { system = orig.system; targetUserId = orig.targetUserId; handleAllWebDataURI = orig.handleAllWebDataURI; + mAutoResolutionAllowed = orig.mAutoResolutionAllowed; isInstantAppAvailable = orig.isInstantAppAvailable; } @@ -450,6 +481,7 @@ public class ResolveInfo implements Parcelable { dest.writeInt(noResourceId ? 1 : 0); dest.writeInt(iconResourceId); dest.writeInt(handleAllWebDataURI ? 1 : 0); + dest.writeInt(mAutoResolutionAllowed ? 1 : 0); dest.writeInt(isInstantAppAvailable ? 1 : 0); } @@ -498,6 +530,7 @@ public class ResolveInfo implements Parcelable { noResourceId = source.readInt() != 0; iconResourceId = source.readInt(); handleAllWebDataURI = source.readInt() != 0; + mAutoResolutionAllowed = source.readInt() != 0; isInstantAppAvailable = source.readInt() != 0; } diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java b/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java index 7c335b1d26dd..62277ef671e3 100644 --- a/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java +++ b/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java @@ -43,9 +43,50 @@ import java.util.UUID; */ @SystemApi @DataClass(genAidl = true, genHiddenConstructor = true, genParcelable = true, genToString = true, - genEqualsHashCode = true) + genEqualsHashCode = true, genHiddenConstDefs = true) public final class DomainVerificationInfo implements Parcelable { + // Implementation note: the following states are OUTPUT only. Any value that is synonymous with + // a value in DomainVerificationState must be the EXACT same integer, so that state + // transformation does not have to occur when sending input into the system, assuming that the + // system only accepts those synonymous values. The public API values declared here are only + // used when exiting the system server to prepare this data object for consumption by the + // verification agent. These constants should only be referenced inside public API classes. + // The server must use DomainVerificationState. + + /** + * No response has been recorded by either the system or any verification agent. + */ + public static final int STATE_NO_RESPONSE = DomainVerificationState.STATE_NO_RESPONSE; + + /** + * The domain has been explicitly verified. + */ + public static final int STATE_SUCCESS = DomainVerificationState.STATE_SUCCESS; + + /** + * Indicates the host cannot be modified by the verification agent. + */ + public static final int STATE_UNMODIFIABLE = 2; + + /** + * Indicates the host can be modified by the verification agent and is not considered verified. + */ + public static final int STATE_MODIFIABLE_UNVERIFIED = 3; + + /** + * Indicates the host can be modified by the verification agent and is considered verified. + */ + public static final int STATE_MODIFIABLE_VERIFIED = 4; + + /** + * The first available custom response code. This and any greater integer, along with {@link + * #STATE_SUCCESS} are the only values settable by the verification agent. All custom values + * will be treated as if the domain is unverified. + */ + public static final int STATE_FIRST_VERIFIER_DEFINED = + DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED; + /** * A domain verification ID for use in later API calls. This represents the snapshot of the * domains for a package on device, and will be invalidated whenever the package changes. @@ -74,16 +115,12 @@ public final class DomainVerificationInfo implements Parcelable { /** * Map of host names to their current state. State is an integer, which defaults to {@link - * DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the domain - * verification agent (the intended consumer of this API), which can be equal to {@link - * DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or greater than {@link - * DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for any unsuccessful response. + * #STATE_NO_RESPONSE}. State can be modified by the domain verification agent (the intended + * consumer of this API), which can be equal to {@link #STATE_SUCCESS} when verified, or equal + * to or greater than {@link #STATE_FIRST_VERIFIER_DEFINED} for any unsuccessful response. * <p> - * Any value non-inclusive between those 2 values are reserved for use by the system. The domain - * verification agent may be able to act on these reserved values, and this ability can be - * queried using {@link DomainVerificationManager#isStateModifiable(int)}. It is expected that - * the agent attempt to verify all domains that it can modify the state of, even if it does not - * understand the meaning of those values. + * Hosts which cannot be edited will be assigned {@link #STATE_UNMODIFIABLE}. It is expected + * that the agent attempt to verify all domains that it can modify the state of. */ @NonNull private final Map<String, Integer> mHostToStateMap; @@ -112,6 +149,39 @@ public final class DomainVerificationInfo implements Parcelable { //@formatter:off + /** @hide */ + @android.annotation.IntDef(prefix = "STATE_", value = { + STATE_NO_RESPONSE, + STATE_SUCCESS, + STATE_UNMODIFIABLE, + STATE_MODIFIABLE_UNVERIFIED, + STATE_MODIFIABLE_VERIFIED, + STATE_FIRST_VERIFIER_DEFINED + }) + @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) + @DataClass.Generated.Member + public @interface State {} + + /** @hide */ + @DataClass.Generated.Member + public static String stateToString(@State int value) { + switch (value) { + case STATE_NO_RESPONSE: + return "STATE_NO_RESPONSE"; + case STATE_SUCCESS: + return "STATE_SUCCESS"; + case STATE_UNMODIFIABLE: + return "STATE_UNMODIFIABLE"; + case STATE_MODIFIABLE_UNVERIFIED: + return "STATE_MODIFIABLE_UNVERIFIED"; + case STATE_MODIFIABLE_VERIFIED: + return "STATE_MODIFIABLE_VERIFIED"; + case STATE_FIRST_VERIFIER_DEFINED: + return "STATE_FIRST_VERIFIER_DEFINED"; + default: return Integer.toHexString(value); + } + } + /** * Creates a new DomainVerificationInfo. * @@ -134,16 +204,12 @@ public final class DomainVerificationInfo implements Parcelable { * The package name that this data corresponds to. * @param hostToStateMap * Map of host names to their current state. State is an integer, which defaults to {@link - * DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the domain - * verification agent (the intended consumer of this API), which can be equal to {@link - * DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or greater than {@link - * DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for any unsuccessful response. + * #STATE_NO_RESPONSE}. State can be modified by the domain verification agent (the intended + * consumer of this API), which can be equal to {@link #STATE_SUCCESS} when verified, or equal + * to or greater than {@link #STATE_FIRST_VERIFIER_DEFINED} for any unsuccessful response. * <p> - * Any value non-inclusive between those 2 values are reserved for use by the system. The domain - * verification agent may be able to act on these reserved values, and this ability can be - * queried using {@link DomainVerificationManager#isStateModifiable(int)}. It is expected that - * the agent attempt to verify all domains that it can modify the state of, even if it does not - * understand the meaning of those values. + * Hosts which cannot be edited will be assigned {@link #STATE_UNMODIFIABLE}. It is expected + * that the agent attempt to verify all domains that it can modify the state of. * @hide */ @DataClass.Generated.Member @@ -195,16 +261,12 @@ public final class DomainVerificationInfo implements Parcelable { /** * Map of host names to their current state. State is an integer, which defaults to {@link - * DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the domain - * verification agent (the intended consumer of this API), which can be equal to {@link - * DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or greater than {@link - * DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for any unsuccessful response. + * #STATE_NO_RESPONSE}. State can be modified by the domain verification agent (the intended + * consumer of this API), which can be equal to {@link #STATE_SUCCESS} when verified, or equal + * to or greater than {@link #STATE_FIRST_VERIFIER_DEFINED} for any unsuccessful response. * <p> - * Any value non-inclusive between those 2 values are reserved for use by the system. The domain - * verification agent may be able to act on these reserved values, and this ability can be - * queried using {@link DomainVerificationManager#isStateModifiable(int)}. It is expected that - * the agent attempt to verify all domains that it can modify the state of, even if it does not - * understand the meaning of those values. + * Hosts which cannot be edited will be assigned {@link #STATE_UNMODIFIABLE}. It is expected + * that the agent attempt to verify all domains that it can modify the state of. */ @DataClass.Generated.Member public @NonNull Map<String,Integer> getHostToStateMap() { @@ -320,10 +382,10 @@ public final class DomainVerificationInfo implements Parcelable { }; @DataClass.Generated( - time = 1614721812023L, + time = 1615317187669L, codegenVersion = "1.0.22", sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java", - inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> mHostToStateMap\nprivate void parcelHostToStateMap(android.os.Parcel,int)\nprivate java.util.Map<java.lang.String,java.lang.Integer> unparcelHostToStateMap(android.os.Parcel)\nclass DomainVerificationInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true)") + inputSignatures = "public static final int STATE_NO_RESPONSE\npublic static final int STATE_SUCCESS\npublic static final int STATE_UNMODIFIABLE\npublic static final int STATE_MODIFIABLE_UNVERIFIED\npublic static final int STATE_MODIFIABLE_VERIFIED\npublic static final int STATE_FIRST_VERIFIER_DEFINED\nprivate final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> mHostToStateMap\nprivate void parcelHostToStateMap(android.os.Parcel,int)\nprivate java.util.Map<java.lang.String,java.lang.Integer> unparcelHostToStateMap(android.os.Parcel)\nclass DomainVerificationInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true, genHiddenConstDefs=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java index f7c81bcffda3..55e19f2727bf 100644 --- a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java +++ b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java @@ -60,154 +60,97 @@ public final class DomainVerificationManager { "android.content.pm.verify.domain.extra.VERIFICATION_REQUEST"; /** - * No response has been recorded by either the system or any verification agent. + * Default return code for when a method has succeeded. * * @hide */ @SystemApi - public static final int STATE_NO_RESPONSE = DomainVerificationState.STATE_NO_RESPONSE; + public static final int STATUS_OK = 0; /** - * The verification agent has explicitly verified the domain at some point. + * The provided domain set ID was invalid, probably due to the package being updated between + * the initial request that provided the ID and the method call that used it. This usually + * means the work being processed by the verification agent is outdated and a new request + * should be scheduled, which should already be in progress as part of the + * {@link Intent#ACTION_DOMAINS_NEED_VERIFICATION} broadcast. * * @hide */ @SystemApi - public static final int STATE_SUCCESS = DomainVerificationState.STATE_SUCCESS; + public static final int ERROR_DOMAIN_SET_ID_INVALID = 1; /** - * The first available custom response code. This and any greater integer, along with {@link - * #STATE_SUCCESS} are the only values settable by the verification agent. All values will be - * treated as if the domain is unverified. + * The provided domain set ID was null. This is a developer error. * * @hide */ @SystemApi - public static final int STATE_FIRST_VERIFIER_DEFINED = - DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED; + public static final int ERROR_DOMAIN_SET_ID_NULL = 2; /** + * The provided set of domains was null or empty. This is a developer error. + * * @hide */ - @NonNull - public static String stateToDebugString(@DomainVerificationState.State int state) { - switch (state) { - case DomainVerificationState.STATE_NO_RESPONSE: - return "none"; - case DomainVerificationState.STATE_SUCCESS: - return "verified"; - case DomainVerificationState.STATE_APPROVED: - return "approved"; - case DomainVerificationState.STATE_DENIED: - return "denied"; - case DomainVerificationState.STATE_MIGRATED: - return "migrated"; - case DomainVerificationState.STATE_RESTORED: - return "restored"; - case DomainVerificationState.STATE_LEGACY_FAILURE: - return "legacy_failure"; - case DomainVerificationState.STATE_SYS_CONFIG: - return "system_configured"; - default: - return String.valueOf(state); - } - } + @SystemApi + public static final int ERROR_DOMAIN_SET_NULL_OR_EMPTY = 3; /** - * Checks if a state considers the corresponding domain to be successfully verified. The domain - * verification agent may use this to determine whether or not to re-verify a domain. + * The provided set of domains contains a domain not declared by the target package. This + * usually means the work being processed by the verification agent is outdated and a new + * request should be scheduled, which should already be in progress as part of the + * {@link Intent#ACTION_DOMAINS_NEED_VERIFICATION} broadcast. * * @hide */ @SystemApi - public static boolean isStateVerified(@DomainVerificationState.State int state) { - switch (state) { - case DomainVerificationState.STATE_SUCCESS: - case DomainVerificationState.STATE_APPROVED: - case DomainVerificationState.STATE_MIGRATED: - case DomainVerificationState.STATE_RESTORED: - case DomainVerificationState.STATE_SYS_CONFIG: - return true; - case DomainVerificationState.STATE_NO_RESPONSE: - case DomainVerificationState.STATE_DENIED: - case DomainVerificationState.STATE_LEGACY_FAILURE: - default: - return false; - } - } + public static final int ERROR_UNKNOWN_DOMAIN = 4; /** - * Checks if a state is modifiable by the domain verification agent. This is useful as the - * platform may add new state codes in newer versions, and older verification agents can use - * this method to determine if a state can be changed without having to be aware of what the new - * state means. + * The system was unable to select the domain for approval. This indicates another application + * has been granted a higher approval, usually through domain verification, and the target + * package is unable to override it. * * @hide */ @SystemApi - public static boolean isStateModifiable(@DomainVerificationState.State int state) { - switch (state) { - case DomainVerificationState.STATE_NO_RESPONSE: - case DomainVerificationState.STATE_SUCCESS: - case DomainVerificationState.STATE_MIGRATED: - case DomainVerificationState.STATE_RESTORED: - case DomainVerificationState.STATE_LEGACY_FAILURE: - return true; - case DomainVerificationState.STATE_APPROVED: - case DomainVerificationState.STATE_DENIED: - case DomainVerificationState.STATE_SYS_CONFIG: - return false; - default: - return state >= DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED; - } - } + public static final int ERROR_UNABLE_TO_APPROVE = 5; /** - * For determine re-verify policy. This is hidden from the domain verification agent so that no - * behavior is made based on the result. + * The provided state code is incorrect. The domain verification agent is only allowed to + * assign {@link DomainVerificationInfo#STATE_SUCCESS} or error codes equal to or greater than + * {@link DomainVerificationInfo#STATE_FIRST_VERIFIER_DEFINED}. * * @hide */ - public static boolean isStateDefault(@DomainVerificationState.State int state) { - switch (state) { - case DomainVerificationState.STATE_NO_RESPONSE: - case DomainVerificationState.STATE_MIGRATED: - case DomainVerificationState.STATE_RESTORED: - return true; - case DomainVerificationState.STATE_SUCCESS: - case DomainVerificationState.STATE_APPROVED: - case DomainVerificationState.STATE_DENIED: - case DomainVerificationState.STATE_LEGACY_FAILURE: - case DomainVerificationState.STATE_SYS_CONFIG: - default: - return false; - } - } + @SystemApi + public static final int ERROR_INVALID_STATE_CODE = 6; /** + * Used to communicate through {@link ServiceSpecificException}. Should not be exposed as API. + * * @hide */ - public static final int ERROR_INVALID_DOMAIN_SET = 1; - /** - * @hide - */ - public static final int ERROR_NAME_NOT_FOUND = 2; + public static final int INTERNAL_ERROR_NAME_NOT_FOUND = 1; /** * @hide */ @IntDef(prefix = {"ERROR_"}, value = { - ERROR_INVALID_DOMAIN_SET, - ERROR_NAME_NOT_FOUND, + ERROR_DOMAIN_SET_ID_INVALID, + ERROR_DOMAIN_SET_ID_NULL, + ERROR_DOMAIN_SET_NULL_OR_EMPTY, + ERROR_UNKNOWN_DOMAIN, + ERROR_UNABLE_TO_APPROVE, + ERROR_INVALID_STATE_CODE }) - private @interface Error { + public @interface Error { } private final Context mContext; private final IDomainVerificationManager mDomainVerificationManager; - /** * System service to access the domain verification APIs. * <p> @@ -289,27 +232,24 @@ public final class DomainVerificationManager { * @param domainSetId See {@link DomainVerificationInfo#getIdentifier()}. * @param domains List of host names to change the state of. * @param state See {@link DomainVerificationInfo#getHostToStateMap()}. - * @throws IllegalArgumentException If the ID is invalidated or the {@param domains} are - * invalid. This usually means the work being processed by the - * verification agent is outdated and a new request should be - * scheduled, if one has not already been done as part of the - * {@link Intent#ACTION_DOMAINS_NEED_VERIFICATION} broadcast. * @throws NameNotFoundException If the ID is known to be good, but the package is * unavailable. This may be because the package is installed on * a volume that is no longer mounted. This error is * unrecoverable until the package is available again, and * should not be re-tried except on a time scheduled basis. + * @return error code or {@link #STATUS_OK} if successful + * * @hide */ @SystemApi @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) - public void setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains, - @DomainVerificationState.State int state) throws NameNotFoundException { + public int setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains, + int state) throws NameNotFoundException { try { - mDomainVerificationManager.setDomainVerificationStatus(domainSetId.toString(), + return mDomainVerificationManager.setDomainVerificationStatus(domainSetId.toString(), new DomainSet(domains), state); } catch (Exception e) { - Exception converted = rethrow(e, domainSetId); + Exception converted = rethrow(e, null); if (converted instanceof NameNotFoundException) { throw (NameNotFoundException) converted; } else if (converted instanceof RuntimeException) { @@ -338,7 +278,7 @@ public final class DomainVerificationManager { mDomainVerificationManager.setDomainVerificationLinkHandlingAllowed(packageName, allowed, mContext.getUserId()); } catch (Exception e) { - Exception converted = rethrow(e, packageName); + Exception converted = rethrow(e, null); if (converted instanceof NameNotFoundException) { throw (NameNotFoundException) converted; } else if (converted instanceof RuntimeException) { @@ -372,24 +312,24 @@ public final class DomainVerificationManager { * @param domainSetId See {@link DomainVerificationInfo#getIdentifier()}. * @param domains The domains to toggle the state of. * @param enabled Whether or not the app should automatically open the domains specified. - * @throws IllegalArgumentException If the ID is invalidated or the {@param domains} are - * invalid. * @throws NameNotFoundException If the ID is known to be good, but the package is * unavailable. This may be because the package is installed on * a volume that is no longer mounted. This error is * unrecoverable until the package is available again, and * should not be re-tried except on a time scheduled basis. + * @return error code or {@link #STATUS_OK} if successful + * * @hide */ @SystemApi @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) - public void setDomainVerificationUserSelection(@NonNull UUID domainSetId, + public int setDomainVerificationUserSelection(@NonNull UUID domainSetId, @NonNull Set<String> domains, boolean enabled) throws NameNotFoundException { try { - mDomainVerificationManager.setDomainVerificationUserSelection(domainSetId.toString(), - new DomainSet(domains), enabled, mContext.getUserId()); + return mDomainVerificationManager.setDomainVerificationUserSelection( + domainSetId.toString(), new DomainSet(domains), enabled, mContext.getUserId()); } catch (Exception e) { - Exception converted = rethrow(e, domainSetId); + Exception converted = rethrow(e, null); if (converted instanceof NameNotFoundException) { throw (NameNotFoundException) converted; } else if (converted instanceof RuntimeException) { @@ -447,123 +387,22 @@ public final class DomainVerificationManager { } } - private Exception rethrow(Exception exception, @Nullable UUID domainSetId) { - return rethrow(exception, domainSetId, null); - } - private Exception rethrow(Exception exception, @Nullable String packageName) { - return rethrow(exception, null, packageName); - } - - private Exception rethrow(Exception exception, @Nullable UUID domainSetId, - @Nullable String packageName) { if (exception instanceof ServiceSpecificException) { - int packedErrorCode = ((ServiceSpecificException) exception).errorCode; + int serviceSpecificErrorCode = ((ServiceSpecificException) exception).errorCode; if (packageName == null) { packageName = exception.getMessage(); } - @Error int managerErrorCode = packedErrorCode & 0xFFFF; - switch (managerErrorCode) { - case ERROR_INVALID_DOMAIN_SET: - int errorSpecificCode = packedErrorCode >> 16; - return new IllegalArgumentException(InvalidDomainSetException.buildMessage( - domainSetId, packageName, errorSpecificCode)); - case ERROR_NAME_NOT_FOUND: - return new NameNotFoundException(packageName); - default: - return exception; + if (serviceSpecificErrorCode == INTERNAL_ERROR_NAME_NOT_FOUND) { + return new NameNotFoundException(packageName); } + + return exception; } else if (exception instanceof RemoteException) { return ((RemoteException) exception).rethrowFromSystemServer(); } else { return exception; } } - - /** - * Thrown if a {@link DomainVerificationInfo#getIdentifier()}} or an associated set of domains - * provided by the caller is no longer valid. This may be recoverable, and the caller should - * re-query the package name associated with the ID using - * {@link #getDomainVerificationInfo(String)} - * in order to check. If that also fails, then the package is no longer known to the device and - * thus all pending work for it should be dropped. - * - * @hide - */ - public static class InvalidDomainSetException extends IllegalArgumentException { - - public static final int REASON_ID_NULL = 1; - public static final int REASON_ID_INVALID = 2; - public static final int REASON_SET_NULL_OR_EMPTY = 3; - public static final int REASON_UNKNOWN_DOMAIN = 4; - public static final int REASON_UNABLE_TO_APPROVE = 5; - - /** - * @hide - */ - @IntDef({ - REASON_ID_NULL, - REASON_ID_INVALID, - REASON_SET_NULL_OR_EMPTY, - REASON_UNKNOWN_DOMAIN, - REASON_UNABLE_TO_APPROVE - }) - public @interface Reason { - } - - public static String buildMessage(@Nullable UUID domainSetId, @Nullable String packageName, - @Reason int reason) { - switch (reason) { - case REASON_ID_NULL: - return "Domain set ID cannot be null"; - case REASON_ID_INVALID: - return "Domain set ID " + domainSetId + " has been invalidated"; - case REASON_SET_NULL_OR_EMPTY: - return "Domain set cannot be null or empty"; - case REASON_UNKNOWN_DOMAIN: - return "Domain set contains value that was not declared by the target package " - + packageName; - case REASON_UNABLE_TO_APPROVE: - return "Domain set contains value that was owned by another package"; - default: - return "Unknown failure"; - } - } - - @Reason - private final int mReason; - - @Nullable - private final UUID mDomainSetId; - - @Nullable - private final String mPackageName; - - /** - * @hide - */ - public InvalidDomainSetException(@Nullable UUID domainSetId, @Nullable String packageName, - @Reason int reason) { - super(buildMessage(domainSetId, packageName, reason)); - mDomainSetId = domainSetId; - mPackageName = packageName; - mReason = reason; - } - - @Nullable - public UUID getDomainSetId() { - return mDomainSetId; - } - - @Nullable - public String getPackageName() { - return mPackageName; - } - - @Reason - public int getReason() { - return mReason; - } - } } diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationState.java b/core/java/android/content/pm/verify/domain/DomainVerificationState.java index 17593ef2aeb1..8e28042bf581 100644 --- a/core/java/android/content/pm/verify/domain/DomainVerificationState.java +++ b/core/java/android/content/pm/verify/domain/DomainVerificationState.java @@ -17,15 +17,13 @@ package android.content.pm.verify.domain; import android.annotation.IntDef; +import android.annotation.NonNull; /** * @hide */ public interface DomainVerificationState { - /** - * @hide - */ @IntDef({ STATE_NO_RESPONSE, STATE_SUCCESS, @@ -42,12 +40,12 @@ public interface DomainVerificationState { // TODO(b/159952358): Document all the places that states need to be updated when one is added /** - * @see DomainVerificationManager#STATE_NO_RESPONSE + * @see DomainVerificationInfo#STATE_NO_RESPONSE */ int STATE_NO_RESPONSE = 0; /** - * @see DomainVerificationManager#STATE_SUCCESS + * @see DomainVerificationInfo#STATE_SUCCESS */ int STATE_SUCCESS = 1; @@ -94,7 +92,132 @@ public interface DomainVerificationState { int STATE_SYS_CONFIG = 7; /** - * @see DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED + * @see DomainVerificationInfo#STATE_FIRST_VERIFIER_DEFINED */ int STATE_FIRST_VERIFIER_DEFINED = 0b10000000000; + + @NonNull + static String stateToDebugString(@DomainVerificationState.State int state) { + switch (state) { + case DomainVerificationState.STATE_NO_RESPONSE: + return "none"; + case DomainVerificationState.STATE_SUCCESS: + return "verified"; + case DomainVerificationState.STATE_APPROVED: + return "approved"; + case DomainVerificationState.STATE_DENIED: + return "denied"; + case DomainVerificationState.STATE_MIGRATED: + return "migrated"; + case DomainVerificationState.STATE_RESTORED: + return "restored"; + case DomainVerificationState.STATE_LEGACY_FAILURE: + return "legacy_failure"; + case DomainVerificationState.STATE_SYS_CONFIG: + return "system_configured"; + default: + return String.valueOf(state); + } + } + + /** + * For determining re-verify policy. This is hidden from the domain verification agent so that + * no behavior is made based on the result. + */ + static boolean isDefault(@State int state) { + switch (state) { + case STATE_NO_RESPONSE: + case STATE_MIGRATED: + case STATE_RESTORED: + return true; + case STATE_SUCCESS: + case STATE_APPROVED: + case STATE_DENIED: + case STATE_LEGACY_FAILURE: + case STATE_SYS_CONFIG: + default: + return false; + } + } + + /** + * Checks if a state considers the corresponding domain to be successfully verified. The domain + * verification agent may use this to determine whether or not to re-verify a domain. + */ + static boolean isVerified(@DomainVerificationState.State int state) { + switch (state) { + case DomainVerificationState.STATE_SUCCESS: + case DomainVerificationState.STATE_APPROVED: + case DomainVerificationState.STATE_MIGRATED: + case DomainVerificationState.STATE_RESTORED: + case DomainVerificationState.STATE_SYS_CONFIG: + return true; + case DomainVerificationState.STATE_NO_RESPONSE: + case DomainVerificationState.STATE_DENIED: + case DomainVerificationState.STATE_LEGACY_FAILURE: + default: + return false; + } + } + + /** + * Checks if a state is modifiable by the domain verification agent. This is useful as the + * platform may add new state codes in newer versions, and older verification agents can use + * this method to determine if a state can be changed without having to be aware of what the new + * state means. + */ + static boolean isModifiable(@DomainVerificationState.State int state) { + switch (state) { + case DomainVerificationState.STATE_NO_RESPONSE: + case DomainVerificationState.STATE_SUCCESS: + case DomainVerificationState.STATE_MIGRATED: + case DomainVerificationState.STATE_RESTORED: + case DomainVerificationState.STATE_LEGACY_FAILURE: + return true; + case DomainVerificationState.STATE_APPROVED: + case DomainVerificationState.STATE_DENIED: + case DomainVerificationState.STATE_SYS_CONFIG: + return false; + default: + return state >= DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED; + } + } + + /** + * Whether the state is migrated when updating a package. Generally this is only for states + * that maintain verification state or were set by an explicit user or developer action. + */ + static boolean shouldMigrate(@State int state) { + switch (state) { + case STATE_SUCCESS: + case STATE_MIGRATED: + case STATE_RESTORED: + case STATE_APPROVED: + case STATE_DENIED: + return true; + case STATE_NO_RESPONSE: + case STATE_LEGACY_FAILURE: + case STATE_SYS_CONFIG: + case STATE_FIRST_VERIFIER_DEFINED: + default: + return false; + } + } + + @DomainVerificationInfo.State + static int convertToInfoState(@State int internalState) { + if (internalState >= STATE_FIRST_VERIFIER_DEFINED) { + return internalState; + } else if (internalState == STATE_NO_RESPONSE) { + return DomainVerificationInfo.STATE_NO_RESPONSE; + } else if (internalState == STATE_SUCCESS) { + return DomainVerificationInfo.STATE_SUCCESS; + } else if (!isModifiable(internalState)) { + return DomainVerificationInfo.STATE_UNMODIFIABLE; + } else if (isVerified(internalState)) { + return DomainVerificationInfo.STATE_MODIFIABLE_VERIFIED; + } else { + return DomainVerificationInfo.STATE_MODIFIABLE_UNVERIFIED; + } + } } diff --git a/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl b/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl index 332b92544581..53205f3ea470 100644 --- a/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl +++ b/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl @@ -40,10 +40,10 @@ interface IDomainVerificationManager { @nullable List<DomainOwner> getOwnersForDomain(String domain, int userId); - void setDomainVerificationStatus(String domainSetId, in DomainSet domains, int state); + int setDomainVerificationStatus(String domainSetId, in DomainSet domains, int state); void setDomainVerificationLinkHandlingAllowed(String packageName, boolean allowed, int userId); - void setDomainVerificationUserSelection(String domainSetId, in DomainSet domains, + int setDomainVerificationUserSelection(String domainSetId, in DomainSet domains, boolean enabled, int userId); } diff --git a/packages/Connectivity/framework/src/android/net/IOnSetOemNetworkPreferenceListener.aidl b/core/java/android/net/IOnCompleteListener.aidl index 7979afc54f90..4bb89f6c89e4 100644 --- a/packages/Connectivity/framework/src/android/net/IOnSetOemNetworkPreferenceListener.aidl +++ b/core/java/android/net/IOnCompleteListener.aidl @@ -18,6 +18,6 @@ package android.net; /** @hide */ -oneway interface IOnSetOemNetworkPreferenceListener { +oneway interface IOnCompleteListener { void onComplete(); } diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index e75e224d9a6f..ab1f688d60ca 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -16,8 +16,11 @@ package android.os; +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; + import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.system.ErrnoException; @@ -108,6 +111,7 @@ public class Process { * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @SystemApi(client = MODULE_LIBRARIES) public static final int VPN_UID = 1016; /** diff --git a/core/java/android/os/RemoteCallbackList.java b/core/java/android/os/RemoteCallbackList.java index df4ade09753b..d89c3d591d46 100644 --- a/core/java/android/os/RemoteCallbackList.java +++ b/core/java/android/os/RemoteCallbackList.java @@ -21,6 +21,7 @@ import android.util.ArrayMap; import android.util.Slog; import java.io.PrintWriter; +import java.util.function.BiConsumer; import java.util.function.Consumer; /** @@ -354,6 +355,23 @@ public class RemoteCallbackList<E extends IInterface> { } /** + * Performs {@code action} on each callback and associated cookie, calling {@link + * #beginBroadcast()}/{@link #finishBroadcast()} before/after looping. + * + * @hide + */ + public <C> void broadcast(BiConsumer<E, C> action) { + int itemCount = beginBroadcast(); + try { + for (int i = 0; i < itemCount; i++) { + action.accept(getBroadcastItem(i), (C) getBroadcastCookie(i)); + } + } finally { + finishBroadcast(); + } + } + + /** * Returns the number of registered callbacks. Note that the number of registered * callbacks may differ from the value returned by {@link #beginBroadcast()} since * the former returns the number of callbacks registered at the time of the call diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java index a0875a275ec6..d0000005c237 100644 --- a/core/java/android/telephony/TelephonyCallback.java +++ b/core/java/android/telephony/TelephonyCallback.java @@ -1353,10 +1353,7 @@ public class TelephonyCallback { /** * Interface for current physical channel configuration listener. - * - * @hide */ - @SystemApi public interface PhysicalChannelConfigListener { /** * Callback invoked when the current physical channel configuration has changed diff --git a/core/java/android/view/translation/ITranslationManager.aidl b/core/java/android/view/translation/ITranslationManager.aidl index 7f6c4b474d3a..d347f31eb934 100644 --- a/core/java/android/view/translation/ITranslationManager.aidl +++ b/core/java/android/view/translation/ITranslationManager.aidl @@ -17,6 +17,7 @@ package android.view.translation; import android.os.IBinder; +import android.os.IRemoteCallback; import android.view.autofill.AutofillId; import android.view.translation.TranslationSpec; import com.android.internal.os.IResultReceiver; @@ -40,4 +41,7 @@ oneway interface ITranslationManager { void updateUiTranslationStateByTaskId(int state, in TranslationSpec sourceSpec, in TranslationSpec destSpec, in List<AutofillId> viewIds, int taskId, int userId); + + void registerUiTranslationStateCallback(in IRemoteCallback callback, int userId); + void unregisterUiTranslationStateCallback(in IRemoteCallback callback, int userId); } diff --git a/core/java/android/view/translation/UiTranslationManager.java b/core/java/android/view/translation/UiTranslationManager.java index 7c73e701b7c8..852ffe8303b1 100644 --- a/core/java/android/view/translation/UiTranslationManager.java +++ b/core/java/android/view/translation/UiTranslationManager.java @@ -16,28 +16,36 @@ package android.view.translation; +import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.app.assist.ActivityId; import android.content.Context; +import android.os.Binder; +import android.os.Bundle; +import android.os.IRemoteCallback; import android.os.RemoteException; +import android.util.ArrayMap; +import android.util.Log; import android.view.View; import android.view.autofill.AutofillId; +import com.android.internal.annotations.GuardedBy; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.concurrent.Executor; +// TODO(b/178044703): Describe what UI Translation is. /** * The {@link UiTranslationManager} class provides ways for apps to use the ui translation * function in framework. - * - * @hide */ -@SystemApi public final class UiTranslationManager { private static final String TAG = "UiTranslationManager"; @@ -88,6 +96,14 @@ public final class UiTranslationManager { public @interface UiTranslationState { } + // Keys for the data transmitted in the internal UI Translation state callback. + /** @hide */ + public static final String EXTRA_STATE = "state"; + /** @hide */ + public static final String EXTRA_SOURCE_LOCALE = "source_locale"; + /** @hide */ + public static final String EXTRA_TARGET_LOCALE = "target_locale"; + @NonNull private final Context mContext; @@ -111,9 +127,12 @@ public final class UiTranslationManager { * @param destSpec {@link TranslationSpec} for the translated data. * @param viewIds A list of the {@link View}'s {@link AutofillId} which needs to be translated * @param taskId the Activity Task id which needs ui translation + * + * @hide */ // TODO, hide the APIs @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) + @SystemApi public void startTranslation(@NonNull TranslationSpec sourceSpec, @NonNull TranslationSpec destSpec, @NonNull List<AutofillId> viewIds, int taskId) { @@ -141,8 +160,11 @@ public final class UiTranslationManager { * @throws IllegalArgumentException if the no {@link View}'s {@link AutofillId} in the list * @throws NullPointerException the sourceSpec, destSpec, viewIds, activityId or * {@link android.app.assist.ActivityId#getToken()} is {@code null} + * + * @hide */ @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) + @SystemApi public void startTranslation(@NonNull TranslationSpec sourceSpec, @NonNull TranslationSpec destSpec, @NonNull List<AutofillId> viewIds, @NonNull ActivityId activityId) { @@ -171,9 +193,12 @@ public final class UiTranslationManager { * NOTE: Please use {@code finishTranslation(ActivityId)} instead. * * @param taskId the Activity Task id which needs ui translation + * + * @hide */ // TODO, hide the APIs @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) + @SystemApi public void finishTranslation(int taskId) { try { mService.updateUiTranslationStateByTaskId(STATE_UI_TRANSLATION_FINISHED, @@ -191,8 +216,11 @@ public final class UiTranslationManager { * @param activityId the identifier for the Activity which needs ui translation * @throws NullPointerException the activityId or * {@link android.app.assist.ActivityId#getToken()} is {@code null} + * + * @hide */ @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) + @SystemApi public void finishTranslation(@NonNull ActivityId activityId) { try { Objects.requireNonNull(activityId); @@ -212,9 +240,12 @@ public final class UiTranslationManager { * NOTE: Please use {@code pauseTranslation(ActivityId)} instead. * * @param taskId the Activity Task id which needs ui translation + * + * @hide */ // TODO, hide the APIs @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) + @SystemApi public void pauseTranslation(int taskId) { try { mService.updateUiTranslationStateByTaskId(STATE_UI_TRANSLATION_PAUSED, @@ -232,8 +263,11 @@ public final class UiTranslationManager { * @param activityId the identifier for the Activity which needs ui translation * @throws NullPointerException the activityId or * {@link android.app.assist.ActivityId#getToken()} is {@code null} + * + * @hide */ @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) + @SystemApi public void pauseTranslation(@NonNull ActivityId activityId) { try { Objects.requireNonNull(activityId); @@ -253,9 +287,12 @@ public final class UiTranslationManager { * NOTE: Please use {@code resumeTranslation(ActivityId)} instead. * * @param taskId the Activity Task id which needs ui translation + * + * @hide */ // TODO, hide the APIs @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) + @SystemApi public void resumeTranslation(int taskId) { try { mService.updateUiTranslationStateByTaskId(STATE_UI_TRANSLATION_RESUMED, @@ -273,8 +310,11 @@ public final class UiTranslationManager { * @param activityId the identifier for the Activity which needs ui translation * @throws NullPointerException the activityId or * {@link android.app.assist.ActivityId#getToken()} is {@code null} + * + * @hide */ @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) + @SystemApi public void resumeTranslation(@NonNull ActivityId activityId) { try { Objects.requireNonNull(activityId); @@ -286,4 +326,105 @@ public final class UiTranslationManager { throw e.rethrowFromSystemServer(); } } + + // TODO(b/178044703): Fix the View API link when it becomes public. + /** + * Register for notifications of UI Translation state changes on the foreground activity. This + * is available to the owning application itself and also the current input method. + * <p> + * The application whose UI is being translated can use this to customize the UI Translation + * behavior in ways that aren't made easy by methods like + * View#onCreateTranslationRequest(). + * <p> + * Input methods can use this to offer complementary features to UI Translation; for example, + * enabling outgoing message translation when the system is translating incoming messages in a + * communication app. + * + * @param callback the callback to register for receiving the state change + * notifications + */ + public void registerUiTranslationStateCallback( + @NonNull @CallbackExecutor Executor executor, + @NonNull UiTranslationStateCallback callback) { + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + synchronized (mCallbacks) { + if (mCallbacks.containsKey(callback)) { + Log.w(TAG, "registerUiTranslationStateCallback: callback already registered;" + + " ignoring."); + return; + } + final IRemoteCallback remoteCallback = + new UiTranslationStateRemoteCallback(executor, callback); + try { + mService.registerUiTranslationStateCallback(remoteCallback, mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + mCallbacks.put(callback, remoteCallback); + } + } + + /** + * Unregister {@code callback}. + * + * @see #registerUiTranslationStateCallback(Executor, UiTranslationStateCallback) + */ + public void unregisterUiTranslationStateCallback(@NonNull UiTranslationStateCallback callback) { + Objects.requireNonNull(callback); + + synchronized (mCallbacks) { + final IRemoteCallback remoteCallback = mCallbacks.get(callback); + if (remoteCallback == null) { + Log.w(TAG, "unregisterUiTranslationStateCallback: callback not found; ignoring."); + return; + } + try { + mService.unregisterUiTranslationStateCallback(remoteCallback, mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + mCallbacks.remove(callback); + } + } + + @NonNull + @GuardedBy("mCallbacks") + private final Map<UiTranslationStateCallback, IRemoteCallback> mCallbacks = new ArrayMap<>(); + + private static class UiTranslationStateRemoteCallback extends IRemoteCallback.Stub { + private final Executor mExecutor; + private final UiTranslationStateCallback mCallback; + + UiTranslationStateRemoteCallback(Executor executor, + UiTranslationStateCallback callback) { + mExecutor = executor; + mCallback = callback; + } + + @Override + public void sendResult(Bundle bundle) { + Binder.withCleanCallingIdentity(() -> mExecutor.execute(() -> onStateChange(bundle))); + } + + private void onStateChange(Bundle bundle) { + int state = bundle.getInt(EXTRA_STATE); + switch (state) { + case STATE_UI_TRANSLATION_STARTED: + case STATE_UI_TRANSLATION_RESUMED: + mCallback.onStarted( + bundle.getString(EXTRA_SOURCE_LOCALE), + bundle.getString(EXTRA_TARGET_LOCALE)); + break; + case STATE_UI_TRANSLATION_PAUSED: + mCallback.onPaused(); + break; + case STATE_UI_TRANSLATION_FINISHED: + mCallback.onFinished(); + break; + default: + Log.wtf(TAG, "Unexpected translation state:" + state); + } + } + } } diff --git a/core/java/android/view/translation/UiTranslationStateCallback.java b/core/java/android/view/translation/UiTranslationStateCallback.java new file mode 100644 index 000000000000..1946b703935d --- /dev/null +++ b/core/java/android/view/translation/UiTranslationStateCallback.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 android.view.translation; + +import android.annotation.NonNull; + +import java.util.concurrent.Executor; + +/** + * Callback for listening to UI Translation state changes. See {@link + * UiTranslationManager#registerUiTranslationStateCallback(Executor, UiTranslationStateCallback)}. + */ +public interface UiTranslationStateCallback { + + /** + * The system is requesting translation of the UI from {@code sourceLocale} to {@code + * targetLocale}. + * <p> + * This is also called if either the requested {@code sourceLocale} or {@code targetLocale} has + * changed; or called again after {@link #onPaused()}. + */ + void onStarted(@NonNull String sourceLocale, @NonNull String targetLocale); + + /** + * The system is requesting that the application temporarily show the UI contents in their + * original language. + */ + void onPaused(); + + /** + * The UI Translation session has ended. + */ + void onFinished(); +} diff --git a/core/java/com/android/internal/util/LocationPermissionChecker.java b/core/java/com/android/internal/util/LocationPermissionChecker.java deleted file mode 100644 index d67bd7a853c8..000000000000 --- a/core/java/com/android/internal/util/LocationPermissionChecker.java +++ /dev/null @@ -1,303 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.util; - -import android.Manifest; -import android.annotation.IntDef; -import android.annotation.Nullable; -import android.app.ActivityManager; -import android.app.AppOpsManager; -import android.content.Context; -import android.content.pm.PackageManager; -import android.location.LocationManager; -import android.net.NetworkStack; -import android.os.Binder; -import android.os.Build; -import android.os.UserHandle; -import android.util.Log; - -import com.android.internal.annotations.VisibleForTesting; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - - -/** - * The methods used for location permission and location mode checking. - * - * @hide - */ -public class LocationPermissionChecker { - - private static final String TAG = "LocationPermissionChecker"; - - @Retention(RetentionPolicy.SOURCE) - @IntDef(prefix = {"LOCATION_PERMISSION_CHECK_STATUS_"}, value = { - SUCCEEDED, - ERROR_LOCATION_MODE_OFF, - ERROR_LOCATION_PERMISSION_MISSING, - }) - public @interface LocationPermissionCheckStatus{} - - // The location permission check succeeded. - public static final int SUCCEEDED = 0; - // The location mode turns off for the caller. - public static final int ERROR_LOCATION_MODE_OFF = 1; - // The location permission isn't granted for the caller. - public static final int ERROR_LOCATION_PERMISSION_MISSING = 2; - - private final Context mContext; - private final AppOpsManager mAppOpsManager; - - public LocationPermissionChecker(Context context) { - mContext = context; - mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); - } - - /** - * Check location permission granted by the caller. - * - * This API check if the location mode enabled for the caller and the caller has - * ACCESS_COARSE_LOCATION permission is targetSDK<29, otherwise, has ACCESS_FINE_LOCATION. - * - * @param pkgName package name of the application requesting access - * @param featureId The feature in the package - * @param uid The uid of the package - * @param message A message describing why the permission was checked. Only needed if this is - * not inside of a two-way binder call from the data receiver - * - * @return {@code true} returns if the caller has location permission and the location mode is - * enabled. - */ - public boolean checkLocationPermission(String pkgName, @Nullable String featureId, - int uid, @Nullable String message) { - return checkLocationPermissionInternal(pkgName, featureId, uid, message) == SUCCEEDED; - } - - /** - * Check location permission granted by the caller. - * - * This API check if the location mode enabled for the caller and the caller has - * ACCESS_COARSE_LOCATION permission is targetSDK<29, otherwise, has ACCESS_FINE_LOCATION. - * Compared with {@link #checkLocationPermission(String, String, int, String)}, this API returns - * the detail information about the checking result, including the reason why it's failed and - * logs the error for the caller. - * - * @param pkgName package name of the application requesting access - * @param featureId The feature in the package - * @param uid The uid of the package - * @param message A message describing why the permission was checked. Only needed if this is - * not inside of a two-way binder call from the data receiver - * - * @return {@link LocationPermissionCheckStatus} the result of the location permission check. - */ - public @LocationPermissionCheckStatus int checkLocationPermissionWithDetailInfo( - String pkgName, @Nullable String featureId, int uid, @Nullable String message) { - final int result = checkLocationPermissionInternal(pkgName, featureId, uid, message); - switch (result) { - case ERROR_LOCATION_MODE_OFF: - Log.e(TAG, "Location mode is disabled for the device"); - break; - case ERROR_LOCATION_PERMISSION_MISSING: - Log.e(TAG, "UID " + uid + " has no location permission"); - break; - } - return result; - } - - /** - * Enforce the caller has location permission. - * - * This API determines if the location mode enabled for the caller and the caller has - * ACCESS_COARSE_LOCATION permission is targetSDK<29, otherwise, has ACCESS_FINE_LOCATION. - * SecurityException is thrown if the caller has no permission or the location mode is disabled. - * - * @param pkgName package name of the application requesting access - * @param featureId The feature in the package - * @param uid The uid of the package - * @param message A message describing why the permission was checked. Only needed if this is - * not inside of a two-way binder call from the data receiver - */ - public void enforceLocationPermission(String pkgName, @Nullable String featureId, int uid, - @Nullable String message) throws SecurityException { - final int result = checkLocationPermissionInternal(pkgName, featureId, uid, message); - - switch (result) { - case ERROR_LOCATION_MODE_OFF: - throw new SecurityException("Location mode is disabled for the device"); - case ERROR_LOCATION_PERMISSION_MISSING: - throw new SecurityException("UID " + uid + " has no location permission"); - } - } - - private int checkLocationPermissionInternal(String pkgName, @Nullable String featureId, - int uid, @Nullable String message) { - checkPackage(uid, pkgName); - - // Apps with NETWORK_SETTINGS, NETWORK_SETUP_WIZARD, NETWORK_STACK & MAINLINE_NETWORK_STACK - // are granted a bypass. - if (checkNetworkSettingsPermission(uid) || checkNetworkSetupWizardPermission(uid) - || checkNetworkStackPermission(uid) || checkMainlineNetworkStackPermission(uid)) { - return SUCCEEDED; - } - - // Location mode must be enabled - if (!isLocationModeEnabled()) { - return ERROR_LOCATION_MODE_OFF; - } - - // LocationAccess by App: caller must have Coarse/Fine Location permission to have access to - // location information. - if (!checkCallersLocationPermission(pkgName, featureId, uid, - true /* coarseForTargetSdkLessThanQ */, message)) { - return ERROR_LOCATION_PERMISSION_MISSING; - } - return SUCCEEDED; - } - - /** - * Checks that calling process has android.Manifest.permission.ACCESS_FINE_LOCATION or - * android.Manifest.permission.ACCESS_COARSE_LOCATION (depending on config/targetSDK level) - * and a corresponding app op is allowed for this package and uid. - * - * @param pkgName PackageName of the application requesting access - * @param featureId The feature in the package - * @param uid The uid of the package - * @param coarseForTargetSdkLessThanQ If true and the targetSDK < Q then will check for COARSE - * else (false or targetSDK >= Q) then will check for FINE - * @param message A message describing why the permission was checked. Only needed if this is - * not inside of a two-way binder call from the data receiver - */ - public boolean checkCallersLocationPermission(String pkgName, @Nullable String featureId, - int uid, boolean coarseForTargetSdkLessThanQ, @Nullable String message) { - - boolean isTargetSdkLessThanQ = isTargetSdkLessThan(pkgName, Build.VERSION_CODES.Q, uid); - - String permissionType = Manifest.permission.ACCESS_FINE_LOCATION; - if (coarseForTargetSdkLessThanQ && isTargetSdkLessThanQ) { - // Having FINE permission implies having COARSE permission (but not the reverse) - permissionType = Manifest.permission.ACCESS_COARSE_LOCATION; - } - if (getUidPermission(permissionType, uid) == PackageManager.PERMISSION_DENIED) { - return false; - } - - // Always checking FINE - even if will not enforce. This will record the request for FINE - // so that a location request by the app is surfaced to the user. - boolean isFineLocationAllowed = noteAppOpAllowed( - AppOpsManager.OPSTR_FINE_LOCATION, pkgName, featureId, uid, message); - if (isFineLocationAllowed) { - return true; - } - if (coarseForTargetSdkLessThanQ && isTargetSdkLessThanQ) { - return noteAppOpAllowed(AppOpsManager.OPSTR_COARSE_LOCATION, pkgName, featureId, uid, - message); - } - return false; - } - - /** - * Retrieves a handle to LocationManager (if not already done) and check if location is enabled. - */ - public boolean isLocationModeEnabled() { - final LocationManager LocationManager = - (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE); - try { - return LocationManager.isLocationEnabledForUser(UserHandle.of( - getCurrentUser())); - } catch (Exception e) { - Log.e(TAG, "Failure to get location mode via API, falling back to settings", e); - return false; - } - } - - private boolean isTargetSdkLessThan(String packageName, int versionCode, int callingUid) { - final long ident = Binder.clearCallingIdentity(); - try { - if (mContext.getPackageManager().getApplicationInfoAsUser( - packageName, 0, - UserHandle.getUserHandleForUid(callingUid)).targetSdkVersion - < versionCode) { - return true; - } - } catch (PackageManager.NameNotFoundException e) { - // In case of exception, assume unknown app (more strict checking) - // Note: This case will never happen since checkPackage is - // called to verify validity before checking App's version. - } finally { - Binder.restoreCallingIdentity(ident); - } - return false; - } - - private boolean noteAppOpAllowed(String op, String pkgName, @Nullable String featureId, - int uid, @Nullable String message) { - return mAppOpsManager.noteOp(op, uid, pkgName, featureId, message) - == AppOpsManager.MODE_ALLOWED; - } - - private void checkPackage(int uid, String pkgName) - throws SecurityException { - if (pkgName == null) { - throw new SecurityException("Checking UID " + uid + " but Package Name is Null"); - } - mAppOpsManager.checkPackage(uid, pkgName); - } - - @VisibleForTesting - protected int getCurrentUser() { - return ActivityManager.getCurrentUser(); - } - - private int getUidPermission(String permissionType, int uid) { - // We don't care about pid, pass in -1 - return mContext.checkPermission(permissionType, -1, uid); - } - - /** - * Returns true if the |uid| holds NETWORK_SETTINGS permission. - */ - public boolean checkNetworkSettingsPermission(int uid) { - return getUidPermission(android.Manifest.permission.NETWORK_SETTINGS, uid) - == PackageManager.PERMISSION_GRANTED; - } - - /** - * Returns true if the |uid| holds NETWORK_SETUP_WIZARD permission. - */ - public boolean checkNetworkSetupWizardPermission(int uid) { - return getUidPermission(android.Manifest.permission.NETWORK_SETUP_WIZARD, uid) - == PackageManager.PERMISSION_GRANTED; - } - - /** - * Returns true if the |uid| holds NETWORK_STACK permission. - */ - public boolean checkNetworkStackPermission(int uid) { - return getUidPermission(android.Manifest.permission.NETWORK_STACK, uid) - == PackageManager.PERMISSION_GRANTED; - } - - /** - * Returns true if the |uid| holds MAINLINE_NETWORK_STACK permission. - */ - public boolean checkMainlineNetworkStackPermission(int uid) { - return getUidPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, uid) - == PackageManager.PERMISSION_GRANTED; - } - -} diff --git a/core/java/com/android/internal/util/Protocol.java b/core/java/com/android/internal/util/Protocol.java index 276cad9f6d6d..bbd738bc9eb6 100644 --- a/core/java/com/android/internal/util/Protocol.java +++ b/core/java/com/android/internal/util/Protocol.java @@ -59,8 +59,6 @@ public class Protocol { public static final int BASE_TETHERING = 0x00050000; public static final int BASE_NSD_MANAGER = 0x00060000; public static final int BASE_NETWORK_STATE_TRACKER = 0x00070000; - public static final int BASE_CONNECTIVITY_MANAGER = 0x00080000; - public static final int BASE_NETWORK_AGENT = 0x00081000; public static final int BASE_NETWORK_FACTORY = 0x00083000; public static final int BASE_ETHERNET = 0x00084000; public static final int BASE_LOWPAN = 0x00085000; diff --git a/core/tests/utiltests/src/com/android/internal/util/LocationPermissionCheckerTest.java b/core/tests/utiltests/src/com/android/internal/util/LocationPermissionCheckerTest.java deleted file mode 100644 index 7175f562d7ef..000000000000 --- a/core/tests/utiltests/src/com/android/internal/util/LocationPermissionCheckerTest.java +++ /dev/null @@ -1,296 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.internal.util; - -import static android.Manifest.permission.NETWORK_SETTINGS; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.nullable; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.Manifest; -import android.app.ActivityManager; -import android.app.AppOpsManager; -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.location.LocationManager; -import android.os.Binder; -import android.os.Build; -import android.os.UserHandle; -import android.os.UserManager; - -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; - -import java.util.HashMap; - -/** Unit tests for {@link LocationPermissionChecker}. */ -public class LocationPermissionCheckerTest { - - public static final String TAG = "ConnectivityUtilTest"; - - // Mock objects for testing - @Mock private Context mMockContext; - @Mock private PackageManager mMockPkgMgr; - @Mock private ApplicationInfo mMockApplInfo; - @Mock private AppOpsManager mMockAppOps; - @Mock private UserManager mMockUserManager; - @Mock private LocationManager mLocationManager; - - private static final String TEST_PKG_NAME = "com.google.somePackage"; - private static final String TEST_FEATURE_ID = "com.google.someFeature"; - private static final int MANAGED_PROFILE_UID = 1100000; - private static final int OTHER_USER_UID = 1200000; - - private final String mInteractAcrossUsersFullPermission = - "android.permission.INTERACT_ACROSS_USERS_FULL"; - private final String mManifestStringCoarse = - Manifest.permission.ACCESS_COARSE_LOCATION; - private final String mManifestStringFine = - Manifest.permission.ACCESS_FINE_LOCATION; - - // Test variables - private int mWifiScanAllowApps; - private int mUid; - private int mCoarseLocationPermission; - private int mAllowCoarseLocationApps; - private int mFineLocationPermission; - private int mAllowFineLocationApps; - private int mNetworkSettingsPermission; - private int mCurrentUser; - private boolean mIsLocationEnabled; - private boolean mThrowSecurityException; - private Answer<Integer> mReturnPermission; - private HashMap<String, Integer> mPermissionsList = new HashMap<String, Integer>(); - private LocationPermissionChecker mChecker; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - initTestVars(); - } - - private void setupMocks() throws Exception { - when(mMockPkgMgr.getApplicationInfoAsUser(eq(TEST_PKG_NAME), eq(0), any())) - .thenReturn(mMockApplInfo); - when(mMockContext.getPackageManager()).thenReturn(mMockPkgMgr); - when(mMockAppOps.noteOp(AppOpsManager.OPSTR_WIFI_SCAN, mUid, TEST_PKG_NAME, - TEST_FEATURE_ID, null)).thenReturn(mWifiScanAllowApps); - when(mMockAppOps.noteOp(eq(AppOpsManager.OPSTR_COARSE_LOCATION), eq(mUid), - eq(TEST_PKG_NAME), eq(TEST_FEATURE_ID), nullable(String.class))) - .thenReturn(mAllowCoarseLocationApps); - when(mMockAppOps.noteOp(eq(AppOpsManager.OPSTR_FINE_LOCATION), eq(mUid), - eq(TEST_PKG_NAME), eq(TEST_FEATURE_ID), nullable(String.class))) - .thenReturn(mAllowFineLocationApps); - if (mThrowSecurityException) { - doThrow(new SecurityException("Package " + TEST_PKG_NAME + " doesn't belong" - + " to application bound to user " + mUid)) - .when(mMockAppOps).checkPackage(mUid, TEST_PKG_NAME); - } - when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)) - .thenReturn(mMockAppOps); - when(mMockContext.getSystemService(Context.USER_SERVICE)) - .thenReturn(mMockUserManager); - when(mMockContext.getSystemService(Context.LOCATION_SERVICE)).thenReturn(mLocationManager); - } - - private void setupTestCase() throws Exception { - setupMocks(); - setupMockInterface(); - mChecker = new LocationPermissionChecker(mMockContext); - } - - private void initTestVars() { - mPermissionsList.clear(); - mReturnPermission = createPermissionAnswer(); - mWifiScanAllowApps = AppOpsManager.MODE_ERRORED; - mUid = OTHER_USER_UID; - mThrowSecurityException = true; - mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.M; - mIsLocationEnabled = false; - mCurrentUser = ActivityManager.getCurrentUser(); - mCoarseLocationPermission = PackageManager.PERMISSION_DENIED; - mFineLocationPermission = PackageManager.PERMISSION_DENIED; - mAllowCoarseLocationApps = AppOpsManager.MODE_ERRORED; - mAllowFineLocationApps = AppOpsManager.MODE_ERRORED; - mNetworkSettingsPermission = PackageManager.PERMISSION_DENIED; - } - - private void setupMockInterface() { - Binder.restoreCallingIdentity((((long) mUid) << 32) | Binder.getCallingPid()); - doAnswer(mReturnPermission).when(mMockContext).checkPermission( - anyString(), anyInt(), anyInt()); - when(mMockUserManager.isSameProfileGroup(UserHandle.SYSTEM, - UserHandle.getUserHandleForUid(MANAGED_PROFILE_UID))) - .thenReturn(true); - when(mMockContext.checkPermission(mManifestStringCoarse, -1, mUid)) - .thenReturn(mCoarseLocationPermission); - when(mMockContext.checkPermission(mManifestStringFine, -1, mUid)) - .thenReturn(mFineLocationPermission); - when(mMockContext.checkPermission(NETWORK_SETTINGS, -1, mUid)) - .thenReturn(mNetworkSettingsPermission); - when(mLocationManager.isLocationEnabledForUser(any())).thenReturn(mIsLocationEnabled); - } - - private Answer<Integer> createPermissionAnswer() { - return new Answer<Integer>() { - @Override - public Integer answer(InvocationOnMock invocation) { - int myUid = (int) invocation.getArguments()[1]; - String myPermission = (String) invocation.getArguments()[0]; - mPermissionsList.get(myPermission); - if (mPermissionsList.containsKey(myPermission)) { - int uid = mPermissionsList.get(myPermission); - if (myUid == uid) { - return PackageManager.PERMISSION_GRANTED; - } - } - return PackageManager.PERMISSION_DENIED; - } - }; - } - - @Test - public void testEnforceLocationPermission_HasAllPermissions_BeforeQ() throws Exception { - mIsLocationEnabled = true; - mThrowSecurityException = false; - mCoarseLocationPermission = PackageManager.PERMISSION_GRANTED; - mAllowCoarseLocationApps = AppOpsManager.MODE_ALLOWED; - mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED; - mUid = mCurrentUser; - setupTestCase(); - - final int result = - mChecker.checkLocationPermissionWithDetailInfo( - TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null); - assertEquals(LocationPermissionChecker.SUCCEEDED, result); - } - - @Test - public void testEnforceLocationPermission_HasAllPermissions_AfterQ() throws Exception { - mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.Q; - mIsLocationEnabled = true; - mThrowSecurityException = false; - mUid = mCurrentUser; - mFineLocationPermission = PackageManager.PERMISSION_GRANTED; - mAllowFineLocationApps = AppOpsManager.MODE_ALLOWED; - mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED; - setupTestCase(); - - final int result = - mChecker.checkLocationPermissionWithDetailInfo( - TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null); - assertEquals(LocationPermissionChecker.SUCCEEDED, result); - } - - @Test - public void testEnforceLocationPermission_PkgNameAndUidMismatch() throws Exception { - mThrowSecurityException = true; - mIsLocationEnabled = true; - mFineLocationPermission = PackageManager.PERMISSION_GRANTED; - mAllowFineLocationApps = AppOpsManager.MODE_ALLOWED; - mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED; - setupTestCase(); - - assertThrows(SecurityException.class, - () -> mChecker.checkLocationPermissionWithDetailInfo( - TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null)); - } - - @Test - public void testenforceCanAccessScanResults_NoCoarseLocationPermission() throws Exception { - mThrowSecurityException = false; - mIsLocationEnabled = true; - setupTestCase(); - - final int result = - mChecker.checkLocationPermissionWithDetailInfo( - TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null); - assertEquals(LocationPermissionChecker.ERROR_LOCATION_PERMISSION_MISSING, result); - } - - @Test - public void testenforceCanAccessScanResults_NoFineLocationPermission() throws Exception { - mThrowSecurityException = false; - mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.Q; - mIsLocationEnabled = true; - mCoarseLocationPermission = PackageManager.PERMISSION_GRANTED; - mAllowFineLocationApps = AppOpsManager.MODE_ERRORED; - mUid = MANAGED_PROFILE_UID; - setupTestCase(); - - final int result = - mChecker.checkLocationPermissionWithDetailInfo( - TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null); - assertEquals(LocationPermissionChecker.ERROR_LOCATION_PERMISSION_MISSING, result); - verify(mMockAppOps, never()).noteOp(anyInt(), anyInt(), anyString()); - } - - @Test - public void testenforceCanAccessScanResults_LocationModeDisabled() throws Exception { - mThrowSecurityException = false; - mUid = MANAGED_PROFILE_UID; - mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED; - mPermissionsList.put(mInteractAcrossUsersFullPermission, mUid); - mIsLocationEnabled = false; - - setupTestCase(); - - final int result = - mChecker.checkLocationPermissionWithDetailInfo( - TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null); - assertEquals(LocationPermissionChecker.ERROR_LOCATION_MODE_OFF, result); - } - - @Test - public void testenforceCanAccessScanResults_LocationModeDisabledHasNetworkSettings() - throws Exception { - mThrowSecurityException = false; - mIsLocationEnabled = false; - mNetworkSettingsPermission = PackageManager.PERMISSION_GRANTED; - setupTestCase(); - - final int result = - mChecker.checkLocationPermissionWithDetailInfo( - TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null); - assertEquals(LocationPermissionChecker.SUCCEEDED, result); - } - - - private static void assertThrows(Class<? extends Exception> exceptionClass, Runnable r) { - try { - r.run(); - Assert.fail("Expected " + exceptionClass + " to be thrown."); - } catch (Exception exception) { - assertTrue(exceptionClass.isInstance(exception)); - } - } -} diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java index 937f01ce3767..a08f390c9fd3 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -207,7 +207,7 @@ public class KeyStore { case UserState.LSKF_LOCKED: return KeyStore.State.LOCKED; default: - throw new AssertionError(KeyStore.VALUE_CORRUPTED); + throw new AssertionError(userState); } } ret = mBinder.getState(userId); diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java index f8a642a68dd7..4e8a273ecc9e 100644 --- a/media/java/android/media/MediaFormat.java +++ b/media/java/android/media/MediaFormat.java @@ -161,6 +161,10 @@ public final class MediaFormat { public static final String MIMETYPE_AUDIO_EAC3_JOC = "audio/eac3-joc"; public static final String MIMETYPE_AUDIO_AC4 = "audio/ac4"; public static final String MIMETYPE_AUDIO_SCRAMBLED = "audio/scrambled"; + /** MIME type for MPEG-H Audio single stream */ + public static final String MIMETYPE_AUDIO_MPEGH_MHA1 = "audio/mha1"; + /** MIME type for MPEG-H Audio single stream, encapsulated in MHAS */ + public static final String MIMETYPE_AUDIO_MPEGH_MHM1 = "audio/mhm1"; /** * MIME type for HEIF still image data encoded in HEVC. diff --git a/packages/Connectivity/framework/api/current.txt b/packages/Connectivity/framework/api/current.txt index 243e4ca4295a..f22d4b7b779a 100644 --- a/packages/Connectivity/framework/api/current.txt +++ b/packages/Connectivity/framework/api/current.txt @@ -87,6 +87,7 @@ package android.net { method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public boolean isActiveNetworkMetered(); method public boolean isDefaultNetworkActive(); method @Deprecated public static boolean isNetworkTypeValid(int); + method public void registerBestMatchingNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback); method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback); diff --git a/packages/Connectivity/framework/api/module-lib-current.txt b/packages/Connectivity/framework/api/module-lib-current.txt index cc95a7cc2413..bb296476c72f 100644 --- a/packages/Connectivity/framework/api/module-lib-current.txt +++ b/packages/Connectivity/framework/api/module-lib-current.txt @@ -11,6 +11,7 @@ package android.net { method @NonNull public static String getPrivateDnsMode(@NonNull android.content.Context); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback); + method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setProfileNetworkPreference(@NonNull android.os.UserHandle, int, @Nullable java.util.concurrent.Executor, @Nullable Runnable); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle); field public static final String PRIVATE_DNS_MODE_OFF = "off"; field public static final String PRIVATE_DNS_MODE_OPPORTUNISTIC = "opportunistic"; diff --git a/packages/Connectivity/framework/api/system-current.txt b/packages/Connectivity/framework/api/system-current.txt index a98f14ea9408..4dca411cca24 100644 --- a/packages/Connectivity/framework/api/system-current.txt +++ b/packages/Connectivity/framework/api/system-current.txt @@ -56,7 +56,7 @@ package android.net { method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEventCallback); method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void requestNetwork(@NonNull android.net.NetworkRequest, int, int, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_AIRPLANE_MODE, android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void setAirplaneMode(boolean); - method @RequiresPermission(android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE) public void setOemNetworkPreference(@NonNull android.net.OemNetworkPreferences, @Nullable java.util.concurrent.Executor, @Nullable android.net.ConnectivityManager.OnSetOemNetworkPreferenceListener); + method @RequiresPermission(android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE) public void setOemNetworkPreference(@NonNull android.net.OemNetworkPreferences, @Nullable java.util.concurrent.Executor, @Nullable Runnable); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public boolean shouldAvoidBadWifi(); method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void startCaptivePortalApp(@NonNull android.net.Network, @NonNull android.os.Bundle); method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback); @@ -67,6 +67,8 @@ package android.net { method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void unregisterTetheringEventCallback(@NonNull android.net.ConnectivityManager.OnTetheringEventCallback); field public static final String EXTRA_CAPTIVE_PORTAL_PROBE_SPEC = "android.net.extra.CAPTIVE_PORTAL_PROBE_SPEC"; field public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT = "android.net.extra.CAPTIVE_PORTAL_USER_AGENT"; + field public static final int PROFILE_NETWORK_PREFERENCE_DEFAULT = 0; // 0x0 + field public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE = 1; // 0x1 field public static final int TETHERING_BLUETOOTH = 2; // 0x2 field public static final int TETHERING_USB = 1; // 0x1 field public static final int TETHERING_WIFI = 0; // 0x0 @@ -78,10 +80,6 @@ package android.net { field @Deprecated public static final int TYPE_WIFI_P2P = 13; // 0xd } - public static interface ConnectivityManager.OnSetOemNetworkPreferenceListener { - method public void onComplete(); - } - @Deprecated public abstract static class ConnectivityManager.OnStartTetheringCallback { ctor @Deprecated public ConnectivityManager.OnStartTetheringCallback(); method @Deprecated public void onTetheringFailed(); diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java index 7bfc9702ba6a..ba5eb1090dbf 100644 --- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java +++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java @@ -64,6 +64,7 @@ import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; import android.os.ServiceSpecificException; +import android.os.UserHandle; import android.provider.Settings; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; @@ -76,7 +77,6 @@ import android.util.SparseIntArray; import com.android.connectivity.aidl.INetworkAgent; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; -import com.android.internal.util.Protocol; import libcore.net.event.NetworkEventDispatcher; @@ -971,6 +971,33 @@ public class ConnectivityManager { } /** + * Preference for {@link #setNetworkPreferenceForUser(UserHandle, int, Executor, Runnable)}. + * Specify that the traffic for this user should by follow the default rules. + * @hide + */ + @SystemApi + public static final int PROFILE_NETWORK_PREFERENCE_DEFAULT = 0; + + /** + * Preference for {@link #setNetworkPreferenceForUser(UserHandle, int, Executor, Runnable)}. + * Specify that the traffic for this user should by default go on a network with + * {@link NetworkCapabilities#NET_CAPABILITY_ENTERPRISE}, and on the system default network + * if no such network is available. + * @hide + */ + @SystemApi + public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE = 1; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + PROFILE_NETWORK_PREFERENCE_DEFAULT, + PROFILE_NETWORK_PREFERENCE_ENTERPRISE + }) + public @interface ProfileNetworkPreference { + } + + /** * Specifies the preferred network type. When the device has more * than one type available the preferred network type will be used. * @@ -3523,29 +3550,28 @@ public class ConnectivityManager { } } - private static final int BASE = Protocol.BASE_CONNECTIVITY_MANAGER; /** @hide */ - public static final int CALLBACK_PRECHECK = BASE + 1; + public static final int CALLBACK_PRECHECK = 1; /** @hide */ - public static final int CALLBACK_AVAILABLE = BASE + 2; + public static final int CALLBACK_AVAILABLE = 2; /** @hide arg1 = TTL */ - public static final int CALLBACK_LOSING = BASE + 3; + public static final int CALLBACK_LOSING = 3; /** @hide */ - public static final int CALLBACK_LOST = BASE + 4; + public static final int CALLBACK_LOST = 4; /** @hide */ - public static final int CALLBACK_UNAVAIL = BASE + 5; + public static final int CALLBACK_UNAVAIL = 5; /** @hide */ - public static final int CALLBACK_CAP_CHANGED = BASE + 6; + public static final int CALLBACK_CAP_CHANGED = 6; /** @hide */ - public static final int CALLBACK_IP_CHANGED = BASE + 7; + public static final int CALLBACK_IP_CHANGED = 7; /** @hide obj = NetworkCapabilities, arg1 = seq number */ - private static final int EXPIRE_LEGACY_REQUEST = BASE + 8; + private static final int EXPIRE_LEGACY_REQUEST = 8; /** @hide */ - public static final int CALLBACK_SUSPENDED = BASE + 9; + public static final int CALLBACK_SUSPENDED = 9; /** @hide */ - public static final int CALLBACK_RESUMED = BASE + 10; + public static final int CALLBACK_RESUMED = 10; /** @hide */ - public static final int CALLBACK_BLK_CHANGED = BASE + 11; + public static final int CALLBACK_BLK_CHANGED = 11; /** @hide */ public static String getCallbackName(int whichCallback) { @@ -4241,9 +4267,27 @@ public class ConnectivityManager { } /** - * @hide + * Registers to receive notifications about the best matching network which satisfy the given + * {@link NetworkRequest}. The callbacks will continue to be called until + * either the application exits or {@link #unregisterNetworkCallback(NetworkCallback)} is + * called. + * + * <p>To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * {@link #registerNetworkCallback} and its variants and {@link #requestNetwork} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with + * {@link #unregisterNetworkCallback(NetworkCallback)}. + * + * + * @param request {@link NetworkRequest} describing this request. + * @param networkCallback The {@link NetworkCallback} that the system will call as suitable + * networks change state. + * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + * @throws RuntimeException if the app already has too many callbacks registered. */ - // TODO: Make it public api. @SuppressLint("ExecutorRegistration") public void registerBestMatchingNetworkCallback(@NonNull NetworkRequest request, @NonNull NetworkCallback networkCallback, @NonNull Handler handler) { @@ -5049,19 +5093,6 @@ public class ConnectivityManager { } /** - * Listener for {@link #setOemNetworkPreference(OemNetworkPreferences, Executor, - * OnSetOemNetworkPreferenceListener)}. - * @hide - */ - @SystemApi - public interface OnSetOemNetworkPreferenceListener { - /** - * Called when setOemNetworkPreference() successfully completes. - */ - void onComplete(); - } - - /** * Used by automotive devices to set the network preferences used to direct traffic at an * application level as per the given OemNetworkPreferences. An example use-case would be an * automotive OEM wanting to provide connectivity for applications critical to the usage of a @@ -5083,16 +5114,16 @@ public class ConnectivityManager { @RequiresPermission(android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE) public void setOemNetworkPreference(@NonNull final OemNetworkPreferences preference, @Nullable @CallbackExecutor final Executor executor, - @Nullable final OnSetOemNetworkPreferenceListener listener) { + @Nullable final Runnable listener) { Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null"); if (null != listener) { Objects.requireNonNull(executor, "Executor must be non-null"); } - final IOnSetOemNetworkPreferenceListener listenerInternal = listener == null ? null : - new IOnSetOemNetworkPreferenceListener.Stub() { + final IOnCompleteListener listenerInternal = listener == null ? null : + new IOnCompleteListener.Stub() { @Override public void onComplete() { - executor.execute(listener::onComplete); + executor.execute(listener::run); } }; @@ -5104,6 +5135,52 @@ public class ConnectivityManager { } } + /** + * Request that a user profile is put by default on a network matching a given preference. + * + * See the documentation for the individual preferences for a description of the supported + * behaviors. + * + * @param profile the profile concerned. + * @param preference the preference for this profile. + * @param executor an executor to execute the listener on. Optional if listener is null. + * @param listener an optional listener to listen for completion of the operation. + * @throws IllegalArgumentException if {@code profile} is not a valid user profile. + * @throws SecurityException if missing the appropriate permissions. + * @hide + */ + // This function is for establishing per-profile default networking and can only be called by + // the device policy manager, running as the system server. It would make no sense to call it + // on a context for a user because it does not establish a setting on behalf of a user, rather + // it establishes a setting for a user on behalf of the DPM. + @SuppressLint({"UserHandle"}) + @SystemApi(client = MODULE_LIBRARIES) + @RequiresPermission(android.Manifest.permission.NETWORK_STACK) + public void setProfileNetworkPreference(@NonNull final UserHandle profile, + @ProfileNetworkPreference final int preference, + @Nullable @CallbackExecutor final Executor executor, + @Nullable final Runnable listener) { + if (null != listener) { + Objects.requireNonNull(executor, "Pass a non-null executor, or a null listener"); + } + final IOnCompleteListener proxy; + if (null == listener) { + proxy = null; + } else { + proxy = new IOnCompleteListener.Stub() { + @Override + public void onComplete() { + executor.execute(listener::run); + } + }; + } + try { + mService.setProfileNetworkPreference(profile, preference, proxy); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + // The first network ID of IPSec tunnel interface. private static final int TUN_INTF_NETID_START = 0xFC00; // 0xFC00 = 64512 // The network ID range of IPSec tunnel interface. @@ -5124,8 +5201,7 @@ public class ConnectivityManager { /** * Get private DNS mode from settings. * - * @param context The Context to get its ContentResolver to query the private DNS mode from - * settings. + * @param context The Context to query the private DNS mode from settings. * @return A string of private DNS mode as one of the PRIVATE_DNS_MODE_* constants. * * @hide diff --git a/packages/Connectivity/framework/src/android/net/ConnectivitySettingsManager.java b/packages/Connectivity/framework/src/android/net/ConnectivitySettingsManager.java new file mode 100644 index 000000000000..d4543654522f --- /dev/null +++ b/packages/Connectivity/framework/src/android/net/ConnectivitySettingsManager.java @@ -0,0 +1,46 @@ +/* + * 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.net; + +/** + * A manager class for connectivity module settings. + * + * @hide + */ +public class ConnectivitySettingsManager { + + private ConnectivitySettingsManager() {} + + /** + * Whether to automatically switch away from wifi networks that lose Internet access. + * Only meaningful if config_networkAvoidBadWifi is set to 0, otherwise the system always + * avoids such networks. Valid values are: + * + * 0: Don't avoid bad wifi, don't prompt the user. Get stuck on bad wifi like it's 2013. + * null: Ask the user whether to switch away from bad wifi. + * 1: Avoid bad wifi. + */ + public static final String NETWORK_AVOID_BAD_WIFI = "network_avoid_bad_wifi"; + + /** + * User setting for ConnectivityManager.getMeteredMultipathPreference(). This value may be + * overridden by the system based on device or application state. If null, the value + * specified by config_networkMeteredMultipathPreference is used. + */ + public static final String NETWORK_METERED_MULTIPATH_PREFERENCE = + "network_metered_multipath_preference"; +} diff --git a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl index 1bbf1a95fcca..d83cc163b53f 100644 --- a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl +++ b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl @@ -20,7 +20,7 @@ import android.app.PendingIntent; import android.net.ConnectionInfo; import android.net.ConnectivityDiagnosticsManager; import android.net.IConnectivityDiagnosticsCallback; -import android.net.IOnSetOemNetworkPreferenceListener; +import android.net.IOnCompleteListener; import android.net.INetworkActivityListener; import android.net.IQosCallback; import android.net.ISocketKeepaliveCallback; @@ -43,6 +43,7 @@ import android.os.Messenger; import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.os.ResultReceiver; +import android.os.UserHandle; import com.android.connectivity.aidl.INetworkAgent; @@ -215,5 +216,8 @@ interface IConnectivityManager void unregisterQosCallback(in IQosCallback callback); void setOemNetworkPreference(in OemNetworkPreferences preference, - in IOnSetOemNetworkPreferenceListener listener); + in IOnCompleteListener listener); + + void setProfileNetworkPreference(in UserHandle profile, int preference, + in IOnCompleteListener listener); } diff --git a/packages/Connectivity/framework/src/android/net/NetworkAgent.java b/packages/Connectivity/framework/src/android/net/NetworkAgent.java index b3ab0ee8bd3c..a127c6f6de26 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkAgent.java +++ b/packages/Connectivity/framework/src/android/net/NetworkAgent.java @@ -37,7 +37,6 @@ import android.util.Log; import com.android.connectivity.aidl.INetworkAgent; import com.android.connectivity.aidl.INetworkAgentRegistry; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.Protocol; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -125,7 +124,10 @@ public abstract class NetworkAgent { */ public final int providerId; - private static final int BASE = Protocol.BASE_NETWORK_AGENT; + // ConnectivityService parses message constants from itself and NetworkAgent with MessageUtils + // for debugging purposes, and crashes if some messages have the same values. + // TODO: have ConnectivityService store message names in different maps and remove this base + private static final int BASE = 200; /** * Sent by ConnectivityService to the NetworkAgent to inform it of diff --git a/packages/Connectivity/framework/src/android/net/OemNetworkPreferences.java b/packages/Connectivity/framework/src/android/net/OemNetworkPreferences.java index 48bd29769f83..5a76cd6d6b0f 100644 --- a/packages/Connectivity/framework/src/android/net/OemNetworkPreferences.java +++ b/packages/Connectivity/framework/src/android/net/OemNetworkPreferences.java @@ -73,6 +73,14 @@ public final class OemNetworkPreferences implements Parcelable { private final Bundle mNetworkMappings; /** + * Return whether this object is empty. + * @hide + */ + public boolean isEmpty() { + return mNetworkMappings.keySet().size() == 0; + } + + /** * Return the currently built application package name to {@link OemNetworkPreference} mappings. * @return the current network preferences map. */ diff --git a/packages/Connectivity/framework/src/android/net/VpnTransportInfo.java b/packages/Connectivity/framework/src/android/net/VpnTransportInfo.java index c5100794899a..cd8f4c06de65 100644 --- a/packages/Connectivity/framework/src/android/net/VpnTransportInfo.java +++ b/packages/Connectivity/framework/src/android/net/VpnTransportInfo.java @@ -22,9 +22,6 @@ import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; -import android.util.SparseArray; - -import com.android.internal.util.MessageUtils; import java.util.Objects; @@ -38,9 +35,6 @@ import java.util.Objects; */ @SystemApi(client = MODULE_LIBRARIES) public final class VpnTransportInfo implements TransportInfo, Parcelable { - private static final SparseArray<String> sTypeToString = - MessageUtils.findMessageNames(new Class[]{VpnManager.class}, new String[]{"TYPE_VPN_"}); - /** Type of this VPN. */ public final int type; @@ -63,8 +57,7 @@ public final class VpnTransportInfo implements TransportInfo, Parcelable { @Override public String toString() { - final String typeString = sTypeToString.get(type, "VPN_TYPE_???"); - return String.format("VpnTransportInfo{%s}", typeString); + return String.format("VpnTransportInfo{type=%d}", type); } @Override diff --git a/packages/Connectivity/framework/src/android/net/util/MultinetworkPolicyTracker.java b/packages/Connectivity/framework/src/android/net/util/MultinetworkPolicyTracker.java index 739ddada50b4..6a49aa2576c3 100644 --- a/packages/Connectivity/framework/src/android/net/util/MultinetworkPolicyTracker.java +++ b/packages/Connectivity/framework/src/android/net/util/MultinetworkPolicyTracker.java @@ -16,8 +16,8 @@ package android.net.util; -import static android.provider.Settings.Global.NETWORK_AVOID_BAD_WIFI; -import static android.provider.Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE; +import static android.net.ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI; +import static android.net.ConnectivitySettingsManager.NETWORK_METERED_MULTIPATH_PREFERENCE; import android.annotation.NonNull; import android.content.BroadcastReceiver; @@ -110,8 +110,8 @@ public class MultinetworkPolicyTracker { mHandler = handler; mAvoidBadWifiCallback = avoidBadWifiCallback; mSettingsUris = Arrays.asList( - Settings.Global.getUriFor(NETWORK_AVOID_BAD_WIFI), - Settings.Global.getUriFor(NETWORK_METERED_MULTIPATH_PREFERENCE)); + Settings.Global.getUriFor(NETWORK_AVOID_BAD_WIFI), + Settings.Global.getUriFor(NETWORK_METERED_MULTIPATH_PREFERENCE)); mResolver = mContext.getContentResolver(); mSettingObserver = new SettingObserver(); mBroadcastReceiver = new BroadcastReceiver() { diff --git a/packages/Connectivity/service/ServiceConnectivityResources/res/values/config.xml b/packages/Connectivity/service/ServiceConnectivityResources/res/values/config.xml index 7d98c76a40ba..06c81921fd3f 100644 --- a/packages/Connectivity/service/ServiceConnectivityResources/res/values/config.xml +++ b/packages/Connectivity/service/ServiceConnectivityResources/res/values/config.xml @@ -42,4 +42,14 @@ --> </string-array> + <string-array translatable="false" name="config_legacy_networktype_restore_timers"> + <item>2,60000</item><!-- mobile_mms --> + <item>3,60000</item><!-- mobile_supl --> + <item>4,60000</item><!-- mobile_dun --> + <item>5,60000</item><!-- mobile_hipri --> + <item>10,60000</item><!-- mobile_fota --> + <item>11,60000</item><!-- mobile_ims --> + <item>12,60000</item><!-- mobile_cbs --> + </string-array> + </resources>
\ No newline at end of file diff --git a/packages/Connectivity/service/ServiceConnectivityResources/res/values/overlayable.xml b/packages/Connectivity/service/ServiceConnectivityResources/res/values/overlayable.xml index 00ec2df0e6f1..da8aee56276c 100644 --- a/packages/Connectivity/service/ServiceConnectivityResources/res/values/overlayable.xml +++ b/packages/Connectivity/service/ServiceConnectivityResources/res/values/overlayable.xml @@ -17,11 +17,11 @@ <overlayable name="ServiceConnectivityResourcesConfig"> <policy type="product|system|vendor"> <!-- Configuration values for ConnectivityService --> + <item type="array" name="config_legacy_networktype_restore_timers"/> <item type="string" name="config_networkCaptivePortalServerUrl"/> <item type="integer" name="config_networkTransitionTimeout"/> <item type="array" name="config_wakeonlan_supported_interfaces"/> - </policy> </overlayable> </resources> diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java index 841a49e6d4fd..cbfd4d8ad07b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java @@ -75,7 +75,8 @@ public class WifiStatusTracker { .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR).build(); - private final NetworkCallback mNetworkCallback = new NetworkCallback() { + private final NetworkCallback mNetworkCallback = + new NetworkCallback(NetworkCallback.FLAG_INCLUDE_LOCATION_INFO) { @Override public void onAvailable( Network network, NetworkCapabilities networkCapabilities, @@ -131,7 +132,8 @@ public class WifiStatusTracker { } } }; - private final NetworkCallback mDefaultNetworkCallback = new NetworkCallback() { + private final NetworkCallback mDefaultNetworkCallback = + new NetworkCallback(NetworkCallback.FLAG_INCLUDE_LOCATION_INFO) { @Override public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) { // network is now the default network, and its capabilities are nc. diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java index a78c223bf883..378907c400d7 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java @@ -73,6 +73,16 @@ public class UdfpsKeyguardView extends UdfpsAnimationView { } @Override + void onIlluminationStarting() { + setVisibility(View.INVISIBLE); + } + + @Override + void onIlluminationStopped() { + setVisibility(View.VISIBLE); + } + + @Override public boolean dozeTimeTick() { // TODO: burnin mFingerprintDrawable.dozeTimeTick(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java index 2cd367d03102..3e3451e64b49 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java @@ -445,11 +445,6 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D final ArrayList<String> tiles = new ArrayList<String>(); boolean addedDefault = false; Set<String> addedSpecs = new ArraySet<>(); - // TODO(b/174753536): Move it into the config file. - if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) { - tiles.add("internet"); - addedSpecs.add("internet"); - } for (String tile : tileList.split(",")) { tile = tile.trim(); if (tile.isEmpty()) continue; @@ -457,17 +452,6 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D if (!addedDefault) { List<String> defaultSpecs = getDefaultSpecs(context); for (String spec : defaultSpecs) { - // TODO(b/174753536): Move it into the config file. - if (FeatureFlagUtils.isEnabled( - context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) { - if (spec.equals("wifi") || spec.equals("cell")) { - continue; - } - } else { - if (spec.equals("internet")) { - continue; - } - } if (!addedSpecs.contains(spec)) { tiles.add(spec); addedSpecs.add(spec); @@ -476,18 +460,40 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D addedDefault = true; } } else { - // TODO(b/174753536): Move it into the config file. - if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) { - if (tile.equals("wifi") || tile.equals("cell")) { - continue; - } - } if (!addedSpecs.contains(tile)) { tiles.add(tile); addedSpecs.add(tile); } } } + // TODO(b/174753536): Move it into the config file. + // Only do the below hacking when at least one of the below tiles exist + // --InternetTile + // --WiFiTile + // --CellularTIle + if (tiles.contains("internet") || tiles.contains("wifi") || tiles.contains("cell")) { + if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) { + if (!tiles.contains("internet")) { + tiles.add("internet"); + } + if (tiles.contains("wifi")) { + tiles.remove("wifi"); + } + if (tiles.contains("cell")) { + tiles.remove("cell"); + } + } else { + if (tiles.contains("internet")) { + tiles.remove("internet"); + } + if (!tiles.contains("wifi")) { + tiles.add("wifi"); + } + if (!tiles.contains("cell")) { + tiles.add("cell"); + } + } + } return tiles; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java index 7eeb4bd19f1a..32b41ec0ab66 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java @@ -115,9 +115,32 @@ public class TileQueryHelper { final ArrayList<QSTile> tilesToAdd = new ArrayList<>(); // TODO(b/174753536): Move it into the config file. - if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) { - if (!possibleTiles.contains("internet")) { - possibleTiles.add("internet"); + // Only do the below hacking when at least one of the below tiles exist + // --InternetTile + // --WiFiTile + // --CellularTIle + if (possibleTiles.contains("internet") || possibleTiles.contains("wifi") + || possibleTiles.contains("cell")) { + if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) { + if (!possibleTiles.contains("internet")) { + possibleTiles.add("internet"); + } + if (possibleTiles.contains("wifi")) { + possibleTiles.remove("wifi"); + } + if (possibleTiles.contains("cell")) { + possibleTiles.remove("cell"); + } + } else { + if (possibleTiles.contains("internet")) { + possibleTiles.remove("internet"); + } + if (!possibleTiles.contains("wifi")) { + possibleTiles.add("wifi"); + } + if (!possibleTiles.contains("cell")) { + possibleTiles.add("cell"); + } } } for (String spec : possibleTiles) { @@ -125,15 +148,6 @@ public class TileQueryHelper { // Do not include CustomTile. Those will be created by `addPackageTiles`. if (spec.startsWith(CustomTile.PREFIX)) continue; // TODO(b/174753536): Move it into the config file. - if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) { - if (spec.equals("wifi") || spec.equals("cell")) { - continue; - } - } else { - if (spec.equals("internet")) { - continue; - } - } final QSTile tile = host.createTile(spec); if (tile == null) { continue; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index fbdaf9cdae20..db039b44d91a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -31,6 +31,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.res.Configuration; import android.net.ConnectivityManager; +import android.net.ConnectivityManager.NetworkCallback; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkScoreManager; @@ -307,7 +308,8 @@ public class NetworkControllerImpl extends BroadcastReceiver mWifiManager.registerScanResultsCallback(mReceiverHandler::post, scanResultsCallback); } - ConnectivityManager.NetworkCallback callback = new ConnectivityManager.NetworkCallback(){ + NetworkCallback callback = + new NetworkCallback(NetworkCallback.FLAG_INCLUDE_LOCATION_INFO){ private Network mLastNetwork; private NetworkCapabilities mLastNetworkCapabilities; diff --git a/services/core/java/com/android/server/BluetoothDeviceConfigListener.java b/services/core/java/com/android/server/BluetoothDeviceConfigListener.java index 2dcf82ff9410..611a37de70f4 100644 --- a/services/core/java/com/android/server/BluetoothDeviceConfigListener.java +++ b/services/core/java/com/android/server/BluetoothDeviceConfigListener.java @@ -17,6 +17,9 @@ package com.android.server; import android.provider.DeviceConfig; +import android.util.Slog; + +import java.util.ArrayList; /** * The BluetoothDeviceConfigListener handles system device config change callback and checks @@ -30,10 +33,12 @@ import android.provider.DeviceConfig; class BluetoothDeviceConfigListener { private static final String TAG = "BluetoothDeviceConfigListener"; - BluetoothManagerService mService; + private final BluetoothManagerService mService; + private final boolean mLogDebug; - BluetoothDeviceConfigListener(BluetoothManagerService service) { + BluetoothDeviceConfigListener(BluetoothManagerService service, boolean logDebug) { mService = service; + mLogDebug = logDebug; DeviceConfig.addOnPropertiesChangedListener( DeviceConfig.NAMESPACE_BLUETOOTH, (Runnable r) -> r.run(), @@ -47,6 +52,13 @@ class BluetoothDeviceConfigListener { if (!properties.getNamespace().equals(DeviceConfig.NAMESPACE_BLUETOOTH)) { return; } + if (mLogDebug) { + ArrayList<String> flags = new ArrayList<>(); + for (String name : properties.getKeyset()) { + flags.add(name + "='" + properties.getString(name, "") + "'"); + } + Slog.d(TAG, "onPropertiesChanged: " + String.join(",", flags)); + } boolean foundInit = false; for (String name : properties.getKeyset()) { if (name.startsWith("INIT_")) { diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java index dc24ffdb936d..f0c9ba93b080 100644 --- a/services/core/java/com/android/server/BluetoothManagerService.java +++ b/services/core/java/com/android/server/BluetoothManagerService.java @@ -454,6 +454,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { if (mHandler.hasMessages(MESSAGE_INIT_FLAGS_CHANGED) && state == BluetoothProfile.STATE_DISCONNECTED && !mBluetoothModeChangeHelper.isA2dpOrHearingAidConnected()) { + Slog.i(TAG, "Device disconnected, reactivating pending flag changes"); onInitFlagsChanged(); } } @@ -820,6 +821,35 @@ class BluetoothManagerService extends IBluetoothManager.Stub { return enabledProfiles; } + private boolean isDeviceProvisioned() { + return Settings.Global.getInt(mContentResolver, Settings.Global.DEVICE_PROVISIONED, + 0) != 0; + } + + // Monitor change of BLE scan only mode settings. + private void registerForProvisioningStateChange() { + ContentObserver contentObserver = new ContentObserver(null) { + @Override + public void onChange(boolean selfChange) { + if (!isDeviceProvisioned()) { + if (DBG) { + Slog.d(TAG, "DEVICE_PROVISIONED setting changed, but device is not " + + "provisioned"); + } + return; + } + if (mHandler.hasMessages(MESSAGE_INIT_FLAGS_CHANGED)) { + Slog.i(TAG, "Device provisioned, reactivating pending flag changes"); + onInitFlagsChanged(); + } + } + }; + + mContentResolver.registerContentObserver( + Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), false, + contentObserver); + } + // Monitor change of BLE scan only mode settings. private void registerForBleScanModeChange() { ContentObserver contentObserver = new ContentObserver(null) { @@ -1385,7 +1415,8 @@ class BluetoothManagerService extends IBluetoothManager.Stub { if (mBluetoothAirplaneModeListener != null) { mBluetoothAirplaneModeListener.start(mBluetoothModeChangeHelper); } - mBluetoothDeviceConfigListener = new BluetoothDeviceConfigListener(this); + registerForProvisioningStateChange(); + mBluetoothDeviceConfigListener = new BluetoothDeviceConfigListener(this, DBG); } /** @@ -2229,12 +2260,25 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } mHandler.removeMessages(MESSAGE_INIT_FLAGS_CHANGED); if (mBluetoothModeChangeHelper.isA2dpOrHearingAidConnected()) { + Slog.i(TAG, "Delaying MESSAGE_INIT_FLAGS_CHANGED by " + + DELAY_FOR_RETRY_INIT_FLAG_CHECK_MS + + " ms due to existing connections"); + mHandler.sendEmptyMessageDelayed( + MESSAGE_INIT_FLAGS_CHANGED, + DELAY_FOR_RETRY_INIT_FLAG_CHECK_MS); + break; + } + if (!isDeviceProvisioned()) { + Slog.i(TAG, "Delaying MESSAGE_INIT_FLAGS_CHANGED by " + + DELAY_FOR_RETRY_INIT_FLAG_CHECK_MS + + "ms because device is not provisioned"); mHandler.sendEmptyMessageDelayed( MESSAGE_INIT_FLAGS_CHANGED, DELAY_FOR_RETRY_INIT_FLAG_CHECK_MS); break; } if (mBluetooth != null && isEnabled()) { + Slog.i(TAG, "Restarting Bluetooth due to init flag change"); restartForReason( BluetoothProtoEnums.ENABLE_DISABLE_REASON_INIT_FLAGS_CHANGED); } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index f666490d13aa..3194bdcaad18 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -17,6 +17,10 @@ package com.android.server; import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE; +import static android.content.pm.PackageManager.FEATURE_BLUETOOTH; +import static android.content.pm.PackageManager.FEATURE_WATCH; +import static android.content.pm.PackageManager.FEATURE_WIFI; +import static android.content.pm.PackageManager.FEATURE_WIFI_DIRECT; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_ATTEMPTED_BITMASK; import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_SUCCEEDED_BITMASK; @@ -28,15 +32,30 @@ import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_TCP import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_TCP_PACKET_FAIL_RATE; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC; +import static android.net.ConnectivityManager.TYPE_BLUETOOTH; import static android.net.ConnectivityManager.TYPE_ETHERNET; +import static android.net.ConnectivityManager.TYPE_MOBILE; +import static android.net.ConnectivityManager.TYPE_MOBILE_CBS; +import static android.net.ConnectivityManager.TYPE_MOBILE_DUN; +import static android.net.ConnectivityManager.TYPE_MOBILE_EMERGENCY; +import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA; +import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI; +import static android.net.ConnectivityManager.TYPE_MOBILE_IA; +import static android.net.ConnectivityManager.TYPE_MOBILE_IMS; +import static android.net.ConnectivityManager.TYPE_MOBILE_MMS; +import static android.net.ConnectivityManager.TYPE_MOBILE_SUPL; import static android.net.ConnectivityManager.TYPE_NONE; +import static android.net.ConnectivityManager.TYPE_PROXY; import static android.net.ConnectivityManager.TYPE_VPN; +import static android.net.ConnectivityManager.TYPE_WIFI; +import static android.net.ConnectivityManager.TYPE_WIFI_P2P; import static android.net.ConnectivityManager.getNetworkTypeName; import static android.net.ConnectivityManager.isNetworkTypeValid; import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS; import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL; import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID; import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL; +import static android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE; import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND; import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED; @@ -87,6 +106,7 @@ import android.net.ConnectivityDiagnosticsManager.ConnectivityReport; import android.net.ConnectivityDiagnosticsManager.DataStallReport; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; +import android.net.ConnectivitySettingsManager; import android.net.DataStallReportParcelable; import android.net.DnsResolverServiceManager; import android.net.ICaptivePortal; @@ -98,7 +118,7 @@ import android.net.INetworkActivityListener; import android.net.INetworkMonitor; import android.net.INetworkMonitorCallbacks; import android.net.INetworkPolicyListener; -import android.net.IOnSetOemNetworkPreferenceListener; +import android.net.IOnCompleteListener; import android.net.IQosCallback; import android.net.ISocketKeepaliveCallback; import android.net.InetAddresses; @@ -111,7 +131,6 @@ import android.net.Network; import android.net.NetworkAgent; import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; -import android.net.NetworkConfig; import android.net.NetworkInfo; import android.net.NetworkInfo.DetailedState; import android.net.NetworkMonitorManager; @@ -193,13 +212,13 @@ import com.android.connectivity.aidl.INetworkAgent; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; 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.net.module.util.BaseNetdUnsolicitedEventListener; import com.android.net.module.util.CollectionUtils; import com.android.net.module.util.LinkPropertiesUtils.CompareOrUpdateResult; import com.android.net.module.util.LinkPropertiesUtils.CompareResult; +import com.android.net.module.util.LocationPermissionChecker; import com.android.net.module.util.NetworkCapabilitiesUtils; import com.android.net.module.util.PermissionUtils; import com.android.server.connectivity.AutodestructReference; @@ -215,6 +234,7 @@ import com.android.server.connectivity.NetworkNotificationManager; import com.android.server.connectivity.NetworkNotificationManager.NotificationType; import com.android.server.connectivity.NetworkRanker; import com.android.server.connectivity.PermissionMonitor; +import com.android.server.connectivity.ProfileNetworkPreferences; import com.android.server.connectivity.ProxyTracker; import com.android.server.connectivity.QosCallbackTracker; import com.android.server.net.NetworkPolicyManagerInternal; @@ -559,8 +579,8 @@ public class ConnectivityService extends IConnectivityManager.Stub private static final int EVENT_SET_REQUIRE_VPN_FOR_UIDS = 47; /** - * used internally when setting the default networks for OemNetworkPreferences. - * obj = OemNetworkPreferences + * Used internally when setting the default networks for OemNetworkPreferences. + * obj = Pair<OemNetworkPreferences, listener> */ private static final int EVENT_SET_OEM_NETWORK_PREFERENCE = 48; @@ -570,6 +590,12 @@ public class ConnectivityService extends IConnectivityManager.Stub private static final int EVENT_REPORT_NETWORK_ACTIVITY = 49; /** + * Used internally when setting a network preference for a user profile. + * obj = Pair<ProfileNetworkPreference, Listener> + */ + private static final int EVENT_SET_PROFILE_NETWORK_PREFERENCE = 50; + + /** * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification * should be shown. */ @@ -618,11 +644,8 @@ public class ConnectivityService extends IConnectivityManager.Stub private UserManager mUserManager; - private NetworkConfig[] mNetConfigs; - private int mNetworksDefined; - // the set of network types that can only be enabled by system/sig apps - private List mProtectedNetworks; + private List<Integer> mProtectedNetworks; private Set<String> mWolSupportedInterfaces; @@ -712,18 +735,63 @@ public class ConnectivityService extends IConnectivityManager.Stub * They are therefore not thread-safe with respect to each other. * - getNetworkForType() can be called at any time on binder threads. It is synchronized * on mTypeLists to be thread-safe with respect to a concurrent remove call. + * - getRestoreTimerForType(type) is also synchronized on mTypeLists. * - dump is thread-safe with respect to concurrent add and remove calls. */ private final ArrayList<NetworkAgentInfo> mTypeLists[]; @NonNull private final ConnectivityService mService; + // Restore timers for requestNetworkForFeature (network type -> timer in ms). Types without + // an entry have no timer (equivalent to -1). Lazily loaded. + @NonNull + private ArrayMap<Integer, Integer> mRestoreTimers = new ArrayMap<>(); + LegacyTypeTracker(@NonNull ConnectivityService service) { mService = service; mTypeLists = new ArrayList[ConnectivityManager.MAX_NETWORK_TYPE + 1]; } - public void addSupportedType(int type) { + public void loadSupportedTypes(@NonNull Context ctx, @NonNull TelephonyManager tm) { + final PackageManager pm = ctx.getPackageManager(); + if (pm.hasSystemFeature(FEATURE_WIFI)) { + addSupportedType(TYPE_WIFI); + } + if (pm.hasSystemFeature(FEATURE_WIFI_DIRECT)) { + addSupportedType(TYPE_WIFI_P2P); + } + if (tm.isDataCapable()) { + // Telephony does not have granular support for these types: they are either all + // supported, or none is supported + addSupportedType(TYPE_MOBILE); + addSupportedType(TYPE_MOBILE_MMS); + addSupportedType(TYPE_MOBILE_SUPL); + addSupportedType(TYPE_MOBILE_DUN); + addSupportedType(TYPE_MOBILE_HIPRI); + addSupportedType(TYPE_MOBILE_FOTA); + addSupportedType(TYPE_MOBILE_IMS); + addSupportedType(TYPE_MOBILE_CBS); + addSupportedType(TYPE_MOBILE_IA); + addSupportedType(TYPE_MOBILE_EMERGENCY); + } + if (pm.hasSystemFeature(FEATURE_BLUETOOTH)) { + addSupportedType(TYPE_BLUETOOTH); + } + if (pm.hasSystemFeature(FEATURE_WATCH)) { + // TYPE_PROXY is only used on Wear + addSupportedType(TYPE_PROXY); + } + // Ethernet is often not specified in the configs, although many devices can use it via + // USB host adapters. Add it as long as the ethernet service is here. + if (ctx.getSystemService(Context.ETHERNET_SERVICE) != null) { + addSupportedType(TYPE_ETHERNET); + } + + // Always add TYPE_VPN as a supported type + addSupportedType(TYPE_VPN); + } + + private void addSupportedType(int type) { if (mTypeLists[type] != null) { throw new IllegalStateException( "legacy list for type " + type + "already initialized"); @@ -744,6 +812,35 @@ public class ConnectivityService extends IConnectivityManager.Stub return null; } + public int getRestoreTimerForType(int type) { + synchronized (mTypeLists) { + if (mRestoreTimers == null) { + mRestoreTimers = loadRestoreTimers(); + } + return mRestoreTimers.getOrDefault(type, -1); + } + } + + private ArrayMap<Integer, Integer> loadRestoreTimers() { + final String[] configs = mService.mResources.get().getStringArray( + com.android.connectivity.resources.R.array + .config_legacy_networktype_restore_timers); + final ArrayMap<Integer, Integer> ret = new ArrayMap<>(configs.length); + for (final String config : configs) { + final String[] splits = TextUtils.split(config, ","); + if (splits.length != 2) { + logwtf("Invalid restore timer token count: " + config); + continue; + } + try { + ret.put(Integer.parseInt(splits[0]), Integer.parseInt(splits[1])); + } catch (NumberFormatException e) { + logwtf("Invalid restore timer number format: " + config, e); + } + } + return ret; + } + private void maybeLogBroadcast(NetworkAgentInfo nai, DetailedState state, int type, boolean isDefaultNetwork) { if (DBG) { @@ -1166,64 +1263,12 @@ public class ConnectivityService extends IConnectivityManager.Stub mNetTransitionWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); mPendingIntentWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); - mNetConfigs = new NetworkConfig[ConnectivityManager.MAX_NETWORK_TYPE+1]; - - // TODO: What is the "correct" way to do determine if this is a wifi only device? - boolean wifiOnly = mSystemProperties.getBoolean("ro.radio.noril", false); - log("wifiOnly=" + wifiOnly); - String[] naStrings = context.getResources().getStringArray( - com.android.internal.R.array.networkAttributes); - for (String naString : naStrings) { - try { - NetworkConfig n = new NetworkConfig(naString); - if (VDBG) log("naString=" + naString + " config=" + n); - if (n.type > ConnectivityManager.MAX_NETWORK_TYPE) { - loge("Error in networkAttributes - ignoring attempt to define type " + - n.type); - continue; - } - if (wifiOnly && ConnectivityManager.isNetworkTypeMobile(n.type)) { - log("networkAttributes - ignoring mobile as this dev is wifiOnly " + - n.type); - continue; - } - if (mNetConfigs[n.type] != null) { - loge("Error in networkAttributes - ignoring attempt to redefine type " + - n.type); - continue; - } - mLegacyTypeTracker.addSupportedType(n.type); - - mNetConfigs[n.type] = n; - mNetworksDefined++; - } catch(Exception e) { - // ignore it - leave the entry null - } - } - - // Forcibly add TYPE_VPN as a supported type, if it has not already been added via config. - if (mNetConfigs[TYPE_VPN] == null) { - // mNetConfigs is used only for "restore time", which isn't applicable to VPNs, so we - // don't need to add TYPE_VPN to mNetConfigs. - mLegacyTypeTracker.addSupportedType(TYPE_VPN); - mNetworksDefined++; // used only in the log() statement below. - } - - // Do the same for Ethernet, since it's often not specified in the configs, although many - // devices can use it via USB host adapters. - if (mNetConfigs[TYPE_ETHERNET] == null - && mContext.getSystemService(Context.ETHERNET_SERVICE) != null) { - mLegacyTypeTracker.addSupportedType(TYPE_ETHERNET); - mNetworksDefined++; - } - - if (VDBG) log("mNetworksDefined=" + mNetworksDefined); - - mProtectedNetworks = new ArrayList<Integer>(); + mLegacyTypeTracker.loadSupportedTypes(mContext, mTelephonyManager); + mProtectedNetworks = new ArrayList<>(); int[] protectedNetworks = context.getResources().getIntArray( com.android.internal.R.array.config_protectedNetworks); for (int p : protectedNetworks) { - if ((mNetConfigs[p] != null) && (mProtectedNetworks.contains(p) == false)) { + if (mLegacyTypeTracker.isTypeSupported(p) && !mProtectedNetworks.contains(p)) { mProtectedNetworks.add(p); } else { if (DBG) loge("Ignoring protectedNetwork " + p); @@ -1290,11 +1335,16 @@ public class ConnectivityService extends IConnectivityManager.Stub } private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) { + return createDefaultNetworkCapabilitiesForUidRange(new UidRange(uid, uid)); + } + + private static NetworkCapabilities createDefaultNetworkCapabilitiesForUidRange( + @NonNull final UidRange uids) { final NetworkCapabilities netCap = new NetworkCapabilities(); netCap.addCapability(NET_CAPABILITY_INTERNET); netCap.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); netCap.removeCapability(NET_CAPABILITY_NOT_VPN); - netCap.setSingleUid(uid); + netCap.setUids(Collections.singleton(uids)); return netCap; } @@ -2604,13 +2654,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } catch (RemoteException | ServiceSpecificException e) { loge("Can't set TCP buffer sizes:" + e); } - - final Integer rwndValue = Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.TCP_DEFAULT_INIT_RWND, - mSystemProperties.getInt("net.tcp.default_init_rwnd", 0)); - if (rwndValue != 0) { - mSystemProperties.setTcpInitRwnd(rwndValue); - } } @Override @@ -2627,9 +2670,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // if the system property isn't set, use the value for the apn type int ret = RESTORE_DEFAULT_NETWORK_DELAY; - if ((networkType <= ConnectivityManager.MAX_NETWORK_TYPE) && - (mNetConfigs[networkType] != null)) { - ret = mNetConfigs[networkType].restoreTime; + if (mLegacyTypeTracker.isTypeSupported(networkType)) { + ret = mLegacyTypeTracker.getRestoreTimerForType(networkType); } return ret; } @@ -4533,12 +4575,17 @@ public class ConnectivityService extends IConnectivityManager.Stub handleSetRequireVpnForUids(toBool(msg.arg1), (UidRange[]) msg.obj); break; case EVENT_SET_OEM_NETWORK_PREFERENCE: { - final Pair<OemNetworkPreferences, IOnSetOemNetworkPreferenceListener> arg = - (Pair<OemNetworkPreferences, - IOnSetOemNetworkPreferenceListener>) msg.obj; + final Pair<OemNetworkPreferences, IOnCompleteListener> arg = + (Pair<OemNetworkPreferences, IOnCompleteListener>) msg.obj; handleSetOemNetworkPreference(arg.first, arg.second); break; } + case EVENT_SET_PROFILE_NETWORK_PREFERENCE: { + final Pair<ProfileNetworkPreferences.Preference, IOnCompleteListener> arg = + (Pair<ProfileNetworkPreferences.Preference, IOnCompleteListener>) + msg.obj; + handleSetProfileNetworkPreference(arg.first, arg.second); + } case EVENT_REPORT_NETWORK_ACTIVITY: mNetworkActivityTracker.handleReportNetworkActivity(); break; @@ -4837,6 +4884,10 @@ public class ConnectivityService extends IConnectivityManager.Stub Log.wtf(TAG, s); } + private static void logwtf(String s, Throwable t) { + Log.wtf(TAG, s, t); + } + private static void loge(String s) { Log.e(TAG, s); } @@ -5109,6 +5160,9 @@ public class ConnectivityService extends IConnectivityManager.Stub private void onUserRemoved(UserHandle user) { mPermissionMonitor.onUserRemoved(user); + // If there was a network preference for this user, remove it. + handleSetProfileNetworkPreference(new ProfileNetworkPreferences.Preference(user, null), + null /* listener */); if (mOemNetworkPreferences.getNetworkPreferences().size() > 0) { handleSetOemNetworkPreference(mOemNetworkPreferences, null); } @@ -5907,10 +5961,16 @@ public class ConnectivityService extends IConnectivityManager.Stub @GuardedBy("mBlockedAppUids") private final HashSet<Integer> mBlockedAppUids = new HashSet<>(); - // Current OEM network preferences. + // Current OEM network preferences. This object must only be written to on the handler thread. + // Since it is immutable and always non-null, other threads may read it if they only care + // about seeing a consistent object but not that it is current. @NonNull private OemNetworkPreferences mOemNetworkPreferences = new OemNetworkPreferences.Builder().build(); + // Current per-profile network preferences. This object follows the same threading rules as + // the OEM network preferences above. + @NonNull + private ProfileNetworkPreferences mProfileNetworkPreferences = new ProfileNetworkPreferences(); // The always-on request for an Internet-capable network that apps without a specific default // fall back to. @@ -8185,7 +8245,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } Settings.Global.putString(mContext.getContentResolver(), - Settings.Global.NETWORK_AVOID_BAD_WIFI, null); + ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI, null); } @Override @@ -8890,13 +8950,13 @@ public class ConnectivityService extends IConnectivityManager.Stub private int transportTypeToLegacyType(int type) { switch (type) { case NetworkCapabilities.TRANSPORT_CELLULAR: - return ConnectivityManager.TYPE_MOBILE; + return TYPE_MOBILE; case NetworkCapabilities.TRANSPORT_WIFI: - return ConnectivityManager.TYPE_WIFI; + return TYPE_WIFI; case NetworkCapabilities.TRANSPORT_BLUETOOTH: - return ConnectivityManager.TYPE_BLUETOOTH; + return TYPE_BLUETOOTH; case NetworkCapabilities.TRANSPORT_ETHERNET: - return ConnectivityManager.TYPE_ETHERNET; + return TYPE_ETHERNET; default: loge("Unexpected transport in transportTypeToLegacyType: " + type); } @@ -9111,6 +9171,143 @@ public class ConnectivityService extends IConnectivityManager.Stub mQosCallbackTracker.unregisterCallback(callback); } + // Network preference per-profile and OEM network preferences can't be set at the same + // time, because it is unclear what should happen if both preferences are active for + // one given UID. To make it possible, the stack would have to clarify what would happen + // in case both are active at the same time. The implementation may have to be adjusted + // to implement the resulting rules. For example, a priority could be defined between them, + // where the OEM preference would be considered less or more important than the enterprise + // preference ; this would entail implementing the priorities somehow, e.g. by doing + // UID arithmetic with UID ranges or passing a priority to netd so that the routing rules + // are set at the right level. Other solutions are possible, e.g. merging of the + // preferences for the relevant UIDs. + private static void throwConcurrentPreferenceException() { + throw new IllegalStateException("Can't set NetworkPreferenceForUser and " + + "set OemNetworkPreference at the same time"); + } + + /** + * Request that a user profile is put by default on a network matching a given preference. + * + * See the documentation for the individual preferences for a description of the supported + * behaviors. + * + * @param profile the profile concerned. + * @param preference the preference for this profile, as one of the PROFILE_NETWORK_PREFERENCE_* + * constants. + * @param listener an optional listener to listen for completion of the operation. + */ + @Override + public void setProfileNetworkPreference(@NonNull final UserHandle profile, + @ConnectivityManager.ProfileNetworkPreference final int preference, + @Nullable final IOnCompleteListener listener) { + Objects.requireNonNull(profile); + PermissionUtils.enforceNetworkStackPermission(mContext); + if (DBG) { + log("setProfileNetworkPreference " + profile + " to " + preference); + } + if (profile.getIdentifier() < 0) { + throw new IllegalArgumentException("Must explicitly specify a user handle (" + + "UserHandle.CURRENT not supported)"); + } + final UserManager um; + try { + um = mContext.createContextAsUser(profile, 0 /* flags */) + .getSystemService(UserManager.class); + } catch (IllegalStateException e) { + throw new IllegalArgumentException("Profile does not exist"); + } + if (!um.isManagedProfile()) { + throw new IllegalArgumentException("Profile must be a managed profile"); + } + // Strictly speaking, mOemNetworkPreferences should only be touched on the + // handler thread. However it is an immutable object, so reading the reference is + // safe - it's just possible the value is slightly outdated. For the final check, + // see #handleSetProfileNetworkPreference. But if this can be caught here it is a + // lot easier to understand, so opportunistically check it. + if (!mOemNetworkPreferences.isEmpty()) { + throwConcurrentPreferenceException(); + } + final NetworkCapabilities nc; + switch (preference) { + case ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT: + nc = null; + break; + case ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE: + final UidRange uids = UidRange.createForUser(profile); + nc = createDefaultNetworkCapabilitiesForUidRange(uids); + nc.addCapability(NET_CAPABILITY_ENTERPRISE); + nc.removeCapability(NET_CAPABILITY_NOT_RESTRICTED); + break; + default: + throw new IllegalArgumentException( + "Invalid preference in setProfileNetworkPreference"); + } + mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_PROFILE_NETWORK_PREFERENCE, + new Pair<>(new ProfileNetworkPreferences.Preference(profile, nc), listener))); + } + + private void validateNetworkCapabilitiesOfProfileNetworkPreference( + @Nullable final NetworkCapabilities nc) { + if (null == nc) return; // Null caps are always allowed. It means to remove the setting. + ensureRequestableCapabilities(nc); + } + + private ArraySet<NetworkRequestInfo> createNrisFromProfileNetworkPreferences( + @NonNull final ProfileNetworkPreferences prefs) { + final ArraySet<NetworkRequestInfo> result = new ArraySet<>(); + for (final ProfileNetworkPreferences.Preference pref : prefs.preferences) { + // The NRI for a user should be comprised of two layers: + // - The request for the capabilities + // - The request for the default network, for fallback. Create an image of it to + // have the correct UIDs in it (also a request can only be part of one NRI, because + // of lookups in 1:1 associations like mNetworkRequests). + // Note that denying a fallback can be implemented simply by not adding the second + // request. + final ArrayList<NetworkRequest> nrs = new ArrayList<>(); + nrs.add(createNetworkRequest(NetworkRequest.Type.REQUEST, pref.capabilities)); + nrs.add(createDefaultRequest()); + setNetworkRequestUids(nrs, pref.capabilities.getUids()); + final NetworkRequestInfo nri = new NetworkRequestInfo(nrs); + result.add(nri); + } + return result; + } + + private void handleSetProfileNetworkPreference( + @NonNull final ProfileNetworkPreferences.Preference preference, + @Nullable final IOnCompleteListener listener) { + // setProfileNetworkPreference and setOemNetworkPreference are mutually exclusive, in + // particular because it's not clear what preference should win in case both apply + // to the same app. + // The binder call has already checked this, but as mOemNetworkPreferences is only + // touched on the handler thread, it's theoretically not impossible that it has changed + // since. + if (!mOemNetworkPreferences.isEmpty()) { + // This may happen on a device with an OEM preference set when a user is removed. + // In this case, it's safe to ignore. In particular this happens in the tests. + loge("handleSetProfileNetworkPreference, but OEM network preferences not empty"); + return; + } + + validateNetworkCapabilitiesOfProfileNetworkPreference(preference.capabilities); + + mProfileNetworkPreferences = mProfileNetworkPreferences.plus(preference); + final ArraySet<NetworkRequestInfo> nris = + createNrisFromProfileNetworkPreferences(mProfileNetworkPreferences); + replaceDefaultNetworkRequestsForPreference(nris); + // Finally, rematch. + rematchAllNetworksAndRequests(); + + if (null != listener) { + try { + listener.onComplete(); + } catch (RemoteException e) { + loge("Listener for setProfileNetworkPreference has died"); + } + } + } + private void enforceAutomotiveDevice() { final boolean isAutomotiveDevice = mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); @@ -9129,17 +9326,26 @@ public class ConnectivityService extends IConnectivityManager.Stub * Calling this will overwrite the existing preference. * * @param preference {@link OemNetworkPreferences} The application network preference to be set. - * @param listener {@link ConnectivityManager.OnSetOemNetworkPreferenceListener} Listener used + * @param listener {@link ConnectivityManager.OnCompleteListener} Listener used * to communicate completion of setOemNetworkPreference(); */ @Override public void setOemNetworkPreference( @NonNull final OemNetworkPreferences preference, - @Nullable final IOnSetOemNetworkPreferenceListener listener) { + @Nullable final IOnCompleteListener listener) { enforceAutomotiveDevice(); enforceOemNetworkPreferencesPermission(); + if (!mProfileNetworkPreferences.isEmpty()) { + // Strictly speaking, mProfileNetworkPreferences should only be touched on the + // handler thread. However it is an immutable object, so reading the reference is + // safe - it's just possible the value is slightly outdated. For the final check, + // see #handleSetOemPreference. But if this can be caught here it is a + // lot easier to understand, so opportunistically check it. + throwConcurrentPreferenceException(); + } + Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null"); validateOemNetworkPreferences(preference); mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_OEM_NETWORK_PREFERENCE, @@ -9158,11 +9364,22 @@ public class ConnectivityService extends IConnectivityManager.Stub private void handleSetOemNetworkPreference( @NonNull final OemNetworkPreferences preference, - @Nullable final IOnSetOemNetworkPreferenceListener listener) { + @Nullable final IOnCompleteListener listener) { Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null"); if (DBG) { log("set OEM network preferences :" + preference.toString()); } + // setProfileNetworkPreference and setOemNetworkPreference are mutually exclusive, in + // particular because it's not clear what preference should win in case both apply + // to the same app. + // The binder call has already checked this, but as mOemNetworkPreferences is only + // touched on the handler thread, it's theoretically not impossible that it has changed + // since. + if (!mProfileNetworkPreferences.isEmpty()) { + logwtf("handleSetOemPreference, but per-profile network preferences not empty"); + return; + } + final ArraySet<NetworkRequestInfo> nris = new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(preference); replaceDefaultNetworkRequestsForPreference(nris); diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java index d10cf4dd0505..fa3771ac70fa 100644 --- a/services/core/java/com/android/server/PersistentDataBlockService.java +++ b/services/core/java/com/android/server/PersistentDataBlockService.java @@ -23,6 +23,7 @@ import android.app.ActivityManager; import android.content.Context; import android.content.pm.PackageManager; import android.os.Binder; +import android.os.FileUtils; import android.os.IBinder; import android.os.RemoteException; import android.os.SystemProperties; @@ -104,6 +105,9 @@ import java.util.concurrent.TimeUnit; public class PersistentDataBlockService extends SystemService { private static final String TAG = PersistentDataBlockService.class.getSimpleName(); + private static final String GSI_SANDBOX = "/data/gsi_persistent_data"; + private static final String GSI_RUNNING_PROP = "ro.gsid.image_running"; + private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst"; private static final int HEADER_SIZE = 8; // Magic number to mark block device as adhering to the format consumed by this service @@ -128,12 +132,13 @@ public class PersistentDataBlockService extends SystemService { private static final String FLASH_LOCK_UNLOCKED = "0"; private final Context mContext; - private final String mDataBlockFile; + private final boolean mIsRunningDSU; private final Object mLock = new Object(); private final CountDownLatch mInitDoneSignal = new CountDownLatch(1); private int mAllowedUid = -1; private long mBlockDeviceSize; + private String mDataBlockFile; @GuardedBy("mLock") private boolean mIsWritable = true; @@ -142,6 +147,7 @@ public class PersistentDataBlockService extends SystemService { super(context); mContext = context; mDataBlockFile = SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP); + mIsRunningDSU = SystemProperties.getBoolean(GSI_RUNNING_PROP, false); mBlockDeviceSize = -1; // Load lazily } @@ -286,14 +292,28 @@ public class PersistentDataBlockService extends SystemService { return true; } + private FileOutputStream getBlockOutputStream() throws IOException { + if (!mIsRunningDSU) { + return new FileOutputStream(new File(mDataBlockFile)); + } else { + File sandbox = new File(GSI_SANDBOX); + File realpdb = new File(SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP)); + if (!sandbox.exists()) { + FileUtils.copy(realpdb, sandbox); + mDataBlockFile = GSI_SANDBOX; + } + Slog.i(TAG, "PersistentDataBlock copy-on-write"); + return new FileOutputStream(sandbox); + } + } + private boolean computeAndWriteDigestLocked() { byte[] digest = computeDigestLocked(null); if (digest != null) { DataOutputStream outputStream; try { - outputStream = new DataOutputStream( - new FileOutputStream(new File(mDataBlockFile))); - } catch (FileNotFoundException e) { + outputStream = new DataOutputStream(getBlockOutputStream()); + } catch (IOException e) { Slog.e(TAG, "partition not available?", e); return false; } @@ -358,8 +378,8 @@ public class PersistentDataBlockService extends SystemService { private void formatPartitionLocked(boolean setOemUnlockEnabled) { DataOutputStream outputStream; try { - outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile))); - } catch (FileNotFoundException e) { + outputStream = new DataOutputStream(getBlockOutputStream()); + } catch (IOException e) { Slog.e(TAG, "partition not available?", e); return; } @@ -384,8 +404,8 @@ public class PersistentDataBlockService extends SystemService { private void doSetOemUnlockEnabledLocked(boolean enabled) { FileOutputStream outputStream; try { - outputStream = new FileOutputStream(new File(mDataBlockFile)); - } catch (FileNotFoundException e) { + outputStream = getBlockOutputStream(); + } catch (IOException e) { Slog.e(TAG, "partition not available", e); return; } @@ -461,8 +481,8 @@ public class PersistentDataBlockService extends SystemService { DataOutputStream outputStream; try { - outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile))); - } catch (FileNotFoundException e) { + outputStream = new DataOutputStream(getBlockOutputStream()); + } catch (IOException e) { Slog.e(TAG, "partition not available?", e); return -1; } @@ -547,6 +567,17 @@ public class PersistentDataBlockService extends SystemService { public void wipe() { enforceOemUnlockWritePermission(); + if (mIsRunningDSU) { + File sandbox = new File(GSI_SANDBOX); + if (sandbox.exists()) { + if (sandbox.delete()) { + mDataBlockFile = SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP); + } else { + Slog.e(TAG, "Failed to wipe sandbox persistent data block"); + } + } + return; + } synchronized (mLock) { int ret = nativeWipe(mDataBlockFile); @@ -706,8 +737,8 @@ public class PersistentDataBlockService extends SystemService { private void writeDataBuffer(long offset, ByteBuffer dataBuffer) { FileOutputStream outputStream; try { - outputStream = new FileOutputStream(new File(mDataBlockFile)); - } catch (FileNotFoundException e) { + outputStream = getBlockOutputStream(); + } catch (IOException e) { Slog.e(TAG, "partition not available", e); return; } diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java index 140f24f7cf8f..051cd9907bee 100644 --- a/services/core/java/com/android/server/VcnManagementService.java +++ b/services/core/java/com/android/server/VcnManagementService.java @@ -64,7 +64,7 @@ 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.LocationPermissionChecker; +import com.android.net.module.util.LocationPermissionChecker; import com.android.server.vcn.TelephonySubscriptionTracker; import com.android.server.vcn.Vcn; import com.android.server.vcn.VcnContext; diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java index 71bac577a4a0..ce728bc7c315 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java @@ -62,6 +62,7 @@ public class FaceResetLockoutClient extends HalClientMonitor<ISession> { // Nothing to do here } + @Override public void start(@NonNull Callback callback) { super.start(callback); startHalOperation(); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java index ddcfcad59203..adffba280c7f 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java @@ -63,6 +63,12 @@ class FingerprintResetLockoutClient extends HalClientMonitor<ISession> { } @Override + public void start(@NonNull Callback callback) { + super.start(callback); + startHalOperation(); + } + + @Override protected void startHalOperation() { try { getFreshDaemon().resetLockout(mSequentialId, mHardwareAuthToken); diff --git a/services/core/java/com/android/server/connectivity/MockableSystemProperties.java b/services/core/java/com/android/server/connectivity/MockableSystemProperties.java index ef767341d609..a25b89ac039a 100644 --- a/services/core/java/com/android/server/connectivity/MockableSystemProperties.java +++ b/services/core/java/com/android/server/connectivity/MockableSystemProperties.java @@ -17,7 +17,6 @@ package com.android.server.connectivity; import android.os.SystemProperties; -import android.sysprop.NetworkProperties; public class MockableSystemProperties { @@ -32,10 +31,4 @@ public class MockableSystemProperties { public boolean getBoolean(String key, boolean def) { return SystemProperties.getBoolean(key, def); } - /** - * Set net.tcp_def_init_rwnd to the tcp initial receive window size. - */ - public void setTcpInitRwnd(int value) { - NetworkProperties.tcp_init_rwnd(value); - } } diff --git a/services/core/java/com/android/server/connectivity/ProfileNetworkPreferences.java b/services/core/java/com/android/server/connectivity/ProfileNetworkPreferences.java new file mode 100644 index 000000000000..dd2815d9e2e3 --- /dev/null +++ b/services/core/java/com/android/server/connectivity/ProfileNetworkPreferences.java @@ -0,0 +1,87 @@ +/* + * 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.connectivity; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.NetworkCapabilities; +import android.os.UserHandle; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * A data class containing all the per-profile network preferences. + * + * A given profile can only have one preference. + */ +public class ProfileNetworkPreferences { + /** + * A single preference, as it applies to a given user profile. + */ + public static class Preference { + @NonNull public final UserHandle user; + // Capabilities are only null when sending an object to remove the setting for a user + @Nullable public final NetworkCapabilities capabilities; + + public Preference(@NonNull final UserHandle user, + @Nullable final NetworkCapabilities capabilities) { + this.user = user; + this.capabilities = null == capabilities ? null : new NetworkCapabilities(capabilities); + } + + /** toString */ + public String toString() { + return "[ProfileNetworkPreference user=" + user + " caps=" + capabilities + "]"; + } + } + + @NonNull public final List<Preference> preferences; + + public ProfileNetworkPreferences() { + preferences = Collections.EMPTY_LIST; + } + + private ProfileNetworkPreferences(@NonNull final List<Preference> list) { + preferences = Collections.unmodifiableList(list); + } + + /** + * Returns a new object consisting of this object plus the passed preference. + * + * If a preference already exists for the same user, it will be replaced by the passed + * preference. Passing a Preference object containing a null capabilities object is equivalent + * to (and indeed, implemented as) removing the preference for this user. + */ + public ProfileNetworkPreferences plus(@NonNull final Preference pref) { + final ArrayList<Preference> newPrefs = new ArrayList<>(); + for (final Preference existingPref : preferences) { + if (!existingPref.user.equals(pref.user)) { + newPrefs.add(existingPref); + } + } + if (null != pref.capabilities) { + newPrefs.add(pref); + } + return new ProfileNetworkPreferences(newPrefs); + } + + public boolean isEmpty() { + return preferences.isEmpty(); + } +} diff --git a/services/core/java/com/android/server/pm/ComponentResolver.java b/services/core/java/com/android/server/pm/ComponentResolver.java index 4be509b3f464..1d556fec31ea 100644 --- a/services/core/java/com/android/server/pm/ComponentResolver.java +++ b/services/core/java/com/android/server/pm/ComponentResolver.java @@ -1492,7 +1492,7 @@ public class ComponentResolver { } return null; } - final ResolveInfo res = new ResolveInfo(); + final ResolveInfo res = new ResolveInfo(info.hasCategory(Intent.CATEGORY_BROWSABLE)); res.activityInfo = ai; if ((mFlags & PackageManager.GET_RESOLVED_FILTER) != 0) { res.filter = info; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index dfe72b26f72a..1530e41917c7 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1768,6 +1768,11 @@ public class PackageManagerService extends IPackageManager.Stub public int[] getAllUserIds() { return mUserManager.getUserIds(); } + + @Override + public boolean doesUserExist(@UserIdInt int userId) { + return mUserManager.exists(userId); + } } /** @@ -2639,8 +2644,7 @@ public class PackageManagerService extends IPackageManager.Stub // We'll want to include browser possibilities in a few cases boolean includeBrowser = false; - if (!DomainVerificationUtils.isDomainVerificationIntent(intent, candidates, - matchFlags)) { + if (!DomainVerificationUtils.isDomainVerificationIntent(intent, matchFlags)) { result.addAll(undefinedList); // Maybe add one for the other profile. if (xpDomainInfo != null && xpDomainInfo.highestApprovalLevel @@ -25693,6 +25697,7 @@ public class PackageManagerService extends IPackageManager.Stub if (!convertedFromPreCreated || !readPermissionStateForUser(userId)) { mPermissionManager.onUserCreated(userId); mLegacyPermissionManager.grantDefaultPermissions(userId); + mDomainVerificationManager.clearUser(userId); } } diff --git a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java index 3b77c39cc31b..0c8e36b75425 100644 --- a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java +++ b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java @@ -189,7 +189,7 @@ public class ArtStatsLogUtils { private static int getDexMetadataType(String dexMetadataPath) { if (dexMetadataPath == null) { - return ArtStatsLog.ART_DATUM_REPORTED__DEX_METADATA_TYPE__NONE_DEX_METADATA; + return ArtStatsLog.ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_NONE; } StrictJarFile jarFile = null; try { @@ -199,17 +199,21 @@ public class ArtStatsLogUtils { boolean hasProfile = findFileName(jarFile, PROFILE_DEX_METADATA); boolean hasVdex = findFileName(jarFile, VDEX_DEX_METADATA); if (hasProfile && hasVdex) { - return ArtStatsLog.ART_DATUM_REPORTED__DEX_METADATA_TYPE__PROFILE_AND_VDEX; + return ArtStatsLog. + ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_PROFILE_AND_VDEX; } else if (hasProfile) { - return ArtStatsLog.ART_DATUM_REPORTED__DEX_METADATA_TYPE__PROFILE; + return ArtStatsLog. + ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_PROFILE; } else if (hasVdex) { - return ArtStatsLog.ART_DATUM_REPORTED__DEX_METADATA_TYPE__VDEX; + return ArtStatsLog. + ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_VDEX; } else { - return ArtStatsLog.ART_DATUM_REPORTED__DEX_METADATA_TYPE__UNKNOWN_DEX_METADATA; + return ArtStatsLog. + ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_UNKNOWN; } } catch (IOException ignore) { Slog.e(TAG, "Error when parsing dex metadata " + dexMetadataPath); - return ArtStatsLog.ART_DATUM_REPORTED__DEX_METADATA_TYPE__ERROR_DEX_METADATA; + return ArtStatsLog.ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_ERROR; } finally { try { if (jarFile != null) { diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java index 349561d3f1d1..37f317557aeb 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -986,7 +986,7 @@ public class DexManager { * Fetches the battery manager object and caches it if it hasn't been fetched already. */ private BatteryManager getBatteryManager() { - if (mBatteryManager == null) { + if (mBatteryManager == null && mContext != null) { mBatteryManager = mContext.getSystemService(BatteryManager.class); } @@ -1008,10 +1008,6 @@ public class DexManager { && mPowerManager.getCurrentThermalStatus() >= PowerManager.THERMAL_STATUS_SEVERE); - if (DEBUG) { - Log.d(TAG, "Battery, thermal, or memory are critical: " + isBtmCritical); - } - return isBtmCritical; } diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java index b61fd8d633f6..39ed4882c69c 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java @@ -21,7 +21,6 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.Signature; -import android.content.pm.verify.domain.DomainVerificationManager; import android.content.pm.verify.domain.DomainVerificationState; import android.os.UserHandle; import android.util.ArrayMap; @@ -151,7 +150,7 @@ public class DomainVerificationDebug { Integer state = reusedMap.valueAt(stateIndex); writer.print(domain); writer.print(": "); - writer.println(DomainVerificationManager.stateToDebugString(state)); + writer.println(DomainVerificationState.stateToDebugString(state)); } writer.decreaseIndent(); writer.decreaseIndent(); diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java index 1721a18f4f60..f4bcd3e65913 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java @@ -141,6 +141,12 @@ public class DomainVerificationEnforcer { "Caller is not allowed to edit other users"); } + if (!mCallback.doesUserExist(callingUserId)) { + throw new SecurityException("User " + callingUserId + " does not exist"); + } else if (!mCallback.doesUserExist(targetUserId)) { + throw new SecurityException("User " + targetUserId + " does not exist"); + } + return !mCallback.filterAppAccess(packageName, callingUid, targetUserId); } @@ -161,6 +167,12 @@ public class DomainVerificationEnforcer { Binder.getCallingPid(), callingUid, "Caller is not allowed to edit user selections"); + if (!mCallback.doesUserExist(callingUserId)) { + throw new SecurityException("User " + callingUserId + " does not exist"); + } else if (!mCallback.doesUserExist(targetUserId)) { + throw new SecurityException("User " + targetUserId + " does not exist"); + } + if (packageName == null) { return true; } @@ -184,6 +196,12 @@ public class DomainVerificationEnforcer { } } + if (!mCallback.doesUserExist(callingUserId)) { + throw new SecurityException("User " + callingUserId + " does not exist"); + } else if (!mCallback.doesUserExist(targetUserId)) { + throw new SecurityException("User " + targetUserId + " does not exist"); + } + return !mCallback.filterAppAccess(packageName, callingUid, targetUserId); } @@ -197,6 +215,12 @@ public class DomainVerificationEnforcer { "Caller is not allowed to edit other users"); } + if (!mCallback.doesUserExist(callingUserId)) { + throw new SecurityException("User " + callingUserId + " does not exist"); + } else if (!mCallback.doesUserExist(targetUserId)) { + throw new SecurityException("User " + targetUserId + " does not exist"); + } + return !mCallback.filterAppAccess(packageName, callingUid, targetUserId); } @@ -221,6 +245,12 @@ public class DomainVerificationEnforcer { mContext.enforcePermission( android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION, callingPid, callingUid, "Caller is not allowed to query user selections"); + + if (!mCallback.doesUserExist(callingUserId)) { + throw new SecurityException("User " + callingUserId + " does not exist"); + } else if (!mCallback.doesUserExist(targetUserId)) { + throw new SecurityException("User " + targetUserId + " does not exist"); + } } public interface Callback { @@ -229,5 +259,7 @@ public class DomainVerificationEnforcer { * if the package was not installed */ boolean filterAppAccess(@NonNull String packageName, int callingUid, @UserIdInt int userId); + + boolean doesUserExist(@UserIdInt int userId); } } diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java index b4bcde726173..73182732cdaf 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java @@ -333,10 +333,10 @@ public interface DomainVerificationManagerInternal { @Nullable UUID getDomainVerificationInfoId(@NonNull String packageName); + @DomainVerificationManager.Error @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) - void setDomainVerificationStatusInternal(int callingUid, @NonNull UUID domainSetId, - @NonNull Set<String> domains, int state) - throws IllegalArgumentException, NameNotFoundException; + int setDomainVerificationStatusInternal(int callingUid, @NonNull UUID domainSetId, + @NonNull Set<String> domains, int state) throws NameNotFoundException; interface Connection extends DomainVerificationEnforcer.Callback { diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java index a7a52e0cd10c..2a17c6d4cec5 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java @@ -24,7 +24,6 @@ import android.content.pm.verify.domain.DomainOwner; import android.content.pm.verify.domain.DomainSet; import android.content.pm.verify.domain.DomainVerificationInfo; import android.content.pm.verify.domain.DomainVerificationManager; -import android.content.pm.verify.domain.DomainVerificationManager.InvalidDomainSetException; import android.content.pm.verify.domain.DomainVerificationUserState; import android.content.pm.verify.domain.IDomainVerificationManager; import android.os.ServiceSpecificException; @@ -61,11 +60,12 @@ public class DomainVerificationManagerStub extends IDomainVerificationManager.St } } + @DomainVerificationManager.Error @Override - public void setDomainVerificationStatus(String domainSetId, @NonNull DomainSet domainSet, + public int setDomainVerificationStatus(String domainSetId, @NonNull DomainSet domainSet, int state) { try { - mService.setDomainVerificationStatus(UUID.fromString(domainSetId), + return mService.setDomainVerificationStatus(UUID.fromString(domainSetId), domainSet.getDomains(), state); } catch (Exception e) { throw rethrow(e); @@ -82,11 +82,12 @@ public class DomainVerificationManagerStub extends IDomainVerificationManager.St } } + @DomainVerificationManager.Error @Override - public void setDomainVerificationUserSelection(String domainSetId, @NonNull DomainSet domainSet, + public int setDomainVerificationUserSelection(String domainSetId, @NonNull DomainSet domainSet, boolean enabled, @UserIdInt int userId) { try { - mService.setDomainVerificationUserSelection(UUID.fromString(domainSetId), + return mService.setDomainVerificationUserSelection(UUID.fromString(domainSetId), domainSet.getDomains(), enabled, userId); } catch (Exception e) { throw rethrow(e); @@ -116,14 +117,9 @@ public class DomainVerificationManagerStub extends IDomainVerificationManager.St } private RuntimeException rethrow(Exception exception) throws RuntimeException { - if (exception instanceof InvalidDomainSetException) { - int packedErrorCode = DomainVerificationManager.ERROR_INVALID_DOMAIN_SET; - packedErrorCode |= ((InvalidDomainSetException) exception).getReason() << 16; - return new ServiceSpecificException(packedErrorCode, - ((InvalidDomainSetException) exception).getPackageName()); - } else if (exception instanceof NameNotFoundException) { + if (exception instanceof NameNotFoundException) { return new ServiceSpecificException( - DomainVerificationManager.ERROR_NAME_NOT_FOUND); + DomainVerificationManager.INTERNAL_ERROR_NAME_NOT_FOUND); } else if (exception instanceof RuntimeException) { return (RuntimeException) exception; } else { diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java index e85bbe41f747..a0e252a8a28a 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java @@ -19,6 +19,7 @@ package com.android.server.pm.verify.domain; import static java.util.Collections.emptyList; import static java.util.Collections.emptySet; +import android.annotation.CheckResult; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -34,7 +35,6 @@ import android.content.pm.parsing.component.ParsedActivity; import android.content.pm.verify.domain.DomainOwner; import android.content.pm.verify.domain.DomainVerificationInfo; import android.content.pm.verify.domain.DomainVerificationManager; -import android.content.pm.verify.domain.DomainVerificationManager.InvalidDomainSetException; import android.content.pm.verify.domain.DomainVerificationState; import android.content.pm.verify.domain.DomainVerificationUserState; import android.content.pm.verify.domain.IDomainVerificationManager; @@ -243,17 +243,17 @@ public class DomainVerificationService extends SystemService throws NameNotFoundException { mEnforcer.assertApprovedQuerent(mConnection.getCallingUid(), mProxy); synchronized (mLock) { - DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName); - if (pkgState == null) { - return null; - } - AndroidPackage pkg = mConnection.getPackageLocked(packageName); if (pkg == null) { throw DomainVerificationUtils.throwPackageUnavailable(packageName); } - Map<String, Integer> hostToStateMap = new ArrayMap<>(pkgState.getStateMap()); + DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName); + if (pkgState == null) { + return null; + } + + ArrayMap<String, Integer> hostToStateMap = new ArrayMap<>(pkgState.getStateMap()); // TODO(b/159952358): Should the domain list be cached? ArraySet<String> domains = mCollector.collectValidAutoVerifyDomains(pkg); @@ -267,44 +267,56 @@ public class DomainVerificationService extends SystemService DomainVerificationState.STATE_NO_RESPONSE); } + final int mapSize = hostToStateMap.size(); + for (int index = 0; index < mapSize; index++) { + int internalValue = hostToStateMap.valueAt(index); + int publicValue = DomainVerificationState.convertToInfoState(internalValue); + hostToStateMap.setValueAt(index, publicValue); + } + // TODO(b/159952358): Do not return if no values are editable (all ignored states)? return new DomainVerificationInfo(pkgState.getId(), packageName, hostToStateMap); } } - public void setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains, - int state) throws InvalidDomainSetException, NameNotFoundException { + @DomainVerificationManager.Error + public int setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains, + int state) throws NameNotFoundException { if (state < DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED) { if (state != DomainVerificationState.STATE_SUCCESS) { - throw new IllegalArgumentException( - "Verifier can only set STATE_SUCCESS or codes greater than or equal to " - + "STATE_FIRST_VERIFIER_DEFINED"); + return DomainVerificationManager.ERROR_INVALID_STATE_CODE; } } - setDomainVerificationStatusInternal(mConnection.getCallingUid(), domainSetId, domains, - state); + return setDomainVerificationStatusInternal(mConnection.getCallingUid(), domainSetId, + domains, state); } + @DomainVerificationManager.Error @Override - public void setDomainVerificationStatusInternal(int callingUid, @NonNull UUID domainSetId, + public int setDomainVerificationStatusInternal(int callingUid, @NonNull UUID domainSetId, @NonNull Set<String> domains, int state) - throws InvalidDomainSetException, NameNotFoundException { + throws NameNotFoundException { mEnforcer.assertApprovedVerifier(callingUid, mProxy); synchronized (mLock) { List<String> verifiedDomains = new ArrayList<>(); - DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains, + GetAttachedResult result = getAndValidateAttachedLocked(domainSetId, domains, true /* forAutoVerify */, callingUid, null /* userId */); + if (result.isError()) { + return result.getErrorCode(); + } + + DomainVerificationPkgState pkgState = result.getPkgState(); ArrayMap<String, Integer> stateMap = pkgState.getStateMap(); for (String domain : domains) { Integer previousState = stateMap.get(domain); if (previousState != null - && !DomainVerificationManager.isStateModifiable(previousState)) { + && !DomainVerificationState.isModifiable(previousState)) { continue; } - if (DomainVerificationManager.isStateVerified(state)) { + if (DomainVerificationState.isVerified(state)) { verifiedDomains.add(domain); } @@ -318,6 +330,7 @@ public class DomainVerificationService extends SystemService } mConnection.scheduleWriteSettings(); + return DomainVerificationManager.STATUS_OK; } @Override @@ -460,17 +473,24 @@ public class DomainVerificationService extends SystemService throw DomainVerificationUtils.throwPackageUnavailable(packageName); } - pkgState.getOrCreateUserState(userId) - .setLinkHandlingAllowed(allowed); + if (userId == UserHandle.USER_ALL) { + for (int aUserId : mConnection.getAllUserIds()) { + pkgState.getOrCreateUserState(aUserId) + .setLinkHandlingAllowed(allowed); + } + } else { + pkgState.getOrCreateUserState(userId) + .setLinkHandlingAllowed(allowed); + } } } mConnection.scheduleWriteSettings(); } - public void setDomainVerificationUserSelection(@NonNull UUID domainSetId, + public int setDomainVerificationUserSelection(@NonNull UUID domainSetId, @NonNull Set<String> domains, boolean enabled, @UserIdInt int userId) - throws InvalidDomainSetException, NameNotFoundException { + throws NameNotFoundException { synchronized (mLock) { final int callingUid = mConnection.getCallingUid(); // Pass null for package name here and do the app visibility enforcement inside @@ -478,14 +498,17 @@ public class DomainVerificationService extends SystemService // ID reason if the target app is invisible if (!mEnforcer.assertApprovedUserSelector(callingUid, mConnection.getCallingUserId(), null /* packageName */, userId)) { - throw new InvalidDomainSetException(domainSetId, null, - InvalidDomainSetException.REASON_ID_INVALID); + return DomainVerificationManager.ERROR_DOMAIN_SET_ID_INVALID; } - DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains, + GetAttachedResult result = getAndValidateAttachedLocked(domainSetId, domains, false /* forAutoVerify */, callingUid, userId); - DomainVerificationInternalUserState userState = - pkgState.getOrCreateUserState(userId); + if (result.isError()) { + return result.getErrorCode(); + } + + DomainVerificationPkgState pkgState = result.getPkgState(); + DomainVerificationInternalUserState userState = pkgState.getOrCreateUserState(userId); // Disable other packages if approving this one. Note that this check is only done for // enabling. This allows an escape hatch in case multiple packages somehow get selected. @@ -503,8 +526,7 @@ public class DomainVerificationService extends SystemService userId, APPROVAL_LEVEL_NONE + 1, mConnection::getPackageSettingLocked); int highestApproval = packagesToLevel.second; if (highestApproval > APPROVAL_LEVEL_SELECTION) { - throw new InvalidDomainSetException(domainSetId, null, - InvalidDomainSetException.REASON_UNABLE_TO_APPROVE); + return DomainVerificationManager.ERROR_UNABLE_TO_APPROVE; } domainToApprovedPackages.put(domain, packagesToLevel.first); @@ -544,6 +566,7 @@ public class DomainVerificationService extends SystemService } mConnection.scheduleWriteSettings(); + return DomainVerificationManager.STATUS_OK; } @Override @@ -636,16 +659,16 @@ public class DomainVerificationService extends SystemService throw DomainVerificationUtils.throwPackageUnavailable(packageName); } synchronized (mLock) { - DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName); - if (pkgState == null) { - return null; - } - AndroidPackage pkg = mConnection.getPackageLocked(packageName); if (pkg == null) { throw DomainVerificationUtils.throwPackageUnavailable(packageName); } + DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName); + if (pkgState == null) { + return null; + } + ArraySet<String> webDomains = mCollector.collectAllWebDomains(pkg); int webDomainsSize = webDomains.size(); @@ -659,7 +682,7 @@ public class DomainVerificationService extends SystemService Integer state = stateMap.get(host); int domainState; - if (state != null && DomainVerificationManager.isStateVerified(state)) { + if (state != null && DomainVerificationState.isVerified(state)) { domainState = DomainVerificationUserState.DOMAIN_STATE_VERIFIED; } else if (enabledHosts.contains(host)) { domainState = DomainVerificationUserState.DOMAIN_STATE_SELECTED; @@ -793,19 +816,12 @@ public class DomainVerificationService extends SystemService Integer oldStateInteger = oldStateMap.get(domain); if (oldStateInteger != null) { int oldState = oldStateInteger; - switch (oldState) { - case DomainVerificationState.STATE_SUCCESS: - case DomainVerificationState.STATE_RESTORED: - case DomainVerificationState.STATE_MIGRATED: - newStateMap.put(domain, oldState); - break; - default: - // In all other cases, the state code is left unset - // (STATE_NO_RESPONSE) to signal to the verification agent that any - // existing error has been cleared and the domain should be - // re-attempted. This makes update of a package a signal to - // re-verify. - break; + // If the following case fails, the state code is left unset + // (STATE_NO_RESPONSE) to signal to the verification agent that any existing + // error has been cleared and the domain should be re-attempted. This makes + // update of a package a signal to re-verify. + if (DomainVerificationState.shouldMigrate(oldState)) { + newStateMap.put(domain, oldState); } } } @@ -858,13 +874,13 @@ public class DomainVerificationService extends SystemService boolean sendBroadcast = true; DomainVerificationPkgState pkgState; - pkgState = mSettings.getPendingState(pkgName); + pkgState = mSettings.removePendingState(pkgName); if (pkgState != null) { // Don't send when attaching from pending read, which is usually boot scan. Re-send on // boot is handled in a separate method once all packages are added. sendBroadcast = false; } else { - pkgState = mSettings.getRestoredState(pkgName); + pkgState = mSettings.removeRestoredState(pkgName); } AndroidPackage pkg = newPkgSetting.getPkg(); @@ -872,7 +888,7 @@ public class DomainVerificationService extends SystemService boolean hasAutoVerifyDomains = !domains.isEmpty(); boolean isPendingOrRestored = pkgState != null; if (isPendingOrRestored) { - pkgState.setId(domainSetId); + pkgState = new DomainVerificationPkgState(pkgState, domainSetId, hasAutoVerifyDomains); } else { pkgState = new DomainVerificationPkgState(pkgName, domainSetId, hasAutoVerifyDomains); } @@ -1097,28 +1113,25 @@ public class DomainVerificationService extends SystemService * @param userIdForFilter which user to filter app access to, or null if the caller has already * validated package visibility */ + @CheckResult @GuardedBy("mLock") - private DomainVerificationPkgState getAndValidateAttachedLocked(@NonNull UUID domainSetId, + private GetAttachedResult getAndValidateAttachedLocked(@NonNull UUID domainSetId, @NonNull Set<String> domains, boolean forAutoVerify, int callingUid, - @Nullable Integer userIdForFilter) - throws InvalidDomainSetException, NameNotFoundException { + @Nullable Integer userIdForFilter) throws NameNotFoundException { if (domainSetId == null) { - throw new InvalidDomainSetException(null, null, - InvalidDomainSetException.REASON_ID_NULL); + return GetAttachedResult.error(DomainVerificationManager.ERROR_DOMAIN_SET_ID_NULL); } DomainVerificationPkgState pkgState = mAttachedPkgStates.get(domainSetId); if (pkgState == null) { - throw new InvalidDomainSetException(domainSetId, null, - InvalidDomainSetException.REASON_ID_INVALID); + return GetAttachedResult.error(DomainVerificationManager.ERROR_DOMAIN_SET_ID_INVALID); } String pkgName = pkgState.getPackageName(); if (userIdForFilter != null && mConnection.filterAppAccess(pkgName, callingUid, userIdForFilter)) { - throw new InvalidDomainSetException(domainSetId, null, - InvalidDomainSetException.REASON_ID_INVALID); + return GetAttachedResult.error(DomainVerificationManager.ERROR_DOMAIN_SET_ID_INVALID); } PackageSetting pkgSetting = mConnection.getPackageSettingLocked(pkgName); @@ -1127,8 +1140,8 @@ public class DomainVerificationService extends SystemService } if (CollectionUtils.isEmpty(domains)) { - throw new InvalidDomainSetException(domainSetId, pkgState.getPackageName(), - InvalidDomainSetException.REASON_SET_NULL_OR_EMPTY); + return GetAttachedResult.error( + DomainVerificationManager.ERROR_DOMAIN_SET_NULL_OR_EMPTY); } AndroidPackage pkg = pkgSetting.getPkg(); ArraySet<String> declaredDomains = forAutoVerify @@ -1136,11 +1149,10 @@ public class DomainVerificationService extends SystemService : mCollector.collectAllWebDomains(pkg); if (domains.retainAll(declaredDomains)) { - throw new InvalidDomainSetException(domainSetId, pkgState.getPackageName(), - InvalidDomainSetException.REASON_UNKNOWN_DOMAIN); + return GetAttachedResult.error(DomainVerificationManager.ERROR_UNKNOWN_DOMAIN); } - return pkgState; + return GetAttachedResult.success(pkgState); } @Override @@ -1185,7 +1197,7 @@ public class DomainVerificationService extends SystemService /** * Determine whether or not a broadcast should be sent at boot for the given {@param pkgState}. * Sends only if the only states recorded are default as decided by {@link - * DomainVerificationManager#isStateDefault(int)}. + * DomainVerificationState#isDefault(int)}. * * If any other state is set, it's assumed that the domain verification agent is aware of the * package and has already scheduled future verification requests. @@ -1199,7 +1211,7 @@ public class DomainVerificationService extends SystemService int statesSize = stateMap.size(); for (int stateIndex = 0; stateIndex < statesSize; stateIndex++) { Integer state = stateMap.valueAt(stateIndex); - if (!DomainVerificationManager.isStateDefault(state)) { + if (!DomainVerificationState.isDefault(state)) { return false; } } @@ -1318,83 +1330,50 @@ public class DomainVerificationService extends SystemService @NonNull Function<String, PackageSetting> pkgSettingFunction) { String domain = intent.getData().getHost(); - // Collect package names - ArrayMap<String, Integer> packageApprovals = new ArrayMap<>(); + // Collect valid infos + ArrayMap<ResolveInfo, Integer> infoApprovals = new ArrayMap<>(); int infosSize = infos.size(); for (int index = 0; index < infosSize; index++) { - packageApprovals.put(infos.get(index).getComponentInfo().packageName, - APPROVAL_LEVEL_NONE); + final ResolveInfo info = infos.get(index); + // Only collect for intent filters that can auto resolve + if (info.isAutoResolutionAllowed()) { + infoApprovals.put(info, null); + } } // Find all approval levels - int highestApproval = fillMapWithApprovalLevels(packageApprovals, domain, userId, + int highestApproval = fillMapWithApprovalLevels(infoApprovals, domain, userId, pkgSettingFunction); if (highestApproval == APPROVAL_LEVEL_NONE) { return Pair.create(emptyList(), highestApproval); } - // Filter to highest, non-zero packages - ArraySet<String> approvedPackages = new ArraySet<>(); - int approvalsSize = packageApprovals.size(); - for (int index = 0; index < approvalsSize; index++) { - if (packageApprovals.valueAt(index) == highestApproval) { - approvedPackages.add(packageApprovals.keyAt(index)); + // Filter to highest, non-zero infos + for (int index = infoApprovals.size() - 1; index >= 0; index--) { + if (infoApprovals.valueAt(index) != highestApproval) { + infoApprovals.removeAt(index); } } - ArraySet<String> filteredPackages = new ArraySet<>(); - if (highestApproval == APPROVAL_LEVEL_LEGACY_ASK) { + if (highestApproval != APPROVAL_LEVEL_LEGACY_ASK) { // To maintain legacy behavior while the Settings API is not implemented, // show the chooser if all approved apps are marked ask, skipping the // last app, last declaration filtering. - filteredPackages.addAll(approvedPackages); - } else { - // Filter to last installed package - long latestInstall = Long.MIN_VALUE; - int approvedSize = approvedPackages.size(); - for (int index = 0; index < approvedSize; index++) { - String packageName = approvedPackages.valueAt(index); - PackageSetting pkgSetting = pkgSettingFunction.apply(packageName); - if (pkgSetting == null) { - continue; - } - long installTime = pkgSetting.getFirstInstallTime(); - if (installTime > latestInstall) { - latestInstall = installTime; - filteredPackages.clear(); - filteredPackages.add(packageName); - } else if (installTime == latestInstall) { - filteredPackages.add(packageName); - } - } + filterToLastFirstInstalled(infoApprovals, pkgSettingFunction); } - // Filter to approved ResolveInfos - ArrayMap<String, List<ResolveInfo>> approvedInfos = new ArrayMap<>(); - for (int index = 0; index < infosSize; index++) { - ResolveInfo info = infos.get(index); - String packageName = info.getComponentInfo().packageName; - if (filteredPackages.contains(packageName)) { - List<ResolveInfo> infosPerPackage = approvedInfos.get(packageName); - if (infosPerPackage == null) { - infosPerPackage = new ArrayList<>(); - approvedInfos.put(packageName, infosPerPackage); - } - infosPerPackage.add(info); - } + // Easier to transform into list as the filterToLastDeclared method + // requires swapping indexes, which doesn't work with ArrayMap keys + final int size = infoApprovals.size(); + List<ResolveInfo> finalList = new ArrayList<>(size); + for (int index = 0; index < size; index++) { + finalList.add(infoApprovals.keyAt(index)); } - List<ResolveInfo> finalList; - if (highestApproval == APPROVAL_LEVEL_LEGACY_ASK) { - // If legacy ask, skip the last declaration filtering - finalList = new ArrayList<>(); - int size = approvedInfos.size(); - for (int index = 0; index < size; index++) { - finalList.addAll(approvedInfos.valueAt(index)); - } - } else { + // If legacy ask, skip the last declaration filtering + if (highestApproval != APPROVAL_LEVEL_LEGACY_ASK) { // Find the last declared ResolveInfo per package - finalList = filterToLastDeclared(approvedInfos, pkgSettingFunction); + filterToLastDeclared(finalList, pkgSettingFunction); } return Pair.create(finalList, highestApproval); @@ -1403,68 +1382,127 @@ public class DomainVerificationService extends SystemService /** * @return highest approval level found */ - private int fillMapWithApprovalLevels(@NonNull ArrayMap<String, Integer> inputMap, + @ApprovalLevel + private int fillMapWithApprovalLevels(@NonNull ArrayMap<ResolveInfo, Integer> inputMap, @NonNull String domain, @UserIdInt int userId, @NonNull Function<String, PackageSetting> pkgSettingFunction) { int highestApproval = APPROVAL_LEVEL_NONE; int size = inputMap.size(); for (int index = 0; index < size; index++) { - String packageName = inputMap.keyAt(index); + if (inputMap.valueAt(index) != null) { + // Already filled by previous iteration + continue; + } + + ResolveInfo info = inputMap.keyAt(index); + final String packageName = info.getComponentInfo().packageName; PackageSetting pkgSetting = pkgSettingFunction.apply(packageName); if (pkgSetting == null) { - inputMap.setValueAt(index, APPROVAL_LEVEL_NONE); + fillInfoMapForSamePackage(inputMap, packageName, APPROVAL_LEVEL_NONE); continue; } int approval = approvalLevelForDomain(pkgSetting, domain, userId, domain); highestApproval = Math.max(highestApproval, approval); - inputMap.setValueAt(index, approval); + fillInfoMapForSamePackage(inputMap, packageName, approval); } return highestApproval; } + private void fillInfoMapForSamePackage(@NonNull ArrayMap<ResolveInfo, Integer> inputMap, + @NonNull String targetPackageName, @ApprovalLevel int level) { + final int size = inputMap.size(); + for (int index = 0; index < size; index++) { + final String packageName = inputMap.keyAt(index).getComponentInfo().packageName; + if (Objects.equals(targetPackageName, packageName)) { + inputMap.setValueAt(index, level); + } + } + } + @NonNull - private List<ResolveInfo> filterToLastDeclared( - @NonNull ArrayMap<String, List<ResolveInfo>> inputMap, + private void filterToLastFirstInstalled(@NonNull ArrayMap<ResolveInfo, Integer> inputMap, @NonNull Function<String, PackageSetting> pkgSettingFunction) { - List<ResolveInfo> finalList = new ArrayList<>(inputMap.size()); - - int inputSize = inputMap.size(); - for (int inputIndex = 0; inputIndex < inputSize; inputIndex++) { - String packageName = inputMap.keyAt(inputIndex); - List<ResolveInfo> infos = inputMap.valueAt(inputIndex); + // First, find the package with the latest first install time + String targetPackageName = null; + long latestInstall = Long.MIN_VALUE; + final int size = inputMap.size(); + for (int index = 0; index < size; index++) { + ResolveInfo info = inputMap.keyAt(index); + String packageName = info.getComponentInfo().packageName; PackageSetting pkgSetting = pkgSettingFunction.apply(packageName); + if (pkgSetting == null) { + continue; + } + + long installTime = pkgSetting.getFirstInstallTime(); + if (installTime > latestInstall) { + latestInstall = installTime; + targetPackageName = packageName; + } + } + + // Then, remove all infos that don't match the package + for (int index = inputMap.size() - 1; index >= 0; index--) { + ResolveInfo info = inputMap.keyAt(index); + if (!Objects.equals(targetPackageName, info.getComponentInfo().packageName)) { + inputMap.removeAt(index); + } + } + } + + @NonNull + private void filterToLastDeclared(@NonNull List<ResolveInfo> inputList, + @NonNull Function<String, PackageSetting> pkgSettingFunction) { + // Must call size each time as the size of the list will decrease + for (int index = 0; index < inputList.size(); index++) { + ResolveInfo info = inputList.get(index); + String targetPackageName = info.getComponentInfo().packageName; + PackageSetting pkgSetting = pkgSettingFunction.apply(targetPackageName); AndroidPackage pkg = pkgSetting == null ? null : pkgSetting.getPkg(); if (pkg == null) { continue; } - ResolveInfo result = null; - int highestIndex = -1; - int infosSize = infos.size(); - for (int infoIndex = 0; infoIndex < infosSize; infoIndex++) { - ResolveInfo info = infos.get(infoIndex); - List<ParsedActivity> activities = pkg.getActivities(); - int activitiesSize = activities.size(); - for (int activityIndex = 0; activityIndex < activitiesSize; activityIndex++) { - if (Objects.equals(activities.get(activityIndex).getComponentName(), - info.getComponentInfo().getComponentName())) { - if (activityIndex > highestIndex) { - highestIndex = activityIndex; - result = info; - } - break; - } + ResolveInfo result = info; + int highestIndex = indexOfIntentFilterEntry(pkg, result); + + // Search backwards so that lower results can be removed as they're found + for (int searchIndex = inputList.size() - 1; searchIndex >= index + 1; searchIndex--) { + ResolveInfo searchInfo = inputList.get(searchIndex); + if (!Objects.equals(targetPackageName, searchInfo.getComponentInfo().packageName)) { + continue; } + + int entryIndex = indexOfIntentFilterEntry(pkg, searchInfo); + if (entryIndex > highestIndex) { + highestIndex = entryIndex; + result = searchInfo; + } + + // Always remove the entry so that the current index + // is left as the sole candidate of the target package + inputList.remove(searchIndex); } - // Shouldn't be null, but might as well be safe - if (result != null) { - finalList.add(result); + // Swap the current index for the result, leaving this as + // the only entry with the target package name + inputList.set(index, result); + } + } + + private int indexOfIntentFilterEntry(@NonNull AndroidPackage pkg, + @NonNull ResolveInfo target) { + List<ParsedActivity> activities = pkg.getActivities(); + int activitiesSize = activities.size(); + for (int activityIndex = 0; activityIndex < activitiesSize; activityIndex++) { + if (Objects.equals(activities.get(activityIndex).getComponentName(), + target.getComponentInfo().getComponentName())) { + return activityIndex; } } - return finalList; + return -1; } @Override @@ -1472,8 +1510,7 @@ public class DomainVerificationService extends SystemService @NonNull List<ResolveInfo> candidates, @PackageManager.ResolveInfoFlags int resolveInfoFlags, @UserIdInt int userId) { String packageName = pkgSetting.getName(); - if (!DomainVerificationUtils.isDomainVerificationIntent(intent, candidates, - resolveInfoFlags)) { + if (!DomainVerificationUtils.isDomainVerificationIntent(intent, resolveInfoFlags)) { if (DEBUG_APPROVAL) { debugApproval(packageName, intent, userId, false, "not valid intent"); } @@ -1542,7 +1579,7 @@ public class DomainVerificationService extends SystemService ArrayMap<String, Integer> stateMap = pkgState.getStateMap(); // Check if the exact host matches Integer state = stateMap.get(host); - if (state != null && DomainVerificationManager.isStateVerified(state)) { + if (state != null && DomainVerificationState.isVerified(state)) { if (DEBUG_APPROVAL) { debugApproval(packageName, debugObject, userId, true, "host verified exactly"); @@ -1553,7 +1590,7 @@ public class DomainVerificationService extends SystemService // Otherwise see if the host matches a verified domain by wildcard int stateMapSize = stateMap.size(); for (int index = 0; index < stateMapSize; index++) { - if (!DomainVerificationManager.isStateVerified(stateMap.valueAt(index))) { + if (!DomainVerificationState.isVerified(stateMap.valueAt(index))) { continue; } @@ -1672,4 +1709,40 @@ public class DomainVerificationService extends SystemService Slog.d(TAG + "Approval", packageName + " was " + approvalString + " for " + debugObject + " for user " + userId + ": " + reason); } + + private static class GetAttachedResult { + + @Nullable + private DomainVerificationPkgState mPkgState; + + private int mErrorCode; + + GetAttachedResult(@Nullable DomainVerificationPkgState pkgState, int errorCode) { + mPkgState = pkgState; + mErrorCode = errorCode; + } + + @NonNull + static GetAttachedResult error(@DomainVerificationManager.Error int errorCode) { + return new GetAttachedResult(null, errorCode); + } + + @NonNull + static GetAttachedResult success(@NonNull DomainVerificationPkgState pkgState) { + return new GetAttachedResult(pkgState, DomainVerificationManager.STATUS_OK); + } + + @NonNull + DomainVerificationPkgState getPkgState() { + return mPkgState; + } + + boolean isError() { + return mErrorCode != DomainVerificationManager.STATUS_OK; + } + + public int getErrorCode() { + return mErrorCode; + } + } } diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java index f3d1dbb1f6ad..8b59da7bb944 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java @@ -256,16 +256,16 @@ class DomainVerificationSettings { } @Nullable - public DomainVerificationPkgState getPendingState(@NonNull String pkgName) { + public DomainVerificationPkgState removePendingState(@NonNull String pkgName) { synchronized (mLock) { - return mPendingPkgStates.get(pkgName); + return mPendingPkgStates.remove(pkgName); } } @Nullable - public DomainVerificationPkgState getRestoredState(@NonNull String pkgName) { + public DomainVerificationPkgState removeRestoredState(@NonNull String pkgName) { synchronized (mLock) { - return mRestoredPkgStates.get(pkgName); + return mRestoredPkgStates.remove(pkgName); } } } diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java index 94767f555574..7e755fa384db 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java @@ -38,6 +38,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Locale; +import java.util.function.Function; public class DomainVerificationShell { @@ -226,22 +228,19 @@ public class DomainVerificationShell { userId = translateUserId(userId, "runSetAppLinksUserState"); - String enabledString = commandHandler.getNextArgRequired(); + String enabledArg = commandHandler.getNextArg(); + if (TextUtils.isEmpty(enabledArg)) { + commandHandler.getErrPrintWriter().println("Error: enabled param not specified"); + return false; + } - // Manually ensure that "true" and "false" are the only options, to ensure a domain isn't - // accidentally parsed as a boolean boolean enabled; - switch (enabledString) { - case "true": - enabled = true; - break; - case "false": - enabled = false; - break; - default: - commandHandler.getErrPrintWriter().println( - "Invalid enabled param: " + enabledString); - return false; + try { + enabled = parseEnabled(enabledArg); + } catch (IllegalArgumentException e) { + commandHandler.getErrPrintWriter() + .println("Error: invalid enabled param: " + e.getMessage()); + return false; } ArraySet<String> domains = new ArraySet<>(getRemainingArgs(commandHandler)); @@ -255,8 +254,8 @@ public class DomainVerificationShell { } try { - mCallback.setDomainVerificationUserSelectionInternal(userId, - packageName, enabled, domains); + mCallback.setDomainVerificationUserSelectionInternal(userId, packageName, enabled, + domains); } catch (NameNotFoundException e) { commandHandler.getErrPrintWriter().println("Package not found: " + packageName); return false; @@ -362,15 +361,12 @@ public class DomainVerificationShell { private boolean runSetAppLinksAllowed(@NonNull BasicShellCommandHandler commandHandler) { String packageName = null; Integer userId = null; - Boolean allowed = null; String option; while ((option = commandHandler.getNextOption()) != null) { if (option.equals("--package")) { - packageName = commandHandler.getNextArgRequired(); - } if (option.equals("--user")) { + packageName = commandHandler.getNextArg(); + } else if (option.equals("--user")) { userId = UserHandle.parseUserArg(commandHandler.getNextArgRequired()); - } else if (allowed == null) { - allowed = Boolean.valueOf(option); } else { commandHandler.getErrPrintWriter().println("Error: unexpected option: " + option); return false; @@ -389,11 +385,21 @@ public class DomainVerificationShell { return false; } - if (allowed == null) { + String allowedArg = commandHandler.getNextArg(); + if (TextUtils.isEmpty(allowedArg)) { commandHandler.getErrPrintWriter().println("Error: allowed setting not specified"); return false; } + boolean allowed; + try { + allowed = parseEnabled(allowedArg); + } catch (IllegalArgumentException e) { + commandHandler.getErrPrintWriter() + .println("Error: invalid allowed setting: " + e.getMessage()); + return false; + } + userId = translateUserId(userId, "runSetAppLinksAllowed"); try { @@ -422,6 +428,22 @@ public class DomainVerificationShell { } /** + * Manually ensure that "true" and "false" are the only options, to ensure a domain isn't + * accidentally parsed as a boolean. + */ + @NonNull + private boolean parseEnabled(@NonNull String arg) throws IllegalArgumentException { + switch (arg.toLowerCase(Locale.US)) { + case "true": + return true; + case "false": + return false; + default: + throw new IllegalArgumentException(arg + " is not a valid boolean"); + } + } + + /** * Separated interface from {@link DomainVerificationManagerInternal} to hide methods that are * even more internal, and so that testing is easier. */ @@ -498,7 +520,8 @@ public class DomainVerificationShell { void verifyPackages(@Nullable List<String> packageNames, boolean reVerify); /** - * @see DomainVerificationManagerInternal#printState(IndentingPrintWriter, String, Integer) + * @see DomainVerificationManagerInternal#printState(IndentingPrintWriter, String, Integer, + * Function) */ void printState(@NonNull IndentingPrintWriter writer, @Nullable String packageName, @Nullable @UserIdInt Integer userId) throws NameNotFoundException; diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java index 883bbad1bd2d..cb3b5c9db7e7 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java @@ -22,7 +22,6 @@ import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.ResolveInfo; import android.os.Binder; import com.android.internal.util.CollectionUtils; @@ -30,7 +29,6 @@ import com.android.server.compat.PlatformCompat; import com.android.server.pm.PackageManagerService; import com.android.server.pm.parsing.pkg.AndroidPackage; -import java.util.List; import java.util.Set; public final class DomainVerificationUtils { @@ -46,7 +44,6 @@ public final class DomainVerificationUtils { } public static boolean isDomainVerificationIntent(Intent intent, - @NonNull List<ResolveInfo> candidates, @PackageManager.ResolveInfoFlags int resolveInfoFlags) { if (!intent.isWebIntent()) { return false; @@ -63,42 +60,18 @@ public final class DomainVerificationUtils { && intent.hasCategory(Intent.CATEGORY_BROWSABLE); } - // In cases where at least one browser is resolved and only one non-browser is resolved, - // the Intent is coerced into an app links intent, under the assumption the browser can - // be skipped if the app is approved at any level for the domain. - boolean foundBrowser = false; - boolean foundOneApp = false; - - final int candidatesSize = candidates.size(); - for (int index = 0; index < candidatesSize; index++) { - final ResolveInfo info = candidates.get(index); - if (info.handleAllWebDataURI) { - foundBrowser = true; - } else if (foundOneApp) { - // Already true, so duplicate app - foundOneApp = false; - break; - } else { - foundOneApp = true; - } - } - boolean matchDefaultByFlags = (resolveInfoFlags & PackageManager.MATCH_DEFAULT_ONLY) != 0; - boolean onlyOneNonBrowser = foundBrowser && foundOneApp; // Check if matches (BROWSABLE || none) && DEFAULT if (categoriesSize == 0) { - // No categories, run coerce case, matching DEFAULT by flags - return onlyOneNonBrowser && matchDefaultByFlags; - } else if (intent.hasCategory(Intent.CATEGORY_DEFAULT)) { - // Run coerce case, matching by explicit DEFAULT - return onlyOneNonBrowser; + // No categories, only allow matching DEFAULT by flags + return matchDefaultByFlags; } else if (intent.hasCategory(Intent.CATEGORY_BROWSABLE)) { // Intent matches BROWSABLE, must match DEFAULT by flags return matchDefaultByFlags; } else { - // Otherwise not matching any app link categories - return false; + // Otherwise only needs to have DEFAULT + return intent.hasCategory(Intent.CATEGORY_DEFAULT); } } diff --git a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java index a089a6022735..40c70915f3ce 100644 --- a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java +++ b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java @@ -68,6 +68,12 @@ public class DomainVerificationPkgState { this(packageName, id, hasAutoVerifyDomains, new ArrayMap<>(0), new SparseArray<>(0)); } + public DomainVerificationPkgState(@NonNull DomainVerificationPkgState pkgState, + @NonNull UUID id, boolean hasAutoVerifyDomains) { + this(pkgState.getPackageName(), id, hasAutoVerifyDomains, pkgState.getStateMap(), + pkgState.getUserStates()); + } + @Nullable public DomainVerificationInternalUserState getUserState(@UserIdInt int userId) { return mUserStates.get(userId); @@ -84,10 +90,6 @@ public class DomainVerificationPkgState { return userState; } - public void setId(@NonNull UUID id) { - mId = id; - } - public void removeUser(@UserIdInt int userId) { mUserStates.remove(userId); } diff --git a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java index 18042af139a3..fa36683e4aff 100644 --- a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java +++ b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java @@ -207,20 +207,24 @@ public class DomainVerificationProxyV1 implements DomainVerificationProxy { int callingUid = response.callingUid; if (!successfulDomains.isEmpty()) { try { - mManager.setDomainVerificationStatusInternal(callingUid, domainSetId, - successfulDomains, DomainVerificationState.STATE_SUCCESS); - } catch (DomainVerificationManager.InvalidDomainSetException - | PackageManager.NameNotFoundException e) { + if (mManager.setDomainVerificationStatusInternal(callingUid, domainSetId, + successfulDomains, DomainVerificationState.STATE_SUCCESS) + != DomainVerificationManager.STATUS_OK) { + Slog.e(TAG, "Failure reporting successful domains for " + packageName); + } + } catch (Exception e) { Slog.e(TAG, "Failure reporting successful domains for " + packageName, e); } } if (!failedDomains.isEmpty()) { try { - mManager.setDomainVerificationStatusInternal(callingUid, domainSetId, - failedDomains, DomainVerificationState.STATE_LEGACY_FAILURE); - } catch (DomainVerificationManager.InvalidDomainSetException - | PackageManager.NameNotFoundException e) { + if (mManager.setDomainVerificationStatusInternal(callingUid, domainSetId, + failedDomains, DomainVerificationState.STATE_LEGACY_FAILURE) + != DomainVerificationManager.STATUS_OK) { + Slog.e(TAG, "Failure reporting failed domains for " + packageName); + } + } catch (Exception e) { Slog.e(TAG, "Failure reporting failed domains for " + packageName, e); } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index a909c6d119e8..d361a8c03fbd 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1479,7 +1479,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A private void setCornersRadius(WindowState mainWindow, int cornersRadius) { final SurfaceControl windowSurface = mainWindow.getClientViewRootSurface(); if (windowSurface != null && windowSurface.isValid()) { - Transaction transaction = getPendingTransaction(); + Transaction transaction = getSyncTransaction(); transaction.setCornerRadius(windowSurface, cornersRadius); } } @@ -1491,7 +1491,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } layoutLetterbox(winHint); if (mLetterbox != null && mLetterbox.needsApplySurfaceChanges()) { - mLetterbox.applySurfaceChanges(getPendingTransaction()); + mLetterbox.applySurfaceChanges(getSyncTransaction()); } } diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java index 5ccf576e1099..9855ea50c83d 100644 --- a/services/core/java/com/android/server/wm/DisplayArea.java +++ b/services/core/java/com/android/server/wm/DisplayArea.java @@ -451,7 +451,7 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { void sendDisplayAreaVanished(IDisplayAreaOrganizer organizer) { if (organizer == null) return; - migrateToNewSurfaceControl(); + migrateToNewSurfaceControl(getSyncTransaction()); mOrganizerController.onDisplayAreaVanished(organizer, this); } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index c78f9ec21516..95b2b5d088a0 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -3428,7 +3428,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp /** Updates the layer assignment of windows on this display. */ void assignWindowLayers(boolean setLayoutNeeded) { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "assignWindowLayers"); - assignChildLayers(getPendingTransaction()); + assignChildLayers(getSyncTransaction()); if (setLayoutNeeded) { setLayoutNeeded(); } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index ad0ce5bf4b28..09a8e4f23644 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -2382,11 +2382,11 @@ class Task extends WindowContainer<WindowContainer> { } @Override - void migrateToNewSurfaceControl() { - super.migrateToNewSurfaceControl(); + void migrateToNewSurfaceControl(SurfaceControl.Transaction t) { + super.migrateToNewSurfaceControl(t); mLastSurfaceSize.x = 0; mLastSurfaceSize.y = 0; - updateSurfaceSize(getPendingTransaction()); + updateSurfaceSize(t); } void updateSurfaceSize(SurfaceControl.Transaction transaction) { diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 498fc5c81a4c..88e9ae9179c9 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -963,6 +963,22 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { } } + @Override + void migrateToNewSurfaceControl(SurfaceControl.Transaction t) { + super.migrateToNewSurfaceControl(t); + if (mAppAnimationLayer == null) { + return; + } + + // As TaskDisplayArea is getting a new surface, reparent and reorder the child surfaces. + t.reparent(mAppAnimationLayer, mSurfaceControl); + t.reparent(mBoostedAppAnimationLayer, mSurfaceControl); + t.reparent(mHomeAppAnimationLayer, mSurfaceControl); + t.reparent(mSplitScreenDividerAnchor, mSurfaceControl); + reassignLayer(t); + scheduleAnimation(); + } + void onRootTaskRemoved(Task rootTask) { if (ActivityTaskManagerDebugConfig.DEBUG_ROOT_TASK) { Slog.v(TAG_ROOT_TASK, "onRootTaskRemoved: detaching " + rootTask + " from displayId=" diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index 5d22f8fde057..375b3f49be13 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -310,7 +310,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { boolean taskAppearedSent = t.mTaskAppearedSent; if (taskAppearedSent) { if (t.getSurfaceControl() != null) { - t.migrateToNewSurfaceControl(); + t.migrateToNewSurfaceControl(t.getSyncTransaction()); } t.mTaskAppearedSent = false; } diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 8d859584d5f5..e3679c0d8096 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -389,12 +389,12 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< mParent.onChildAdded(this); } if (!mReparenting) { + onSyncReparent(oldParent, mParent); if (mParent != null && mParent.mDisplayContent != null && mDisplayContent != mParent.mDisplayContent) { onDisplayChanged(mParent.mDisplayContent); } onParentChanged(mParent, oldParent); - onSyncReparent(oldParent, mParent); } } @@ -460,8 +460,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< * This is used to revoke control of the SurfaceControl from a client process that was * previously organizing this WindowContainer. */ - void migrateToNewSurfaceControl() { - SurfaceControl.Transaction t = getPendingTransaction(); + void migrateToNewSurfaceControl(SurfaceControl.Transaction t) { t.remove(mSurfaceControl); // Clear the last position so the new SurfaceControl will get correct position mLastSurfacePosition.set(0, 0); diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp index e3fbeddc3a5f..db70d44d37f6 100644 --- a/services/incremental/IncrementalService.cpp +++ b/services/incremental/IncrementalService.cpp @@ -96,6 +96,10 @@ static const Constants& constants() { return c; } +static bool isPageAligned(IncFsSize s) { + return (s & (Constants::blockSize - 1)) == 0; +} + template <base::LogSeverity level = base::ERROR> bool mkdirOrLog(std::string_view name, int mode = 0770, bool allowExisting = true) { auto cstr = path::c_str(name); @@ -1001,25 +1005,53 @@ std::string IncrementalService::normalizePathToStorage(const IncFsMount& ifs, St int IncrementalService::makeFile(StorageId storage, std::string_view path, int mode, FileId id, incfs::NewFileParams params, std::span<const uint8_t> data) { - if (auto ifs = getIfs(storage)) { - std::string normPath = normalizePathToStorage(*ifs, storage, path); - if (normPath.empty()) { - LOG(ERROR) << "Internal error: storageId " << storage - << " failed to normalize: " << path; + const auto ifs = getIfs(storage); + if (!ifs) { + return -EINVAL; + } + if (data.size() > params.size) { + LOG(ERROR) << "Bad data size - bigger than file size"; + return -EINVAL; + } + if (!data.empty() && data.size() != params.size) { + // Writing a page is an irreversible operation, and it can't be updated with additional + // data later. Check that the last written page is complete, or we may break the file. + if (!isPageAligned(data.size())) { + LOG(ERROR) << "Bad data size - tried to write half a page?"; return -EINVAL; } - if (auto err = mIncFs->makeFile(ifs->control, normPath, mode, id, params); err) { - LOG(ERROR) << "Internal error: storageId " << storage << " failed to makeFile: " << err; - return err; + } + const std::string normPath = normalizePathToStorage(*ifs, storage, path); + if (normPath.empty()) { + LOG(ERROR) << "Internal error: storageId " << storage << " failed to normalize: " << path; + return -EINVAL; + } + if (auto err = mIncFs->makeFile(ifs->control, normPath, mode, id, params); err) { + LOG(ERROR) << "Internal error: storageId " << storage << " failed to makeFile: " << err; + return err; + } + if (params.size > 0) { + // Only v2+ incfs supports automatically trimming file over-reserved sizes + if (mIncFs->features() & incfs::Features::v2) { + if (auto err = mIncFs->reserveSpace(ifs->control, normPath, params.size)) { + if (err != -EOPNOTSUPP) { + LOG(ERROR) << "Failed to reserve space for a new file: " << err; + (void)mIncFs->unlink(ifs->control, normPath); + return err; + } else { + LOG(WARNING) << "Reserving space for backing file isn't supported, " + "may run out of disk later"; + } + } } if (!data.empty()) { if (auto err = setFileContent(ifs, id, path, data); err) { + (void)mIncFs->unlink(ifs->control, normPath); return err; } } - return 0; } - return -EINVAL; + return 0; } int IncrementalService::makeDir(StorageId storageId, std::string_view path, int mode) { @@ -1708,7 +1740,7 @@ bool IncrementalService::configureNativeBinaries(StorageId storage, std::string_ } const auto entryUncompressed = entry.method == kCompressStored; - const auto entryPageAligned = (entry.offset & (constants().blockSize - 1)) == 0; + const auto entryPageAligned = isPageAligned(entry.offset); if (!extractNativeLibs) { // ensure the file is properly aligned and unpacked diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp index 80f409ff1c61..2a061226b713 100644 --- a/services/incremental/ServiceWrappers.cpp +++ b/services/incremental/ServiceWrappers.cpp @@ -209,6 +209,10 @@ public: ErrorCode writeBlocks(std::span<const incfs::DataBlock> blocks) const final { return incfs::writeBlocks({blocks.data(), size_t(blocks.size())}); } + ErrorCode reserveSpace(const Control& control, std::string_view path, + IncFsSize size) const final { + return incfs::reserveSpace(control, path, size); + } WaitResult waitForPendingReads(const Control& control, std::chrono::milliseconds timeout, std::vector<incfs::ReadInfo>* pendingReadsBuffer) const final { return incfs::waitForPendingReads(control, timeout, pendingReadsBuffer); diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h index d113f992de71..231b76ff1701 100644 --- a/services/incremental/ServiceWrappers.h +++ b/services/incremental/ServiceWrappers.h @@ -107,6 +107,8 @@ public: virtual ErrorCode unlink(const Control& control, std::string_view path) const = 0; virtual UniqueFd openForSpecialOps(const Control& control, FileId id) const = 0; virtual ErrorCode writeBlocks(std::span<const incfs::DataBlock> blocks) const = 0; + virtual ErrorCode reserveSpace(const Control& control, std::string_view path, + IncFsSize size) const = 0; virtual WaitResult waitForPendingReads( const Control& control, std::chrono::milliseconds timeout, std::vector<incfs::ReadInfo>* pendingReadsBuffer) const = 0; diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp index bf798273a8a9..45b796bf4704 100644 --- a/services/incremental/test/IncrementalServiceTest.cpp +++ b/services/incremental/test/IncrementalServiceTest.cpp @@ -372,6 +372,8 @@ public: MOCK_CONST_METHOD2(unlink, ErrorCode(const Control& control, std::string_view path)); MOCK_CONST_METHOD2(openForSpecialOps, UniqueFd(const Control& control, FileId id)); MOCK_CONST_METHOD1(writeBlocks, ErrorCode(std::span<const DataBlock> blocks)); + MOCK_CONST_METHOD3(reserveSpace, + ErrorCode(const Control& control, std::string_view path, IncFsSize size)); MOCK_CONST_METHOD3(waitForPendingReads, WaitResult(const Control& control, std::chrono::milliseconds timeout, std::vector<incfs::ReadInfo>* pendingReadsBuffer)); @@ -379,7 +381,10 @@ public: ErrorCode(const Control& control, const std::vector<PerUidReadTimeouts>& perUidReadTimeouts)); - MockIncFs() { ON_CALL(*this, listExistingMounts(_)).WillByDefault(Return()); } + MockIncFs() { + ON_CALL(*this, listExistingMounts(_)).WillByDefault(Return()); + ON_CALL(*this, reserveSpace(_, _, _)).WillByDefault(Return(0)); + } void makeFileFails() { ON_CALL(*this, makeFile(_, _, _, _, _)).WillByDefault(Return(-1)); } void makeFileSuccess() { ON_CALL(*this, makeFile(_, _, _, _, _)).WillByDefault(Return(0)); } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 592952354b8f..6f71e991bb96 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -390,8 +390,6 @@ public final class SystemServer implements Dumpable { private static final String UNCRYPT_PACKAGE_FILE = "/cache/recovery/uncrypt_file"; private static final String BLOCK_MAP_FILE = "/cache/recovery/block.map"; - private static final String GSI_RUNNING_PROP = "ro.gsid.image_running"; - // maximum number of binder threads used for system_server // will be higher than the system default private static final int sMaxBinderThreads = 31; @@ -1662,8 +1660,7 @@ public final class SystemServer implements Dumpable { t.traceEnd(); final boolean hasPdb = !SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP).equals(""); - final boolean hasGsi = SystemProperties.getInt(GSI_RUNNING_PROP, 0) > 0; - if (hasPdb && !hasGsi) { + if (hasPdb) { t.traceBegin("StartPersistentDataBlock"); mSystemServiceManager.startService(PersistentDataBlockService.class); t.traceEnd(); diff --git a/services/tests/PackageManagerServiceTests/unit/Android.bp b/services/tests/PackageManagerServiceTests/unit/Android.bp index 334e53a3aec7..988c02bfb3db 100644 --- a/services/tests/PackageManagerServiceTests/unit/Android.bp +++ b/services/tests/PackageManagerServiceTests/unit/Android.bp @@ -23,14 +23,17 @@ package { android_test { name: "PackageManagerServiceUnitTests", - srcs: ["src/**/*.kt"], + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], static_libs: [ "androidx.test.rules", "androidx.test.runner", "junit", + "kotlin-test", "services.core", "servicestests-utils", - "testng", "truth-prebuilt", ], platform_apis: true, diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt index 0fa9a1def381..d5eda203e42f 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt @@ -25,8 +25,8 @@ import android.os.PatternMatcher import android.util.ArraySet import com.android.server.SystemConfig import com.android.server.compat.PlatformCompat -import com.android.server.pm.verify.domain.DomainVerificationCollector import com.android.server.pm.parsing.pkg.AndroidPackage +import com.android.server.pm.verify.domain.DomainVerificationCollector import com.android.server.testutils.mockThrowOnUnmocked import com.android.server.testutils.whenever import com.google.common.truth.Truth.assertThat diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt index 8ef92393242a..9693f3bab127 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt @@ -19,6 +19,7 @@ package com.android.server.pm.test.verify.domain import android.content.pm.verify.domain.DomainSet import android.content.pm.verify.domain.DomainVerificationInfo import android.content.pm.verify.domain.DomainVerificationRequest +import android.content.pm.verify.domain.DomainVerificationState import android.content.pm.verify.domain.DomainVerificationUserState import android.os.Parcel import android.os.Parcelable @@ -74,7 +75,9 @@ class DomainVerificationCoreApiTest { DomainVerificationInfo( UUID.fromString("703f6d34-6241-4cfd-8176-2e1d23355811"), "com.test.pkg", - massiveSet.withIndex().associate { it.value to it.index } + massiveSet.withIndex().associate { + it.value to DomainVerificationState.convertToInfoState(it.index) + } ) }, unparcel = { DomainVerificationInfo.CREATOR.createFromParcel(it) }, diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt index 53f0ca20e787..7e25901301aa 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt @@ -20,20 +20,21 @@ import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.content.pm.PackageUserState -import android.content.pm.verify.domain.DomainVerificationManager import android.content.pm.parsing.component.ParsedActivity import android.content.pm.parsing.component.ParsedIntentInfo +import android.content.pm.verify.domain.DomainVerificationManager +import android.content.pm.verify.domain.DomainVerificationState import android.os.Build import android.os.Process import android.util.ArraySet import android.util.SparseArray import androidx.test.platform.app.InstrumentationRegistry import com.android.server.pm.PackageSetting +import com.android.server.pm.parsing.pkg.AndroidPackage import com.android.server.pm.verify.domain.DomainVerificationEnforcer 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.parsing.pkg.AndroidPackage import com.android.server.testutils.mockThrowOnUnmocked import com.android.server.testutils.spyThrowOnUnmocked import com.android.server.testutils.whenever @@ -50,6 +51,8 @@ import java.io.File import java.util.UUID import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicInteger +import kotlin.test.assertFailsWith +import kotlin.test.fail @RunWith(Parameterized::class) class DomainVerificationEnforcerTest { @@ -81,47 +84,49 @@ class DomainVerificationEnforcerTest { whenever(filterAppAccess(eq(INVISIBLE_PKG), anyInt(), anyInt())) { true } + whenever(doesUserExist(anyInt())) { (arguments[0] as Int) <= 1 } }) } } - val makeService: (Context) -> Triple<AtomicInteger, AtomicInteger, DomainVerificationService> = - { - val callingUidInt = AtomicInteger(-1) - val callingUserIdInt = AtomicInteger(-1) - - val connection: DomainVerificationManagerInternal.Connection = - mockThrowOnUnmocked { - whenever(callingUid) { callingUidInt.get() } - whenever(callingUserId) { callingUserIdInt.get() } - whenever(getPackageSettingLocked(VISIBLE_PKG)) { visiblePkgSetting } - whenever(getPackageLocked(VISIBLE_PKG)) { visiblePkg } - whenever(getPackageSettingLocked(INVISIBLE_PKG)) { invisiblePkgSetting } - whenever(getPackageLocked(INVISIBLE_PKG)) { invisiblePkg } - whenever(schedule(anyInt(), any())) - whenever(scheduleWriteSettings()) - whenever(filterAppAccess(eq(VISIBLE_PKG), anyInt(), anyInt())) { false } - whenever(filterAppAccess(eq(INVISIBLE_PKG), anyInt(), anyInt())) { - true - } + val makeService: (Context) -> Triple<AtomicInteger, AtomicInteger, + DomainVerificationService> = { + val callingUidInt = AtomicInteger(-1) + val callingUserIdInt = AtomicInteger(-1) + + val connection: DomainVerificationManagerInternal.Connection = + mockThrowOnUnmocked { + whenever(callingUid) { callingUidInt.get() } + whenever(callingUserId) { callingUserIdInt.get() } + whenever(getPackageSettingLocked(VISIBLE_PKG)) { visiblePkgSetting } + whenever(getPackageLocked(VISIBLE_PKG)) { visiblePkg } + whenever(getPackageSettingLocked(INVISIBLE_PKG)) { invisiblePkgSetting } + whenever(getPackageLocked(INVISIBLE_PKG)) { invisiblePkg } + whenever(schedule(anyInt(), any())) + whenever(scheduleWriteSettings()) + whenever(filterAppAccess(eq(VISIBLE_PKG), anyInt(), anyInt())) { false } + whenever(filterAppAccess(eq(INVISIBLE_PKG), anyInt(), anyInt())) { + true } - val service = DomainVerificationService( - it, - mockThrowOnUnmocked { whenever(linkedApps) { ArraySet<String>() } }, - mockThrowOnUnmocked { - whenever( - isChangeEnabled( - anyLong(), - any() - ) - ) { true } - }).apply { - setConnection(connection) + whenever(doesUserExist(anyInt())) { (arguments[0] as Int) <= 1 } } - - Triple(callingUidInt, callingUserIdInt, service) + val service = DomainVerificationService( + it, + mockThrowOnUnmocked { whenever(linkedApps) { ArraySet<String>() } }, + mockThrowOnUnmocked { + whenever( + isChangeEnabled( + anyLong(), + any() + ) + ) { true } + }).apply { + setConnection(connection) } + Triple(callingUidInt, callingUserIdInt, service) + } + fun enforcer( type: Type, name: String, @@ -175,7 +180,7 @@ class DomainVerificationEnforcerTest { service(Type.INTERNAL, "setStatusInternalPackageName") { setDomainVerificationStatusInternal( it.targetPackageName, - DomainVerificationManager.STATE_SUCCESS, + DomainVerificationState.STATE_SUCCESS, ArraySet(setOf("example.com")) ) }, @@ -206,7 +211,7 @@ class DomainVerificationEnforcerTest { setDomainVerificationStatus( it.targetDomainSetId, setOf("example.com"), - DomainVerificationManager.STATE_SUCCESS + DomainVerificationState.STATE_SUCCESS ) }, service(Type.VERIFIER, "setStatusInternalUid") { @@ -214,7 +219,7 @@ class DomainVerificationEnforcerTest { it.callingUid, it.targetDomainSetId, setOf("example.com"), - DomainVerificationManager.STATE_SUCCESS + DomainVerificationState.STATE_SUCCESS ) }, service(Type.SELECTOR_USER, "setLinkHandlingAllowedUserId") { @@ -475,24 +480,7 @@ class DomainVerificationEnforcerTest { fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) { // User selector makes no distinction by UID val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID - if (throws) { - allUids.forEach { - assertFails { - runMethod(target, it, visible = true, callingUserId, targetUserId) - } - } - } else { - allUids.forEach { - runMethod(target, it, visible = true, callingUserId, targetUserId) - } - } - - // User selector doesn't use QUERY_ALL, so the invisible package should always fail - allUids.forEach { - assertFails { - runMethod(target, it, visible = false, callingUserId, targetUserId) - } - } + runCrossUserMethod(allUids, target, callingUserId, targetUserId, throws) } val callingUserId = 0 @@ -529,24 +517,7 @@ class DomainVerificationEnforcerTest { fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) { // User selector makes no distinction by UID val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID - if (throws) { - allUids.forEach { - assertFails { - runMethod(target, it, visible = true, callingUserId, targetUserId) - } - } - } else { - allUids.forEach { - runMethod(target, it, visible = true, callingUserId, targetUserId) - } - } - - // User selector doesn't use QUERY_ALL, so the invisible package should always fail - allUids.forEach { - assertFails { - runMethod(target, it, visible = false, callingUserId, targetUserId) - } - } + runCrossUserMethod(allUids, target, callingUserId, targetUserId, throws) } val callingUserId = 0 @@ -590,24 +561,10 @@ class DomainVerificationEnforcerTest { fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) { // Legacy makes no distinction by UID val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID - if (throws) { - allUids.forEach { - assertFails { - runMethod(target, it, visible = true, callingUserId, targetUserId) - } - } - } else { - allUids.forEach { - runMethod(target, it, visible = true, callingUserId, targetUserId) - } - } - - // Legacy doesn't use QUERY_ALL, so the invisible package should always fail - allUids.forEach { - assertFails { - runMethod(target, it, visible = false, callingUserId, targetUserId) - } - } + // The legacy selector does a silent failure when the user IDs don't match, so it + // cannot verify the non-existent user ID check, as it will not throw an Exception. + runCrossUserMethod(allUids, target, callingUserId, targetUserId, throws, + verifyUserIdCheck = false) } val callingUserId = 0 @@ -642,27 +599,29 @@ class DomainVerificationEnforcerTest { } val target = params.construct(context) - fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) { - // Legacy makes no distinction by UID - val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID - if (throws) { - allUids.forEach { - assertFails { - runMethod(target, it, visible = true, callingUserId, targetUserId) - } - } - } else { - allUids.forEach { - runMethod(target, it, visible = true, callingUserId, targetUserId) + // Legacy code can return PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED + // as an error code. This is distinct from the class level assertFails as unfortunately + // the same number, 0, is used in opposite contexts, where it does represent a failure + // for this legacy case, but not for the modern APIs. + fun assertFailsLegacy(block: () -> Any?) { + try { + val value = block() + if ((value as? Int) + != PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED + ) { + throw AssertionError("Expected call to return false, was $value") } + } catch (e: SecurityException) { + } catch (e: PackageManager.NameNotFoundException) { + // Any of these 2 exceptions are considered failures, which is expected } + } - // Legacy doesn't use QUERY_ALL, so the invisible package should always fail - allUids.forEach { - assertFails { - runMethod(target, it, visible = false, callingUserId, targetUserId) - } - } + fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) { + // Legacy makes no distinction by UID + val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID + runCrossUserMethod(allUids, target, callingUserId, targetUserId, throws, + assertFailsMethod = ::assertFailsLegacy) } val callingUserId = 0 @@ -704,17 +663,8 @@ class DomainVerificationEnforcerTest { fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) { // Owner querent makes no distinction by UID val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID - if (throws) { - allUids.forEach { - assertFails { - runMethod(target, it, visible = true, callingUserId, targetUserId) - } - } - } else { - allUids.forEach { - runMethod(target, it, visible = true, callingUserId, targetUserId) - } - } + runCrossUserMethod(allUids, target, callingUserId, targetUserId, throws, + verifyInvisiblePkg = false) } val callingUserId = 0 @@ -782,22 +732,88 @@ class DomainVerificationEnforcerTest { return params.runMethod(target, callingUid, callingUserId, userId, packageName, uuid, proxy) } + private fun runCrossUserMethod( + allUids: Iterable<Int>, + target: Any, + callingUserId: Int, + targetUserId: Int, + throws: Boolean, + verifyUserIdCheck: Boolean = true, + verifyInvisiblePkg: Boolean = true, + assertFailsMethod: (() -> Any?) -> Unit = ::assertFails, + ) { + if (throws) { + allUids.forEach { + assertFailsMethod { + // When testing a non-user ID failure, send an invalid user ID. + // This ensures the failure occurs before the user ID check is run. + try { + runMethod(target, it, visible = true, callingUserId, 100) + } catch (e: SecurityException) { + if (verifyUserIdCheck) { + e.message?.let { + if (it.contains("user ID", ignoreCase = true) + || it.contains("100")) { + fail( + "Method should not check user existence before permissions" + ) + } + } + } + + // Rethrow to allow normal fail checking logic to run + throw e + } + } + } + } else { + allUids.forEach { + runMethod(target, it, visible = true, callingUserId, targetUserId) + } + } + + if (verifyInvisiblePkg) { + allUids.forEach { + assertFailsMethod { + runMethod(target, it, visible = false, callingUserId, targetUserId) + } + } + } + + if (verifyUserIdCheck) { + // An invalid target user ID should always fail + allUids.forEach { + assertFailsWith(SecurityException::class) { + runMethod(target, it, visible = true, callingUserId, 100) + } + } + + // An invalid calling user ID should always fail, although this cannot happen in prod + allUids.forEach { + assertFailsWith(SecurityException::class) { + runMethod(target, it, visible = true, 100, targetUserId) + } + } + } + } + private fun assertFails(block: () -> Any?) { try { val value = block() - // Some methods return false rather than throwing, so check that as well - if ((value as? Boolean) != false) { - // Can also return default value if it's a legacy call - if ((value as? Int) - != PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED - ) { - throw AssertionError("Expected call to return false, was $value") - } + // Some methods return false or an error rather than throwing, so check that as well + val valueAsBoolean = value as? Boolean + if (valueAsBoolean == false) { + // Expected failure, do not throw + return + } + + val valueAsInt = value as? Int + if (valueAsInt != null && valueAsInt == DomainVerificationManager.STATUS_OK) { + throw AssertionError("Expected call to return false, was $value") } } catch (e: SecurityException) { } catch (e: PackageManager.NameNotFoundException) { - } catch (e: DomainVerificationManager.InvalidDomainSetException) { - // Any of these 3 exceptions are considered failures, which is expected + // Any of these 2 exceptions are considered failures, which is expected } } diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationJavaUtil.java b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationJavaUtil.java new file mode 100644 index 000000000000..8ae4c5ae96a3 --- /dev/null +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationJavaUtil.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.server.pm.test.verify.domain; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.content.pm.PackageManager; + +import com.android.server.pm.verify.domain.DomainVerificationService; + +import java.util.Set; +import java.util.UUID; + +/** + * Proxies Kotlin calls to the Java layer such that null values can be passed for {@link NonNull} + * marked parameters, as Kotlin disallows this at the compiler leveling, preventing the null error + * codes from being tested. + */ +class DomainVerificationJavaUtil { + + static int setStatusForceNullable(@NonNull DomainVerificationService service, + @Nullable UUID domainSetId, @Nullable Set<String> domains, int state) + throws PackageManager.NameNotFoundException { + return service.setDomainVerificationStatus(domainSetId, domains, state); + } + + static int setUserSelectionForceNullable(@NonNull DomainVerificationService service, + @Nullable UUID domainSetId, @Nullable Set<String> domains, boolean enabled, + @UserIdInt int userId) + throws PackageManager.NameNotFoundException { + return service.setDomainVerificationUserSelection(domainSetId, domains, enabled, userId); + } +} diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationLegacySettingsTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationLegacySettingsTest.kt index 9a3bd994eac0..509824d93512 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationLegacySettingsTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationLegacySettingsTest.kt @@ -19,9 +19,9 @@ package com.android.server.pm.test.verify.domain import android.content.pm.IntentFilterVerificationInfo import android.content.pm.PackageManager import android.util.ArraySet -import com.android.server.pm.verify.domain.DomainVerificationLegacySettings import com.android.server.pm.test.verify.domain.DomainVerificationPersistenceTest.Companion.readXml import com.android.server.pm.test.verify.domain.DomainVerificationPersistenceTest.Companion.writeXml +import com.android.server.pm.verify.domain.DomainVerificationLegacySettings import com.google.common.truth.Truth.assertWithMessage import org.junit.Rule import org.junit.Test diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt new file mode 100644 index 000000000000..0e74b65d25d5 --- /dev/null +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt @@ -0,0 +1,372 @@ +/* + * 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.test.verify.domain + +import android.content.Intent +import android.content.pm.PackageManager +import android.content.pm.parsing.component.ParsedActivity +import android.content.pm.parsing.component.ParsedIntentInfo +import android.content.pm.verify.domain.DomainVerificationInfo +import android.content.pm.verify.domain.DomainVerificationManager +import android.content.pm.verify.domain.DomainVerificationUserState +import android.os.Build +import android.os.PatternMatcher +import android.os.Process +import android.util.ArraySet +import com.android.server.pm.PackageSetting +import com.android.server.pm.parsing.pkg.AndroidPackage +import com.android.server.pm.verify.domain.DomainVerificationService +import com.android.server.testutils.mockThrowOnUnmocked +import com.android.server.testutils.whenever +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyLong +import org.mockito.ArgumentMatchers.anyString +import java.util.UUID +import kotlin.test.assertFailsWith + +class DomainVerificationManagerApiTest { + + companion object { + private const val PKG_ONE = "com.test.one" + private const val PKG_TWO = "com.test.two" + private const val PKG_THREE = "com.test.three" + + private val UUID_ONE = UUID.fromString("1b041c96-8d37-4932-a858-561bfac5947c") + private val UUID_TWO = UUID.fromString("a3389c16-7f9f-4e86-85e3-500d1249c74c") + private val UUID_THREE = UUID.fromString("0b3260ed-07c4-4b45-840b-237f8fb8b433") + private val UUID_INVALID = UUID.fromString("ad33babc-490b-4965-9d78-7e91248b00f") + + private val DOMAIN_BASE = DomainVerificationManagerApiTest::class.java.packageName + private val DOMAIN_1 = "one.$DOMAIN_BASE" + private val DOMAIN_2 = "two.$DOMAIN_BASE" + private val DOMAIN_3 = "three.$DOMAIN_BASE" + private val DOMAIN_4 = "four.$DOMAIN_BASE" + } + + @Test + fun queryValidVerificationPackageNames() { + val pkgWithDomains = mockPkgSetting(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2)) + val pkgWithoutDomains = mockPkgSetting(PKG_TWO, UUID_TWO, emptyList()) + + val service = makeService(pkgWithDomains, pkgWithoutDomains).apply { + addPackages(pkgWithDomains, pkgWithoutDomains) + } + + assertThat(service.queryValidVerificationPackageNames()) + .containsExactly(pkgWithDomains.getName()) + } + + @Test + fun getDomainVerificationInfoId() { + val pkgWithDomains = mockPkgSetting(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2)) + val pkgWithoutDomains = mockPkgSetting(PKG_TWO, UUID_TWO, emptyList()) + + val service = makeService(pkgWithDomains, pkgWithoutDomains).apply { + addPackages(pkgWithDomains, pkgWithoutDomains) + } + + assertThat(service.getDomainVerificationInfoId(PKG_ONE)).isEqualTo(UUID_ONE) + assertThat(service.getDomainVerificationInfoId(PKG_TWO)).isEqualTo(UUID_TWO) + + assertThat(service.getDomainVerificationInfoId("invalid.pkg.name")).isEqualTo(null) + } + + @Test + fun getDomainVerificationInfo() { + val pkgWithDomains = mockPkgSetting(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2)) + val pkgWithoutDomains = mockPkgSetting(PKG_TWO, UUID_TWO, emptyList()) + + val service = makeService(pkgWithDomains, pkgWithoutDomains).apply { + addPackages(pkgWithDomains, pkgWithoutDomains) + } + + val infoOne = service.getDomainVerificationInfo(pkgWithDomains.getName()) + assertThat(infoOne).isNotNull() + assertThat(infoOne!!.identifier).isEqualTo(pkgWithDomains.domainSetId) + assertThat(infoOne.packageName).isEqualTo(pkgWithDomains.getName()) + assertThat(infoOne.hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to DomainVerificationInfo.STATE_NO_RESPONSE, + DOMAIN_2 to DomainVerificationInfo.STATE_NO_RESPONSE, + )) + + assertThat(service.getDomainVerificationInfo(pkgWithoutDomains.getName())).isNull() + + assertFailsWith(PackageManager.NameNotFoundException::class) { + service.getDomainVerificationInfo("invalid.pkg.name") + } + } + + @Test + fun setStatus() { + val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2)) + val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO, listOf(DOMAIN_3, DOMAIN_4)) + + val map = mutableMapOf(pkg1.getName() to pkg1, pkg2.getName() to pkg2) + val service = makeService(map::get).apply { addPackages(pkg1, pkg2) } + + assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_2), 1100)) + .isEqualTo(DomainVerificationManager.STATUS_OK) + + assertThat(service.setStatus(UUID_INVALID, setOf(DOMAIN_1), 1100)) + .isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_ID_INVALID) + + assertThat(DomainVerificationJavaUtil.setStatusForceNullable(service, null, + setOf(DOMAIN_1), 1100)) + .isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_ID_NULL) + + assertThat(DomainVerificationJavaUtil.setStatusForceNullable(service, UUID_ONE, null, + 1100)).isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_NULL_OR_EMPTY) + + assertThat(service.setStatus(UUID_ONE, emptySet(), 1100)) + .isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_NULL_OR_EMPTY) + + assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_3), 1100)) + .isEqualTo(DomainVerificationManager.ERROR_UNKNOWN_DOMAIN) + + assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_1, DOMAIN_2, DOMAIN_3), 1100)) + .isEqualTo(DomainVerificationManager.ERROR_UNKNOWN_DOMAIN) + + assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_1), 15)) + .isEqualTo(DomainVerificationManager.ERROR_INVALID_STATE_CODE) + + map.clear() + assertFailsWith(PackageManager.NameNotFoundException::class){ + service.setStatus(UUID_ONE, setOf(DOMAIN_1), 1100) + } + } + + @Test + fun setDomainVerificationLinkHandlingAllowed() { + val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2)) + val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO, listOf(DOMAIN_3, DOMAIN_4)) + + val map = mutableMapOf(pkg1.getName() to pkg1, pkg2.getName() to pkg2) + val service = makeService(map::get).apply { addPackages(pkg1, pkg2) } + + service.setDomainVerificationLinkHandlingAllowed(PKG_ONE, false, 0); + + // Should edit same package, same user + assertThat(service.getDomainVerificationUserState(PKG_ONE, 0) + ?.isLinkHandlingAllowed).isEqualTo(false) + + // Shouldn't edit different user + assertThat(service.getDomainVerificationUserState(PKG_ONE, 1) + ?.isLinkHandlingAllowed).isEqualTo(true) + + // Shouldn't edit different package + assertThat(service.getDomainVerificationUserState(PKG_TWO, 0) + ?.isLinkHandlingAllowed).isEqualTo(true) + + assertFailsWith(PackageManager.NameNotFoundException::class){ + service.setDomainVerificationLinkHandlingAllowed("invalid.pkg.name", false, 0); + } + } + + @Test + fun setUserSelection() { + val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2)) + val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO, listOf(DOMAIN_3, DOMAIN_4)) + val pkg3 = mockPkgSetting(PKG_THREE, UUID_THREE, listOf(DOMAIN_1, DOMAIN_2)) + + val map = mutableMapOf( + pkg1.getName() to pkg1, + pkg2.getName() to pkg2, + pkg3.getName() to pkg3 + ) + val service = makeService(map::get).apply { addPackages(pkg1, pkg2, pkg3) } + + assertThat(service.setUserSelection(UUID_ONE, setOf(DOMAIN_2), true, 0)) + .isEqualTo(DomainVerificationManager.STATUS_OK) + + assertThat(service.setUserSelection(UUID_INVALID, setOf(DOMAIN_1), true, 0)) + .isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_ID_INVALID) + + assertThat(DomainVerificationJavaUtil.setUserSelectionForceNullable(service, null, + setOf(DOMAIN_1), true, 0)) + .isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_ID_NULL) + + assertThat(DomainVerificationJavaUtil.setUserSelectionForceNullable(service, UUID_ONE, null, + true, 0)).isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_NULL_OR_EMPTY) + + assertThat(service.setUserSelection(UUID_ONE, emptySet(), true, 0)) + .isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_NULL_OR_EMPTY) + + assertThat(service.setUserSelection(UUID_ONE, setOf(DOMAIN_3), true, 0)) + .isEqualTo(DomainVerificationManager.ERROR_UNKNOWN_DOMAIN) + + assertThat(service.setUserSelection(UUID_ONE, setOf(DOMAIN_1, DOMAIN_2, DOMAIN_3), true, 0)) + .isEqualTo(DomainVerificationManager.ERROR_UNKNOWN_DOMAIN) + + service.setStatus(UUID_ONE, setOf(DOMAIN_2), DomainVerificationInfo.STATE_SUCCESS) + + assertThat(service.setUserSelection(UUID_THREE, setOf(DOMAIN_2), true, 0)) + .isEqualTo(DomainVerificationManager.ERROR_UNABLE_TO_APPROVE) + + map.clear() + assertFailsWith(PackageManager.NameNotFoundException::class){ + service.setUserSelection(UUID_ONE, setOf(DOMAIN_1), true, 0) + } + } + + @Test + fun getDomainVerificationUserState() { + val pkgWithDomains = mockPkgSetting(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2)) + val pkgWithoutDomains = mockPkgSetting(PKG_TWO, UUID_TWO, emptyList()) + + val service = makeService(pkgWithDomains, pkgWithoutDomains).apply { + addPackages(pkgWithDomains, pkgWithoutDomains) + } + + val infoOne = service.getDomainVerificationUserState(pkgWithDomains.getName(), 0) + assertThat(infoOne).isNotNull() + assertThat(infoOne!!.identifier).isEqualTo(pkgWithDomains.domainSetId) + assertThat(infoOne.packageName).isEqualTo(pkgWithDomains.getName()) + assertThat(infoOne.isLinkHandlingAllowed).isTrue() + assertThat(infoOne.hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to DomainVerificationUserState.DOMAIN_STATE_NONE, + DOMAIN_2 to DomainVerificationUserState.DOMAIN_STATE_NONE, + )) + + val infoTwo = service.getDomainVerificationUserState(pkgWithoutDomains.getName(), 0) + assertThat(infoTwo).isNotNull() + assertThat(infoTwo!!.identifier).isEqualTo(pkgWithoutDomains.domainSetId) + assertThat(infoTwo.packageName).isEqualTo(pkgWithoutDomains.getName()) + assertThat(infoOne.isLinkHandlingAllowed).isTrue() + assertThat(infoTwo.hostToStateMap).isEmpty() + + assertFailsWith(PackageManager.NameNotFoundException::class) { + service.getDomainVerificationUserState("invalid.pkg.name", 0) + } + } + + @Test + fun getOwnersForDomain() { + val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2)) + val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO, listOf(DOMAIN_1, DOMAIN_2)) + + val service = makeService(pkg1, pkg2).apply { + addPackages(pkg1, pkg2) + } + + assertThat(service.getOwnersForDomain(DOMAIN_1, 0)).isEmpty() + + service.setStatus(pkg1.domainSetId, setOf(DOMAIN_1), DomainVerificationInfo.STATE_SUCCESS) + + service.setStatus(pkg2.domainSetId, setOf(DOMAIN_1), DomainVerificationInfo.STATE_SUCCESS) + + service.setUserSelection(pkg1.domainSetId, setOf(DOMAIN_2), true, 0) + + service.getOwnersForDomain(DOMAIN_1, 0).let { + assertThat(it).hasSize(2) + assertThat(it[0].packageName).isEqualTo(pkg1.getName()) + assertThat(it[0].isOverrideable).isEqualTo(false) + assertThat(it[1].packageName).isEqualTo(pkg2.getName()) + assertThat(it[1].isOverrideable).isEqualTo(false) + } + + service.getOwnersForDomain(DOMAIN_2, 0).let { + assertThat(it).hasSize(1) + assertThat(it.single().packageName).isEqualTo(pkg1.getName()) + assertThat(it.single().isOverrideable).isEqualTo(true) + } + assertThat(service.getOwnersForDomain(DOMAIN_2, 1)).isEmpty() + + service.setUserSelection(pkg2.domainSetId, setOf(DOMAIN_2), true, 0) + service.getOwnersForDomain(DOMAIN_2, 0).let { + assertThat(it).hasSize(1) + assertThat(it.single().packageName).isEqualTo(pkg2.getName()) + assertThat(it.single().isOverrideable).isEqualTo(true) + } + assertThat(service.getOwnersForDomain(DOMAIN_2, 1)).isEmpty() + } + + private fun makeService(vararg pkgSettings: PackageSetting) = + makeService { pkgName -> pkgSettings.find { pkgName == it.getName() } } + + private fun makeService(pkgSettingFunction: (String) -> PackageSetting? = { null }) = + DomainVerificationService(mockThrowOnUnmocked { + // Assume the test has every permission necessary + whenever(enforcePermission(anyString(), anyInt(), anyInt(), anyString())) + whenever(checkPermission(anyString(), anyInt(), anyInt())) { + PackageManager.PERMISSION_GRANTED + } + }, mockThrowOnUnmocked { + whenever(linkedApps) { ArraySet<String>() } + }, mockThrowOnUnmocked { + whenever(isChangeEnabled(anyLong(), any())) { true } + }).apply { + setConnection(mockThrowOnUnmocked { + whenever(filterAppAccess(anyString(), anyInt(), anyInt())) { false } + whenever(doesUserExist(0)) { true } + whenever(doesUserExist(1)) { true } + whenever(scheduleWriteSettings()) + + // Need to provide an internal UID so some permission checks are ignored + whenever(callingUid) { Process.ROOT_UID } + whenever(callingUserId) { 0 } + + whenever(getPackageSettingLocked(anyString())) { + pkgSettingFunction(arguments[0] as String) + } + whenever(getPackageLocked(anyString())) { + pkgSettingFunction(arguments[0] as String)?.getPkg() + } + }) + } + + private fun mockPkgSetting(pkgName: String, domainSetId: UUID, domains: List<String> = listOf( + DOMAIN_1, DOMAIN_2 + )) = mockThrowOnUnmocked<PackageSetting> { + val pkg = mockThrowOnUnmocked<AndroidPackage> { + whenever(packageName) { pkgName } + whenever(targetSdkVersion) { Build.VERSION_CODES.S } + + val activityList = listOf( + ParsedActivity().apply { + domains.forEach { + addIntent( + ParsedIntentInfo().apply { + autoVerify = true + addAction(Intent.ACTION_VIEW) + addCategory(Intent.CATEGORY_BROWSABLE) + addCategory(Intent.CATEGORY_DEFAULT) + addDataScheme("http") + addDataScheme("https") + addDataPath("/sub", PatternMatcher.PATTERN_LITERAL) + addDataAuthority(it, null) + } + ) + } + }, + ) + + whenever(activities) { activityList } + } + + whenever(getPkg()) { pkg } + whenever(getName()) { pkgName } + whenever(this.domainSetId) { domainSetId } + whenever(getInstantApp(anyInt())) { false } + whenever(firstInstallTime) { 0L } + } + + fun DomainVerificationService.addPackages(vararg pkgSettings: PackageSetting) = + pkgSettings.forEach(::addPackage) +} diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt index 8c31c65e1b0a..a5db3c578670 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt @@ -16,8 +16,8 @@ package com.android.server.pm.test.verify.domain -import android.content.pm.verify.domain.DomainVerificationRequest import android.content.pm.verify.domain.DomainVerificationInfo +import android.content.pm.verify.domain.DomainVerificationRequest import android.content.pm.verify.domain.DomainVerificationUserState import com.android.server.pm.verify.domain.DomainVerificationPersistence diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt new file mode 100644 index 000000000000..fe3672d06bc0 --- /dev/null +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt @@ -0,0 +1,425 @@ +/* + * 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.test.verify.domain + +import android.content.Intent +import android.content.pm.PackageManager +import android.content.pm.parsing.component.ParsedActivity +import android.content.pm.parsing.component.ParsedIntentInfo +import android.content.pm.verify.domain.DomainVerificationInfo.STATE_NO_RESPONSE +import android.content.pm.verify.domain.DomainVerificationInfo.STATE_SUCCESS +import android.content.pm.verify.domain.DomainVerificationInfo.STATE_UNMODIFIABLE +import android.content.pm.verify.domain.DomainVerificationManager +import android.content.pm.verify.domain.DomainVerificationState +import android.content.pm.verify.domain.DomainVerificationUserState.DOMAIN_STATE_NONE +import android.content.pm.verify.domain.DomainVerificationUserState.DOMAIN_STATE_SELECTED +import android.content.pm.verify.domain.DomainVerificationUserState.DOMAIN_STATE_VERIFIED +import android.os.Build +import android.os.PatternMatcher +import android.os.Process +import android.util.ArraySet +import android.util.Xml +import com.android.server.pm.PackageSetting +import com.android.server.pm.parsing.pkg.AndroidPackage +import com.android.server.pm.verify.domain.DomainVerificationService +import com.android.server.testutils.mockThrowOnUnmocked +import com.android.server.testutils.whenever +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.mockito.ArgumentMatchers +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyString +import java.util.UUID + +class DomainVerificationPackageTest { + + companion object { + private const val PKG_ONE = "com.test.one" + private const val PKG_TWO = "com.test.two" + private val UUID_ONE = UUID.fromString("1b041c96-8d37-4932-a858-561bfac5947c") + private val UUID_TWO = UUID.fromString("a3389c16-7f9f-4e86-85e3-500d1249c74c") + + private val DOMAIN_BASE = DomainVerificationPackageTest::class.java.packageName + private val DOMAIN_1 = "one.$DOMAIN_BASE" + private val DOMAIN_2 = "two.$DOMAIN_BASE" + private val DOMAIN_3 = "three.$DOMAIN_BASE" + private val DOMAIN_4 = "four.$DOMAIN_BASE" + + private const val USER_ID = 0 + } + + private val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE) + private val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO) + + @Test + fun addPackageFirstTime() { + val service = makeService(pkg1, pkg2) + service.addPackage(pkg1) + val info = service.getInfo(pkg1.getName()) + assertThat(info.packageName).isEqualTo(pkg1.getName()) + assertThat(info.identifier).isEqualTo(pkg1.domainSetId) + assertThat(info.hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to STATE_NO_RESPONSE, + DOMAIN_2 to STATE_NO_RESPONSE, + )) + + val userState = service.getUserState(pkg1.getName()) + assertThat(userState.packageName).isEqualTo(pkg1.getName()) + assertThat(userState.identifier).isEqualTo(pkg1.domainSetId) + assertThat(userState.isLinkHandlingAllowed).isEqualTo(true) + assertThat(userState.user.identifier).isEqualTo(USER_ID) + assertThat(userState.hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to DOMAIN_STATE_NONE, + DOMAIN_2 to DOMAIN_STATE_NONE, + )) + + assertThat(service.queryValidVerificationPackageNames()) + .containsExactly(pkg1.getName()) + } + + @Test + fun addPackageActive() { + // language=XML + val xml = """ + <?xml?> + <domain-verifications> + <active> + <package-state + packageName="${pkg1.getName()}" + id="${pkg1.domainSetId}" + > + <state> + <domain name="$DOMAIN_1" state="$STATE_SUCCESS"/> + </state> + <user-states> + <user-state userId="$USER_ID" allowLinkHandling="false"> + <enabled-hosts> + <host name="$DOMAIN_2"/> + </enabled-hosts> + </user-state> + </user-states> + </package-state> + </active> + </domain-verifications> + """.trimIndent() + + val service = makeService(pkg1, pkg2) + xml.byteInputStream().use { + service.readSettings(Xml.resolvePullParser(it)) + } + + service.addPackage(pkg1) + + val info = service.getInfo(pkg1.getName()) + assertThat(info.packageName).isEqualTo(pkg1.getName()) + assertThat(info.identifier).isEqualTo(pkg1.domainSetId) + assertThat(info.hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to STATE_SUCCESS, + DOMAIN_2 to STATE_NO_RESPONSE, + )) + + val userState = service.getUserState(pkg1.getName()) + assertThat(userState.packageName).isEqualTo(pkg1.getName()) + assertThat(userState.identifier).isEqualTo(pkg1.domainSetId) + assertThat(userState.isLinkHandlingAllowed).isEqualTo(false) + assertThat(userState.user.identifier).isEqualTo(USER_ID) + assertThat(userState.hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to DOMAIN_STATE_VERIFIED, + DOMAIN_2 to DOMAIN_STATE_SELECTED, + )) + + assertThat(service.queryValidVerificationPackageNames()) + .containsExactly(pkg1.getName()) + } + + @Test + fun migratePackageDropDomain() { + val pkgName = PKG_ONE + val pkgBefore = mockPkgSetting(pkgName, UUID_ONE, + listOf(DOMAIN_1, DOMAIN_2, DOMAIN_3, DOMAIN_4)) + val pkgAfter = mockPkgSetting(pkgName, UUID_TWO, + listOf(DOMAIN_1, DOMAIN_2)) + + // Test 4 domains: + // 1 will be approved and preserved, 2 will be selected and preserved, + // 3 will be denied and dropped, 4 will be selected and dropped + + val map = mutableMapOf<String, PackageSetting>() + val service = makeService { map[it] } + service.addPackage(pkgBefore) + + // Only insert the package after addPackage call to ensure the service doesn't access + // a live package inside the addPackage logic. It should only use the provided input. + map[pkgName] = pkgBefore + + // To test the approve/denial states, use the internal methods for this variant + service.setDomainVerificationStatusInternal(pkgName, DomainVerificationState.STATE_APPROVED, + ArraySet(setOf(DOMAIN_1))) + service.setDomainVerificationStatusInternal(pkgName, DomainVerificationState.STATE_DENIED, + ArraySet(setOf(DOMAIN_3))) + service.setUserSelection( + UUID_ONE, setOf(DOMAIN_2, DOMAIN_4), true, USER_ID) + + // Check the verifier cannot change the shell approve/deny states + service.setStatus(UUID_ONE, setOf(DOMAIN_1, DOMAIN_3), STATE_SUCCESS) + + assertThat(service.getInfo(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to STATE_UNMODIFIABLE, + DOMAIN_2 to STATE_NO_RESPONSE, + DOMAIN_3 to STATE_UNMODIFIABLE, + DOMAIN_4 to STATE_NO_RESPONSE, + )) + assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to DOMAIN_STATE_VERIFIED, + DOMAIN_2 to DOMAIN_STATE_SELECTED, + DOMAIN_3 to DOMAIN_STATE_NONE, + DOMAIN_4 to DOMAIN_STATE_SELECTED, + )) + + // Now remove the package because migrateState shouldn't use it either + map.remove(pkgName) + + map[pkgName] = pkgAfter + + service.migrateState(pkgBefore, pkgAfter) + + assertThat(service.getInfo(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to STATE_UNMODIFIABLE, + DOMAIN_2 to STATE_NO_RESPONSE, + )) + assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to DOMAIN_STATE_VERIFIED, + DOMAIN_2 to DOMAIN_STATE_SELECTED, + )) + assertThat(service.queryValidVerificationPackageNames()).containsExactly(pkgName) + } + + @Test + fun migratePackageDropAll() { + val pkgName = PKG_ONE + val pkgBefore = mockPkgSetting(pkgName, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2)) + val pkgAfter = mockPkgSetting(pkgName, UUID_TWO, emptyList()) + + val map = mutableMapOf<String, PackageSetting>() + val service = makeService { map[it] } + service.addPackage(pkgBefore) + + // Only insert the package after addPackage call to ensure the service doesn't access + // a live package inside the addPackage logic. It should only use the provided input. + map[pkgName] = pkgBefore + + assertThat(service.getInfo(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to STATE_NO_RESPONSE, + DOMAIN_2 to STATE_NO_RESPONSE, + )) + assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to DOMAIN_STATE_NONE, + DOMAIN_2 to DOMAIN_STATE_NONE, + )) + assertThat(service.queryValidVerificationPackageNames()).containsExactly(pkgName) + + // Now remove the package because migrateState shouldn't use it either + map.remove(pkgName) + + service.migrateState(pkgBefore, pkgAfter) + + map[pkgName] = pkgAfter + + assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_1), STATE_SUCCESS)) + .isNotEqualTo(DomainVerificationManager.STATUS_OK) + + assertThat(service.setUserSelection(UUID_ONE, setOf(DOMAIN_2), true, USER_ID)) + .isNotEqualTo(DomainVerificationManager.STATUS_OK) + + assertThat(service.getDomainVerificationInfo(pkgName)).isNull() + assertThat(service.getUserState(pkgName).hostToStateMap).isEmpty() + assertThat(service.queryValidVerificationPackageNames()).isEmpty() + } + + @Test + fun migratePackageAddDomain() { + val pkgName = PKG_ONE + val pkgBefore = mockPkgSetting(pkgName, UUID_ONE, + listOf(DOMAIN_1, DOMAIN_2)) + val pkgAfter = mockPkgSetting(pkgName, UUID_TWO, + listOf(DOMAIN_1, DOMAIN_2, DOMAIN_3)) + + // Test 3 domains: + // 1 will be verified and preserved, 2 will be selected and preserved, + // 3 will be new and default + + val map = mutableMapOf<String, PackageSetting>() + val service = makeService { map[it] } + service.addPackage(pkgBefore) + + // Only insert the package after addPackage call to ensure the service doesn't access + // a live package inside the addPackage logic. It should only use the provided input. + map[pkgName] = pkgBefore + + service.setStatus(UUID_ONE, setOf(DOMAIN_1), STATE_SUCCESS) + service.setUserSelection(UUID_ONE, setOf(DOMAIN_2), true, USER_ID) + + assertThat(service.getInfo(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to STATE_SUCCESS, + DOMAIN_2 to STATE_NO_RESPONSE, + )) + assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to DOMAIN_STATE_VERIFIED, + DOMAIN_2 to DOMAIN_STATE_SELECTED, + )) + + // Now remove the package because migrateState shouldn't use it either + map.remove(pkgName) + + service.migrateState(pkgBefore, pkgAfter) + + map[pkgName] = pkgAfter + + assertThat(service.getInfo(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to STATE_SUCCESS, + DOMAIN_2 to STATE_NO_RESPONSE, + DOMAIN_3 to STATE_NO_RESPONSE, + )) + assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to DOMAIN_STATE_VERIFIED, + DOMAIN_2 to DOMAIN_STATE_SELECTED, + DOMAIN_3 to DOMAIN_STATE_NONE, + )) + assertThat(service.queryValidVerificationPackageNames()).containsExactly(pkgName) + } + + @Test + fun migratePackageAddAll() { + val pkgName = PKG_ONE + val pkgBefore = mockPkgSetting(pkgName, UUID_ONE, emptyList()) + val pkgAfter = mockPkgSetting(pkgName, UUID_TWO, listOf(DOMAIN_1, DOMAIN_2)) + + val map = mutableMapOf<String, PackageSetting>() + val service = makeService { map[it] } + service.addPackage(pkgBefore) + + // Only insert the package after addPackage call to ensure the service doesn't access + // a live package inside the addPackage logic. It should only use the provided input. + map[pkgName] = pkgBefore + + assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_1), STATE_SUCCESS)) + .isNotEqualTo(DomainVerificationManager.STATUS_OK) + + assertThat(service.setUserSelection(UUID_ONE, setOf(DOMAIN_2), true, USER_ID)) + .isNotEqualTo(DomainVerificationManager.STATUS_OK) + + assertThat(service.getDomainVerificationInfo(pkgName)).isNull() + assertThat(service.getUserState(pkgName).hostToStateMap).isEmpty() + assertThat(service.queryValidVerificationPackageNames()).isEmpty() + + // Now remove the package because migrateState shouldn't use it either + map.remove(pkgName) + + service.migrateState(pkgBefore, pkgAfter) + + map[pkgName] = pkgAfter + + assertThat(service.getInfo(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to STATE_NO_RESPONSE, + DOMAIN_2 to STATE_NO_RESPONSE, + )) + assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to DOMAIN_STATE_NONE, + DOMAIN_2 to DOMAIN_STATE_NONE, + )) + assertThat(service.queryValidVerificationPackageNames()).containsExactly(pkgName) + } + + private fun DomainVerificationService.getInfo(pkgName: String) = + getDomainVerificationInfo(pkgName) + .also { assertThat(it).isNotNull() }!! + + private fun DomainVerificationService.getUserState(pkgName: String) = + getDomainVerificationUserState(pkgName, USER_ID) + .also { assertThat(it).isNotNull() }!! + + private fun makeService(vararg pkgSettings: PackageSetting) = + makeService { pkgName -> pkgSettings.find { pkgName == it.getName()} } + + private fun makeService(pkgSettingFunction: (String) -> PackageSetting? = { null }) = + DomainVerificationService(mockThrowOnUnmocked { + // Assume the test has every permission necessary + whenever(enforcePermission(anyString(), anyInt(), anyInt(), anyString())) + whenever(checkPermission(anyString(), anyInt(), anyInt())) { + PackageManager.PERMISSION_GRANTED + } + }, mockThrowOnUnmocked { + whenever(linkedApps) { ArraySet<String>() } + }, mockThrowOnUnmocked { + whenever(isChangeEnabled(ArgumentMatchers.anyLong(), any())) { true } + }).apply { + setConnection(mockThrowOnUnmocked { + whenever(filterAppAccess(anyString(), anyInt(), anyInt())) { false } + whenever(doesUserExist(0)) { true } + whenever(doesUserExist(1)) { true } + whenever(scheduleWriteSettings()) + + // Need to provide an internal UID so some permission checks are ignored + whenever(callingUid) { Process.ROOT_UID } + whenever(callingUserId) { 0 } + + whenever(getPackageSettingLocked(anyString())) { + pkgSettingFunction(arguments[0] as String)!! + } + whenever(getPackageLocked(anyString())) { + pkgSettingFunction(arguments[0] as String)!!.getPkg() + } + }) + } + + private fun mockPkgSetting(pkgName: String, domainSetId: UUID, domains: List<String> = listOf( + DOMAIN_1, DOMAIN_2 + )) = mockThrowOnUnmocked<PackageSetting> { + val pkg = mockThrowOnUnmocked<AndroidPackage> { + whenever(packageName) { pkgName } + whenever(targetSdkVersion) { Build.VERSION_CODES.S } + + val activityList = listOf( + ParsedActivity().apply { + domains.forEach { + addIntent( + ParsedIntentInfo().apply { + autoVerify = true + addAction(Intent.ACTION_VIEW) + addCategory(Intent.CATEGORY_BROWSABLE) + addCategory(Intent.CATEGORY_DEFAULT) + addDataScheme("http") + addDataScheme("https") + addDataPath("/sub", PatternMatcher.PATTERN_LITERAL) + addDataAuthority(it, null) + } + ) + } + }, + ) + + whenever(activities) { activityList } + } + + whenever(getPkg()) { pkg } + whenever(getName()) { pkgName } + whenever(this.domainSetId) { domainSetId } + whenever(getInstantApp(anyInt())) { false } + whenever(firstInstallTime) { 0L } + } +} diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt index 6597577cf14f..f8fda12f806d 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt @@ -16,7 +16,7 @@ package com.android.server.pm.test.verify.domain -import android.content.pm.verify.domain.DomainVerificationManager +import android.content.pm.verify.domain.DomainVerificationState import android.util.ArrayMap import android.util.TypedXmlPullParser import android.util.TypedXmlSerializer @@ -117,11 +117,11 @@ class DomainVerificationPersistenceTest { @Test fun readMalformed() { val stateZero = mockEmptyPkgState(0).apply { - stateMap["example.com"] = DomainVerificationManager.STATE_SUCCESS - stateMap["example.org"] = DomainVerificationManager.STATE_FIRST_VERIFIER_DEFINED + stateMap["example.com"] = DomainVerificationState.STATE_SUCCESS + stateMap["example.org"] = DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED // A domain without a written state falls back to default - stateMap["missing-state.com"] = DomainVerificationManager.STATE_NO_RESPONSE + stateMap["missing-state.com"] = DomainVerificationState.STATE_NO_RESPONSE userStates[1] = DomainVerificationInternalUserState(1).apply { addHosts(setOf("example-user1.com", "example-user1.org")) @@ -159,9 +159,9 @@ class DomainVerificationPersistenceTest { > <state> <domain name="example.com" state="${ - DomainVerificationManager.STATE_SUCCESS}"/> + DomainVerificationState.STATE_SUCCESS}"/> <domain name="example.org" state="${ - DomainVerificationManager.STATE_FIRST_VERIFIER_DEFINED}"/> + DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED}"/> <not-domain name="not-domain.com" state="1"/> <domain name="missing-state.com"/> </state> diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationProxyTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationProxyTest.kt index 91e5beccee09..a9b77ea6d95b 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationProxyTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationProxyTest.kt @@ -21,20 +21,20 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import android.content.pm.PackageManager +import android.content.pm.verify.domain.DomainVerificationInfo import android.content.pm.verify.domain.DomainVerificationManager import android.content.pm.verify.domain.DomainVerificationRequest -import android.content.pm.verify.domain.DomainVerificationInfo import android.content.pm.verify.domain.DomainVerificationState import android.os.Bundle import android.os.UserHandle import android.util.ArraySet import com.android.server.DeviceIdleInternal +import com.android.server.pm.parsing.pkg.AndroidPackage import com.android.server.pm.verify.domain.DomainVerificationCollector import com.android.server.pm.verify.domain.DomainVerificationManagerInternal 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.pkg.AndroidPackage import com.android.server.testutils.mockThrowOnUnmocked import com.android.server.testutils.whenever import com.google.common.truth.Truth.assertThat @@ -106,20 +106,22 @@ class DomainVerificationProxyTest { when (val pkgName = arguments[0] as String) { TEST_PKG_NAME_TARGET_ONE -> DomainVerificationInfo( TEST_UUID_ONE, pkgName, mapOf( - "example1.com" to DomainVerificationManager.STATE_NO_RESPONSE, - "example2.com" to DomainVerificationManager.STATE_NO_RESPONSE + "example1.com" to DomainVerificationInfo.STATE_NO_RESPONSE, + "example2.com" to DomainVerificationInfo.STATE_NO_RESPONSE ) ) TEST_PKG_NAME_TARGET_TWO -> DomainVerificationInfo( TEST_UUID_TWO, pkgName, mapOf( - "example3.com" to DomainVerificationManager.STATE_NO_RESPONSE, - "example4.com" to DomainVerificationManager.STATE_NO_RESPONSE + "example3.com" to DomainVerificationInfo.STATE_NO_RESPONSE, + "example4.com" to DomainVerificationInfo.STATE_NO_RESPONSE ) ) else -> throw IllegalArgumentException("Unexpected package name $pkgName") } } - whenever(setDomainVerificationStatusInternal(anyInt(), any(), any(), anyInt())) + whenever(setDomainVerificationStatusInternal(anyInt(), any(), any(), anyInt())) { + DomainVerificationManager.STATUS_OK + } } collector = mockThrowOnUnmocked { whenever(collectValidAutoVerifyDomains(any())) { @@ -316,7 +318,7 @@ class DomainVerificationProxyTest { eq(TEST_CALLING_UID_ACCEPT), idCaptor.capture(), hostCaptor.capture(), - eq(DomainVerificationManager.STATE_SUCCESS) + eq(DomainVerificationState.STATE_SUCCESS) ) assertThat(idCaptor.allValues).containsExactly(TEST_UUID_ONE, TEST_UUID_TWO) diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationServiceUtil.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationServiceUtil.kt new file mode 100644 index 000000000000..6859b113298d --- /dev/null +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationServiceUtil.kt @@ -0,0 +1,31 @@ +/* + * 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.test.verify.domain + +import android.annotation.UserIdInt +import com.android.server.pm.verify.domain.DomainVerificationService +import java.util.UUID + +fun DomainVerificationService.setStatus(domainSetId: UUID, domains: Set<String>, state: Int) = + setDomainVerificationStatus(domainSetId, domains.toMutableSet(), state) + +fun DomainVerificationService.setUserSelection( + domainSetId: UUID, + domains: Set<String>, + enabled: Boolean, + @UserIdInt userId: Int +) = setDomainVerificationUserSelection(domainSetId, domains.toMutableSet(), enabled, userId) diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt index 0d8f275be09c..377bae15e2d5 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt @@ -20,28 +20,27 @@ import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.content.pm.PackageUserState -import android.content.pm.verify.domain.DomainVerificationManager import android.content.pm.parsing.component.ParsedActivity import android.content.pm.parsing.component.ParsedIntentInfo +import android.content.pm.verify.domain.DomainVerificationState import android.os.Build import android.os.Process import android.util.ArraySet import android.util.SparseArray import com.android.server.pm.PackageSetting +import com.android.server.pm.parsing.pkg.AndroidPackage 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.parsing.pkg.AndroidPackage import com.android.server.testutils.mockThrowOnUnmocked import com.android.server.testutils.spyThrowOnUnmocked import com.android.server.testutils.whenever import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized -import org.mockito.Mockito +import org.mockito.Mockito.any import org.mockito.Mockito.anyInt import org.mockito.Mockito.anyLong -import org.mockito.Mockito.any import org.mockito.Mockito.anyString import org.mockito.Mockito.eq import org.mockito.Mockito.verify @@ -129,13 +128,13 @@ class DomainVerificationSettingsMutationTest { setDomainVerificationStatus( TEST_UUID, setOf("example.com"), - DomainVerificationManager.STATE_SUCCESS + DomainVerificationState.STATE_SUCCESS ) }, service("setStatusInternalPackageName") { setDomainVerificationStatusInternal( TEST_PKG, - DomainVerificationManager.STATE_SUCCESS, + DomainVerificationState.STATE_SUCCESS, ArraySet(setOf("example.com")) ) }, @@ -144,7 +143,7 @@ class DomainVerificationSettingsMutationTest { TEST_UID, TEST_UUID, setOf("example.com"), - DomainVerificationManager.STATE_SUCCESS + DomainVerificationState.STATE_SUCCESS ) }, service("setLinkHandlingAllowedUserId") { @@ -266,5 +265,7 @@ class DomainVerificationSettingsMutationTest { // This doesn't check for visibility; that's done in the enforcer test whenever(filterAppAccess(anyString(), anyInt(), anyInt())) { false } + whenever(doesUserExist(0)) { true } + whenever(doesUserExist(10)) { true } } } diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt index 0576125748fb..44c1b8f3fbb9 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt @@ -21,6 +21,7 @@ import android.content.pm.PackageManager import android.content.pm.parsing.component.ParsedActivity import android.content.pm.parsing.component.ParsedIntentInfo import android.content.pm.verify.domain.DomainVerificationManager +import android.content.pm.verify.domain.DomainVerificationState import android.content.pm.verify.domain.DomainVerificationUserState import android.os.Build import android.os.PatternMatcher @@ -74,6 +75,8 @@ class DomainVerificationUserStateOverrideTest { }).apply { setConnection(mockThrowOnUnmocked { whenever(filterAppAccess(anyString(), anyInt(), anyInt())) { false } + whenever(doesUserExist(0)) { true } + whenever(doesUserExist(1)) { true } whenever(scheduleWriteSettings()) // Need to provide an internal UID so some permission checks are ignored @@ -154,19 +157,20 @@ class DomainVerificationUserStateOverrideTest { .containsExactly(PKG_TWO) } - @Test(expected = IllegalArgumentException::class) + @Test fun anotherPackageTakeoverFailure() { val service = makeService() // Verify 1 to give it a higher approval level service.setDomainVerificationStatus(UUID_ONE, setOf(DOMAIN_ONE), - DomainVerificationManager.STATE_SUCCESS) + DomainVerificationState.STATE_SUCCESS) assertThat(service.stateFor(PKG_ONE, DOMAIN_ONE)).isEqualTo(STATE_VERIFIED) assertThat(service.getOwnersForDomain(DOMAIN_ONE, USER_ID).map { it.packageName }) .containsExactly(PKG_ONE) // Attempt override by package 2 - service.setDomainVerificationUserSelection(UUID_TWO, setOf(DOMAIN_ONE), true, USER_ID) + assertThat(service.setDomainVerificationUserSelection(UUID_TWO, setOf(DOMAIN_ONE), true, + USER_ID)).isEqualTo(DomainVerificationManager.ERROR_UNABLE_TO_APPROVE) } private fun DomainVerificationService.stateFor(pkgName: String, host: String) = diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/mockingservicestests/src/com/android/server/pm/dex/DexManagerTests.java index ee0a16a70265..2e0cadf264cf 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/dex/DexManagerTests.java @@ -11,7 +11,7 @@ * 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 + * limitations under the License. */ package com.android.server.pm.dex; @@ -28,28 +28,34 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.os.BatteryManager; import android.os.Build; +import android.os.PowerManager; import android.os.UserHandle; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.dx.mockito.inline.extended.ExtendedMockito; +import com.android.dx.mockito.inline.extended.StaticMockitoSession; import com.android.server.pm.Installer; +import com.android.server.pm.PackageManagerService; import dalvik.system.DelegateLastClassLoader; import dalvik.system.PathClassLoader; import dalvik.system.VMRuntime; +import org.junit.After; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; import org.mockito.quality.Strictness; import java.io.File; @@ -69,9 +75,15 @@ public class DexManagerTests { DelegateLastClassLoader.class.getName(); private static final String UNSUPPORTED_CLASS_LOADER_NAME = "unsupported.class_loader"; - @Rule public MockitoRule mockito = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS); + private static final int TEST_BATTERY_LEVEL_CRITICAL = 10; + private static final int TEST_BATTERY_LEVEL_DEFAULT = 80; + + public StaticMockitoSession mMockitoSession; @Mock Installer mInstaller; @Mock IPackageManager mPM; + @Mock BatteryManager mMockBatteryManager; + @Mock PowerManager mMockPowerManager; + private final Object mInstallLock = new Object(); private DexManager mDexManager; @@ -117,7 +129,37 @@ public class DexManagerTests { mSystemServerJarUpdatedContext = new TestData("android", isa, mUser0, DELEGATE_LAST_CLASS_LOADER_NAME); - mDexManager = new DexManager(/*Context*/ null, mPM, /*PackageDexOptimizer*/ null, + // Initialize Static Mocking + + mMockitoSession = ExtendedMockito.mockitoSession() + .initMocks(this) + .strictness(Strictness.LENIENT) + .startMocking(); + + // Mock.... + + mMockBatteryManager = ExtendedMockito.mock(BatteryManager.class); + mMockPowerManager = ExtendedMockito.mock(PowerManager.class); + + setDefaultMockValues(); + + Resources mockResources = ExtendedMockito.mock(Resources.class); + ExtendedMockito.when(mockResources + .getInteger(com.android.internal.R.integer.config_criticalBatteryWarningLevel)) + .thenReturn(15); + + Context mockContext = ExtendedMockito.mock(Context.class); + ExtendedMockito.doReturn(mockResources) + .when(mockContext) + .getResources(); + ExtendedMockito.doReturn(mMockBatteryManager) + .when(mockContext) + .getSystemService(BatteryManager.class); + ExtendedMockito.doReturn(mMockPowerManager) + .when(mockContext) + .getSystemService(PowerManager.class); + + mDexManager = new DexManager(mockContext, mPM, /*PackageDexOptimizer*/ null, mInstaller, mInstallLock); // Foo and Bar are available to user0. @@ -128,6 +170,25 @@ public class DexManagerTests { mDexManager.load(existingPackages); } + @After + public void teardown() throws Exception { + mMockitoSession.finishMocking(); + } + + private void setDefaultMockValues() { + ExtendedMockito.doReturn(BatteryManager.BATTERY_STATUS_DISCHARGING) + .when(mMockBatteryManager) + .getIntProperty(BatteryManager.BATTERY_PROPERTY_STATUS); + + ExtendedMockito.doReturn(TEST_BATTERY_LEVEL_DEFAULT) + .when(mMockBatteryManager) + .getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY); + + ExtendedMockito.doReturn(PowerManager.THERMAL_STATUS_NONE) + .when(mMockPowerManager) + .getCurrentThermalStatus(); + } + @Test public void testNotifyPrimaryUse() { // The main dex file and splits are re-loaded by the app. @@ -633,6 +694,114 @@ public class DexManagerTests { assertNoDclInfo(mSystemServerJarInvalid); } + @Test + public void testInstallScenarioToReasonDefault() { + assertEquals( + PackageManagerService.REASON_INSTALL, + mDexManager.getCompilationReasonForInstallScenario( + PackageManager.INSTALL_SCENARIO_DEFAULT)); + + assertEquals( + PackageManagerService.REASON_INSTALL_FAST, + mDexManager.getCompilationReasonForInstallScenario( + PackageManager.INSTALL_SCENARIO_FAST)); + + assertEquals( + PackageManagerService.REASON_INSTALL_BULK, + mDexManager.getCompilationReasonForInstallScenario( + PackageManager.INSTALL_SCENARIO_BULK)); + + assertEquals( + PackageManagerService.REASON_INSTALL_BULK_SECONDARY, + mDexManager.getCompilationReasonForInstallScenario( + PackageManager.INSTALL_SCENARIO_BULK_SECONDARY)); + } + + @Test + public void testInstallScenarioToReasonThermal() { + ExtendedMockito.doReturn(PowerManager.THERMAL_STATUS_SEVERE) + .when(mMockPowerManager) + .getCurrentThermalStatus(); + + assertEquals( + PackageManagerService.REASON_INSTALL, + mDexManager.getCompilationReasonForInstallScenario( + PackageManager.INSTALL_SCENARIO_DEFAULT)); + + assertEquals( + PackageManagerService.REASON_INSTALL_FAST, + mDexManager.getCompilationReasonForInstallScenario( + PackageManager.INSTALL_SCENARIO_FAST)); + + assertEquals( + PackageManagerService.REASON_INSTALL_BULK_DOWNGRADED, + mDexManager.getCompilationReasonForInstallScenario( + PackageManager.INSTALL_SCENARIO_BULK)); + + assertEquals( + PackageManagerService.REASON_INSTALL_BULK_SECONDARY_DOWNGRADED, + mDexManager.getCompilationReasonForInstallScenario( + PackageManager.INSTALL_SCENARIO_BULK_SECONDARY)); + } + + @Test + public void testInstallScenarioToReasonBatteryDischarging() { + ExtendedMockito.doReturn(TEST_BATTERY_LEVEL_CRITICAL) + .when(mMockBatteryManager) + .getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY); + + assertEquals( + PackageManagerService.REASON_INSTALL, + mDexManager.getCompilationReasonForInstallScenario( + PackageManager.INSTALL_SCENARIO_DEFAULT)); + + assertEquals( + PackageManagerService.REASON_INSTALL_FAST, + mDexManager.getCompilationReasonForInstallScenario( + PackageManager.INSTALL_SCENARIO_FAST)); + + assertEquals( + PackageManagerService.REASON_INSTALL_BULK_DOWNGRADED, + mDexManager.getCompilationReasonForInstallScenario( + PackageManager.INSTALL_SCENARIO_BULK)); + + assertEquals( + PackageManagerService.REASON_INSTALL_BULK_SECONDARY_DOWNGRADED, + mDexManager.getCompilationReasonForInstallScenario( + PackageManager.INSTALL_SCENARIO_BULK_SECONDARY)); + } + + @Test + public void testInstallScenarioToReasonBatteryCharging() { + ExtendedMockito.doReturn(TEST_BATTERY_LEVEL_CRITICAL) + .when(mMockBatteryManager) + .getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY); + + ExtendedMockito.doReturn(BatteryManager.BATTERY_STATUS_CHARGING) + .when(mMockBatteryManager) + .getIntProperty(BatteryManager.BATTERY_PROPERTY_STATUS); + + assertEquals( + PackageManagerService.REASON_INSTALL, + mDexManager.getCompilationReasonForInstallScenario( + PackageManager.INSTALL_SCENARIO_DEFAULT)); + + assertEquals( + PackageManagerService.REASON_INSTALL_FAST, + mDexManager.getCompilationReasonForInstallScenario( + PackageManager.INSTALL_SCENARIO_FAST)); + + assertEquals( + PackageManagerService.REASON_INSTALL_BULK, + mDexManager.getCompilationReasonForInstallScenario( + PackageManager.INSTALL_SCENARIO_BULK)); + + assertEquals( + PackageManagerService.REASON_INSTALL_BULK_SECONDARY, + mDexManager.getCompilationReasonForInstallScenario( + PackageManager.INSTALL_SCENARIO_BULK_SECONDARY)); + } + private void assertSecondaryUse(TestData testData, PackageUseInfo pui, List<String> secondaries, boolean isUsedByOtherApps, int ownerUserId, String[] expectedContexts) { diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/dex/OWNERS b/services/tests/mockingservicestests/src/com/android/server/pm/dex/OWNERS new file mode 100644 index 000000000000..5a4431ee8c89 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/pm/dex/OWNERS @@ -0,0 +1,2 @@ +calin@google.com +ngeoffray@google.com diff --git a/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/OWNERS b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/OWNERS new file mode 100644 index 000000000000..e95633abe79a --- /dev/null +++ b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/OWNERS @@ -0,0 +1 @@ +include /core/java/android/app/admin/OWNERS diff --git a/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest2/OWNERS b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest2/OWNERS new file mode 100644 index 000000000000..e95633abe79a --- /dev/null +++ b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest2/OWNERS @@ -0,0 +1 @@ +include /core/java/android/app/admin/OWNERS diff --git a/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest3/OWNERS b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest3/OWNERS new file mode 100644 index 000000000000..e95633abe79a --- /dev/null +++ b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest3/OWNERS @@ -0,0 +1 @@ +include /core/java/android/app/admin/OWNERS diff --git a/services/tests/servicestests/assets/OwnersTest/OWNERS b/services/tests/servicestests/assets/OwnersTest/OWNERS new file mode 100644 index 000000000000..e95633abe79a --- /dev/null +++ b/services/tests/servicestests/assets/OwnersTest/OWNERS @@ -0,0 +1 @@ +include /core/java/android/app/admin/OWNERS diff --git a/services/tests/servicestests/assets/PolicyVersionUpgraderTest/OWNERS b/services/tests/servicestests/assets/PolicyVersionUpgraderTest/OWNERS new file mode 100644 index 000000000000..e95633abe79a --- /dev/null +++ b/services/tests/servicestests/assets/PolicyVersionUpgraderTest/OWNERS @@ -0,0 +1 @@ +include /core/java/android/app/admin/OWNERS diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java index a38745f2a66e..d9af51f819c3 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java @@ -42,7 +42,6 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.os.FileUtils; -import android.security.keystore.AndroidKeyStoreSecretKey; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; import android.security.keystore.recovery.KeyChainSnapshot; @@ -109,7 +108,7 @@ public class KeySyncTaskTest { private RecoverySnapshotStorage mRecoverySnapshotStorage; private RecoverableKeyStoreDb mRecoverableKeyStoreDb; private File mDatabaseFile; - private AndroidKeyStoreSecretKey mWrappingKey; + private SecretKey mWrappingKey; private PlatformEncryptionKey mEncryptKey; private KeySyncTask mKeySyncTask; @@ -848,7 +847,7 @@ public class KeySyncTaskTest { return keyGenerator.generateKey(); } - private AndroidKeyStoreSecretKey generateAndroidKeyStoreKey() throws Exception { + private SecretKey generateAndroidKeyStoreKey() throws Exception { KeyGenerator keyGenerator = KeyGenerator.getInstance( KEY_ALGORITHM, ANDROID_KEY_STORE_PROVIDER); @@ -857,7 +856,7 @@ public class KeySyncTaskTest { .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .build()); - return (AndroidKeyStoreSecretKey) keyGenerator.generateKey(); + return keyGenerator.generateKey(); } private static byte[] utf8Bytes(String s) { diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java index c295177814b0..64130266b2c4 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java @@ -23,7 +23,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import android.content.Context; -import android.security.keystore.AndroidKeyStoreSecretKey; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; @@ -45,6 +44,7 @@ import java.util.Random; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; import javax.crypto.spec.GCMParameterSpec; @SmallTest @@ -77,7 +77,7 @@ public class RecoverableKeyGeneratorTest { mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME); mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context); - AndroidKeyStoreSecretKey platformKey = generatePlatformKey(); + SecretKey platformKey = generatePlatformKey(); mPlatformKey = new PlatformEncryptionKey(TEST_GENERATION_ID, platformKey); mDecryptKey = new PlatformDecryptionKey(TEST_GENERATION_ID, platformKey); mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(mRecoverableKeyStoreDb); @@ -168,7 +168,7 @@ public class RecoverableKeyGeneratorTest { assertArrayEquals(rawMaterial, unwrappedMaterial); } - private AndroidKeyStoreSecretKey generatePlatformKey() throws Exception { + private SecretKey generatePlatformKey() throws Exception { KeyGenerator keyGenerator = KeyGenerator.getInstance( KEY_ALGORITHM, ANDROID_KEY_STORE_PROVIDER); @@ -177,7 +177,7 @@ public class RecoverableKeyGeneratorTest { .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .build()); - return (AndroidKeyStoreSecretKey) keyGenerator.generateKey(); + return keyGenerator.generateKey(); } private static byte[] randomBytes(int n) { diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java index b65e4877da50..a227cd3c6f5c 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java @@ -45,7 +45,6 @@ import android.content.Intent; import android.os.Binder; import android.os.ServiceSpecificException; import android.os.UserHandle; -import android.security.keystore.AndroidKeyStoreSecretKey; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; import android.security.keystore.recovery.KeyChainProtectionParams; @@ -1311,7 +1310,7 @@ public class RecoverableKeyStoreManagerTest { mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false); } - private AndroidKeyStoreSecretKey generateAndroidKeyStoreKey() throws Exception { + private SecretKey generateAndroidKeyStoreKey() throws Exception { KeyGenerator keyGenerator = KeyGenerator.getInstance( KEY_ALGORITHM, ANDROID_KEY_STORE_PROVIDER); @@ -1320,6 +1319,6 @@ public class RecoverableKeyStoreManagerTest { .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .build()); - return (AndroidKeyStoreSecretKey) keyGenerator.generateKey(); + return keyGenerator.generateKey(); } } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/WrappedKeyTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/WrappedKeyTest.java index 9813ab74721e..60052f7114b3 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/WrappedKeyTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/WrappedKeyTest.java @@ -21,7 +21,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import android.security.keystore.AndroidKeyStoreSecretKey; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; import android.util.Pair; @@ -117,7 +116,7 @@ public class WrappedKeyTest { @Test public void decryptWrappedKeys_decryptsWrappedKeys_nullMetadata() throws Exception { String alias = "karlin"; - AndroidKeyStoreSecretKey platformKey = generateAndroidKeyStoreKey(); + SecretKey platformKey = generateAndroidKeyStoreKey(); SecretKey appKey = generateKey(); WrappedKey wrappedKey = WrappedKey.fromSecretKey( new PlatformEncryptionKey(GENERATION_ID, platformKey), appKey, NULL_METADATA); @@ -136,7 +135,7 @@ public class WrappedKeyTest { @Test public void decryptWrappedKeys_decryptsWrappedKeys_nonNullMetadata() throws Exception { String alias = "karlin"; - AndroidKeyStoreSecretKey platformKey = generateAndroidKeyStoreKey(); + SecretKey platformKey = generateAndroidKeyStoreKey(); SecretKey appKey = generateKey(); WrappedKey wrappedKey = WrappedKey.fromSecretKey( new PlatformEncryptionKey(GENERATION_ID, platformKey), appKey, NON_NULL_METADATA); @@ -155,7 +154,7 @@ public class WrappedKeyTest { @Test public void decryptWrappedKeys_doesNotDieIfSomeKeysAreUnwrappable() throws Exception { String alias = "karlin"; - AndroidKeyStoreSecretKey platformKey = generateAndroidKeyStoreKey(); + SecretKey platformKey = generateAndroidKeyStoreKey(); SecretKey appKey = generateKey(); WrappedKey wrappedKey = WrappedKey.fromSecretKey( new PlatformEncryptionKey(GENERATION_ID, platformKey), appKey, NULL_METADATA); @@ -171,7 +170,7 @@ public class WrappedKeyTest { @Test public void decryptWrappedKeys_throwsIfPlatformKeyGenerationIdDoesNotMatch() throws Exception { - AndroidKeyStoreSecretKey platformKey = generateAndroidKeyStoreKey(); + SecretKey platformKey = generateAndroidKeyStoreKey(); WrappedKey wrappedKey = WrappedKey.fromSecretKey( new PlatformEncryptionKey(GENERATION_ID, platformKey), generateKey(), /*metadata=*/ null); @@ -197,7 +196,7 @@ public class WrappedKeyTest { return keyGenerator.generateKey(); } - private AndroidKeyStoreSecretKey generateAndroidKeyStoreKey() throws Exception { + private SecretKey generateAndroidKeyStoreKey() throws Exception { KeyGenerator keyGenerator = KeyGenerator.getInstance( KEY_ALGORITHM, ANDROID_KEY_STORE_PROVIDER); @@ -207,6 +206,6 @@ public class WrappedKeyTest { .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .build()); - return (AndroidKeyStoreSecretKey) keyGenerator.generateKey(); + return keyGenerator.generateKey(); } } diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/ArtStatsLogUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/ArtStatsLogUtilsTest.java index 3ab34484ce25..e605d755183f 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/ArtStatsLogUtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/ArtStatsLogUtilsTest.java @@ -106,7 +106,8 @@ public final class ArtStatsLogUtilsTest { RESULT_CODE); // Assert - verifyWrites(ArtStatsLog.ART_DATUM_REPORTED__DEX_METADATA_TYPE__PROFILE_AND_VDEX); + verifyWrites(ArtStatsLog. + ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_PROFILE_AND_VDEX); } finally { deleteSliently(dexMetadataPath); deleteSliently(apk); @@ -135,7 +136,8 @@ public final class ArtStatsLogUtilsTest { RESULT_CODE); // Assert - verifyWrites(ArtStatsLog.ART_DATUM_REPORTED__DEX_METADATA_TYPE__PROFILE); + verifyWrites(ArtStatsLog. + ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_PROFILE); } finally { deleteSliently(dexMetadataPath); deleteSliently(apk); @@ -164,7 +166,8 @@ public final class ArtStatsLogUtilsTest { RESULT_CODE); // Assert - verifyWrites(ArtStatsLog.ART_DATUM_REPORTED__DEX_METADATA_TYPE__VDEX); + verifyWrites(ArtStatsLog. + ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_VDEX); } finally { deleteSliently(dexMetadataPath); deleteSliently(apk); @@ -191,7 +194,8 @@ public final class ArtStatsLogUtilsTest { RESULT_CODE); // Assert - verifyWrites(ArtStatsLog.ART_DATUM_REPORTED__DEX_METADATA_TYPE__NONE_DEX_METADATA); + verifyWrites(ArtStatsLog. + ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_NONE); } finally { deleteSliently(apk); } @@ -219,7 +223,8 @@ public final class ArtStatsLogUtilsTest { RESULT_CODE); // Assert - verifyWrites(ArtStatsLog.ART_DATUM_REPORTED__DEX_METADATA_TYPE__UNKNOWN_DEX_METADATA); + verifyWrites(ArtStatsLog. + ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_UNKNOWN); } finally { deleteSliently(dexMetadataPath); deleteSliently(apk); diff --git a/services/translation/java/com/android/server/translation/TranslationManagerService.java b/services/translation/java/com/android/server/translation/TranslationManagerService.java index 8874e0afd716..72e1e33491ff 100644 --- a/services/translation/java/com/android/server/translation/TranslationManagerService.java +++ b/services/translation/java/com/android/server/translation/TranslationManagerService.java @@ -27,6 +27,7 @@ import android.content.Context; import android.content.pm.PackageManager; import android.os.Binder; import android.os.IBinder; +import android.os.IRemoteCallback; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; @@ -203,6 +204,28 @@ public final class TranslationManagerService } } + @Override + public void registerUiTranslationStateCallback(IRemoteCallback callback, int userId) { + TranslationManagerServiceImpl service; + synchronized (mLock) { + service = getServiceForUserLocked(userId); + } + if (service != null) { + service.registerUiTranslationStateCallback(callback, Binder.getCallingUid()); + } + } + + @Override + public void unregisterUiTranslationStateCallback(IRemoteCallback callback, int userId) { + TranslationManagerServiceImpl service; + synchronized (mLock) { + service = getServiceForUserLocked(userId); + } + if (service != null) { + service.unregisterUiTranslationStateCallback(callback); + } + } + /** * Dump the service state into the given stream. You run "adb shell dumpsys translation". */ diff --git a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java index ab6ac12c90fa..1ca07cb8d928 100644 --- a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java +++ b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java @@ -17,17 +17,24 @@ package com.android.server.translation; import static android.view.translation.TranslationManager.STATUS_SYNC_CALL_SUCCESS; +import static android.view.translation.UiTranslationManager.EXTRA_SOURCE_LOCALE; +import static android.view.translation.UiTranslationManager.EXTRA_STATE; +import static android.view.translation.UiTranslationManager.EXTRA_TARGET_LOCALE; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; +import android.os.Bundle; import android.os.IBinder; +import android.os.IRemoteCallback; +import android.os.RemoteCallbackList; import android.os.RemoteException; import android.service.translation.TranslationServiceInfo; import android.util.Slog; import android.view.autofill.AutofillId; +import android.view.inputmethod.InputMethodInfo; import android.view.translation.TranslationSpec; import android.view.translation.UiTranslationManager.UiTranslationState; @@ -36,6 +43,7 @@ import com.android.internal.os.IResultReceiver; import com.android.internal.util.SyncResultReceiver; import com.android.server.LocalServices; import com.android.server.infra.AbstractPerUserSystemService; +import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal.ActivityTokens; @@ -174,5 +182,50 @@ final class TranslationManagerServiceImpl extends } catch (RemoteException e) { Slog.w(TAG, "Update UiTranslationState fail: " + e); } + invokeCallbacks(state, sourceSpec, destSpec); } + + private void invokeCallbacks( + int state, TranslationSpec sourceSpec, TranslationSpec targetSpec) { + Bundle res = new Bundle(); + res.putInt(EXTRA_STATE, state); + // TODO(177500482): Store the locale pair so it can be sent for RESUME events. + if (sourceSpec != null) { + res.putString(EXTRA_SOURCE_LOCALE, sourceSpec.getLanguage()); + res.putString(EXTRA_TARGET_LOCALE, targetSpec.getLanguage()); + } + // TODO(177500482): Only support the *current* Input Method. + List<InputMethodInfo> enabledInputMethods = + LocalServices.getService(InputMethodManagerInternal.class) + .getEnabledInputMethodListAsUser(mUserId); + mCallbacks.broadcast((callback, uid) -> { + // Code here is non-optimal since it's temporary.. + boolean isIme = false; + for (InputMethodInfo inputMethod : enabledInputMethods) { + if ((int) uid == inputMethod.getServiceInfo().applicationInfo.uid) { + isIme = true; + } + } + // TODO(177500482): Invoke it for the application being translated too. + if (!isIme) { + return; + } + try { + callback.sendResult(res); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to invoke UiTranslationStateCallback: " + e); + } + }); + } + + public void registerUiTranslationStateCallback(IRemoteCallback callback, int sourceUid) { + mCallbacks.register(callback, sourceUid); + // TODO(177500482): trigger the callback here if we're already translating the UI. + } + + public void unregisterUiTranslationStateCallback(IRemoteCallback callback) { + mCallbacks.unregister(callback); + } + + private final RemoteCallbackList<IRemoteCallback> mCallbacks = new RemoteCallbackList<>(); } diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 45eafa45c78d..47fbe1350a79 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -2974,6 +2974,18 @@ public class CarrierConfigManager { public static final String KEY_RTT_SUPPORTED_FOR_VT_BOOL = "rtt_supported_for_vt_bool"; /** + * Indicates if the carrier supports upgrading a call that was previously an RTT call to VT. + */ + public static final String KEY_VT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_RTT_CALL_BOOL = + "vt_upgrade_supported_for_downgraded_rtt_call"; + + /** + * Indicates if the carrier supports upgrading a call that was previously a VT call to RTT. + */ + public static final String KEY_RTT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_VT_CALL_BOOL = + "rtt_upgrade_supported_for_downgraded_vt_call"; + + /** * Indicates if the carrier supports upgrading a voice call to an RTT call during the call. */ public static final String KEY_RTT_UPGRADE_SUPPORTED_BOOL = "rtt_upgrade_supported_bool"; @@ -5156,6 +5168,8 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_TTY_SUPPORTED_BOOL, true); sDefaults.putBoolean(KEY_HIDE_TTY_HCO_VCO_WITH_RTT_BOOL, false); sDefaults.putBoolean(KEY_RTT_SUPPORTED_WHILE_ROAMING_BOOL, false); + sDefaults.putBoolean(KEY_RTT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_VT_CALL_BOOL, true); + sDefaults.putBoolean(KEY_VT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_RTT_CALL_BOOL, true); sDefaults.putBoolean(KEY_DISABLE_CHARGE_INDICATION_BOOL, false); sDefaults.putBoolean(KEY_SUPPORT_NO_REPLY_TIMER_FOR_CFNRY_BOOL, true); sDefaults.putStringArray(KEY_FEATURE_ACCESS_CODES_STRING_ARRAY, null); diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index b30dd2697645..96af172e489e 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -54,6 +54,7 @@ import android.telephony.TelephonyHistogram; import android.telephony.VisualVoicemailSmsFilterSettings; import android.telephony.emergency.EmergencyNumber; import android.telephony.ims.RcsClientConfiguration; +import android.telephony.ims.RcsContactUceCapability; import android.telephony.ims.aidl.IImsCapabilityCallback; import android.telephony.ims.aidl.IImsConfig; import android.telephony.ims.aidl.IImsConfigCallback; @@ -2374,6 +2375,41 @@ interface ITelephony { void setDeviceUceEnabled(boolean isEnabled); /** + * Add feature tags to the IMS registration being tracked by UCE and potentially + * generate a new PUBLISH to the network. + * Note: This is designed for a SHELL command only. + */ + RcsContactUceCapability addUceRegistrationOverrideShell(int subId, in List<String> featureTags); + + /** + * Remove feature tags from the IMS registration being tracked by UCE and potentially + * generate a new PUBLISH to the network. + * Note: This is designed for a SHELL command only. + */ + RcsContactUceCapability removeUceRegistrationOverrideShell(int subId, + in List<String> featureTags); + + /** + * Clear overridden feature tags in the IMS registration being tracked by UCE and potentially + * generate a new PUBLISH to the network. + * Note: This is designed for a SHELL command only. + */ + RcsContactUceCapability clearUceRegistrationOverrideShell(int subId); + + /** + * Get the latest RcsContactUceCapability structure that is used in SIP PUBLISH procedures. + * Note: This is designed for a SHELL command only. + */ + RcsContactUceCapability getLatestRcsContactUceCapabilityShell(int subId); + + /** + * Returns the last PIDF XML sent to the network during the last PUBLISH or "none" if the + * device does not have an active PUBLISH. + * Note: This is designed for a SHELL command only. + */ + String getLastUcePidfXmlShell(int subId); + + /** * Set a SignalStrengthUpdateRequest to receive notification when Signal Strength breach the * specified thresholds. */ diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 58d8ee512dc2..a0b13c89ff49 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -23,6 +23,8 @@ import static android.content.Intent.ACTION_USER_ADDED; import static android.content.Intent.ACTION_USER_REMOVED; import static android.content.Intent.ACTION_USER_UNLOCKED; import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED; +import static android.content.pm.PackageManager.FEATURE_WIFI; +import static android.content.pm.PackageManager.FEATURE_WIFI_DIRECT; import static android.content.pm.PackageManager.GET_PERMISSIONS; import static android.content.pm.PackageManager.MATCH_ANY_USER; import static android.content.pm.PackageManager.PERMISSION_DENIED; @@ -35,11 +37,14 @@ import static android.net.ConnectivityManager.NETID_UNSET; import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF; import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC; import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; +import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT; +import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE; import static android.net.ConnectivityManager.TYPE_ETHERNET; import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA; import static android.net.ConnectivityManager.TYPE_MOBILE_MMS; import static android.net.ConnectivityManager.TYPE_MOBILE_SUPL; +import static android.net.ConnectivityManager.TYPE_PROXY; import static android.net.ConnectivityManager.TYPE_VPN; import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_DNS; @@ -183,7 +188,7 @@ import android.net.INetd; import android.net.INetworkMonitor; import android.net.INetworkMonitorCallbacks; import android.net.INetworkPolicyListener; -import android.net.IOnSetOemNetworkPreferenceListener; +import android.net.IOnCompleteListener; import android.net.IQosCallback; import android.net.InetAddresses; import android.net.InterfaceConfigurationParcel; @@ -378,6 +383,11 @@ public class ConnectivityServiceTest { // Set a non-zero value to verify the flow to set tcp init rwnd value. private static final int TEST_TCP_INIT_RWND = 60; + // Used for testing the per-work-profile default network. + private static final int TEST_APP_ID = 103; + private static final int TEST_WORK_PROFILE_USER_ID = 2; + private static final int TEST_WORK_PROFILE_APP_UID = + UserHandle.getUid(TEST_WORK_PROFILE_USER_ID, TEST_APP_ID); private static final String CLAT_PREFIX = "v4-"; private static final String MOBILE_IFNAME = "test_rmnet_data0"; private static final String WIFI_IFNAME = "test_wlan0"; @@ -421,6 +431,7 @@ public class ConnectivityServiceTest { private VpnManagerService mVpnManagerService; private TestNetworkCallback mDefaultNetworkCallback; private TestNetworkCallback mSystemDefaultNetworkCallback; + private TestNetworkCallback mProfileDefaultNetworkCallback; // State variables required to emulate NetworkPolicyManagerService behaviour. private int mUidRules = RULE_NONE; @@ -542,13 +553,26 @@ public class ConnectivityServiceTest { return super.getSystemService(name); } + final HashMap<UserHandle, UserManager> mUserManagers = new HashMap<>(); @Override public Context createContextAsUser(UserHandle user, int flags) { final Context asUser = mock(Context.class, AdditionalAnswers.delegatesTo(this)); doReturn(user).when(asUser).getUser(); + doAnswer((inv) -> { + final UserManager um = mUserManagers.computeIfAbsent(user, + u -> mock(UserManager.class, AdditionalAnswers.delegatesTo(mUserManager))); + return um; + }).when(asUser).getSystemService(Context.USER_SERVICE); return asUser; } + public void setWorkProfile(@NonNull final UserHandle userHandle, boolean value) { + // This relies on all contexts for a given user returning the same UM mock + final UserManager umMock = createContextAsUser(userHandle, 0 /* flags */) + .getSystemService(UserManager.class); + doReturn(value).when(umMock).isManagedProfile(); + } + @Override public ContentResolver getContentResolver() { return mContentResolver; @@ -1408,17 +1432,36 @@ public class ConnectivityServiceTest { fail("ConditionVariable was blocked for more than " + TIMEOUT_MS + "ms"); } - private void registerNetworkCallbackAsUid(NetworkRequest request, NetworkCallback callback, - int uid) { + private <T> T doAsUid(final int uid, @NonNull final Supplier<T> what) { when(mDeps.getCallingUid()).thenReturn(uid); try { - mCm.registerNetworkCallback(request, callback); - waitForIdle(); + return what.get(); } finally { returnRealCallingUid(); } } + private void doAsUid(final int uid, @NonNull final Runnable what) { + doAsUid(uid, () -> { + what.run(); return Void.TYPE; + }); + } + + private void registerNetworkCallbackAsUid(NetworkRequest request, NetworkCallback callback, + int uid) { + doAsUid(uid, () -> { + mCm.registerNetworkCallback(request, callback); + }); + } + + private void registerDefaultNetworkCallbackAsUid(@NonNull final NetworkCallback callback, + final int uid) { + doAsUid(uid, () -> { + mCm.registerDefaultNetworkCallback(callback); + waitForIdle(); + }); + } + private static final int PRIMARY_USER = 0; private static final int APP1_UID = UserHandle.getUid(PRIMARY_USER, 10100); private static final int APP2_UID = UserHandle.getUid(PRIMARY_USER, 10101); @@ -1465,6 +1508,9 @@ public class ConnectivityServiceTest { Looper.prepare(); } mockDefaultPackages(); + mockHasSystemFeature(FEATURE_WIFI, true); + mockHasSystemFeature(FEATURE_WIFI_DIRECT, true); + doReturn(true).when(mTelephonyManager).isDataCapable(); FakeSettingsProvider.clearSettingsProvider(); mServiceContext = new MockContext(InstrumentationRegistry.getContext(), @@ -1517,10 +1563,7 @@ public class ConnectivityServiceTest { } private ConnectivityService.Dependencies makeDependencies() { - doReturn(TEST_TCP_INIT_RWND).when(mSystemProperties) - .getInt("net.tcp.default_init_rwnd", 0); doReturn(false).when(mSystemProperties).getBoolean("ro.radio.noril", false); - doNothing().when(mSystemProperties).setTcpInitRwnd(anyInt()); final ConnectivityService.Dependencies deps = mock(ConnectivityService.Dependencies.class); doReturn(mCsHandlerThread).when(deps).makeHandlerThread(); doReturn(mNetIdManager).when(deps).makeNetIdManager(); @@ -1578,6 +1621,7 @@ public class ConnectivityServiceTest { @After public void tearDown() throws Exception { unregisterDefaultNetworkCallbacks(); + maybeTearDownEnterpriseNetwork(); setAlwaysOnNetworks(false); if (mCellNetworkAgent != null) { mCellNetworkAgent.disconnect(); @@ -1788,7 +1832,8 @@ public class ConnectivityServiceTest { assertTrue(mCm.isNetworkSupported(TYPE_WIFI)); assertTrue(mCm.isNetworkSupported(TYPE_MOBILE)); assertTrue(mCm.isNetworkSupported(TYPE_MOBILE_MMS)); - assertFalse(mCm.isNetworkSupported(TYPE_MOBILE_FOTA)); + assertTrue(mCm.isNetworkSupported(TYPE_MOBILE_FOTA)); + assertFalse(mCm.isNetworkSupported(TYPE_PROXY)); // Check that TYPE_ETHERNET is supported. Unlike the asserts above, which only validate our // mocks, this assert exercises the ConnectivityService code path that ensures that @@ -7997,7 +8042,6 @@ public class ConnectivityServiceTest { // Switching default network updates TCP buffer sizes. verifyTcpBufferSizeChange(ConnectivityService.DEFAULT_TCP_BUFFER_SIZES); - verify(mSystemProperties, times(1)).setTcpInitRwnd(eq(TEST_TCP_INIT_RWND)); // Add an IPv4 address. Expect prefix discovery to be stopped. Netd doesn't tell us that // the NAT64 prefix was removed because one was never discovered. cellLp.addLinkAddress(myIpv4); @@ -8483,14 +8527,12 @@ public class ConnectivityServiceTest { mCellNetworkAgent.connect(false); networkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); verifyTcpBufferSizeChange(ConnectivityService.DEFAULT_TCP_BUFFER_SIZES); - verify(mSystemProperties, times(1)).setTcpInitRwnd(eq(TEST_TCP_INIT_RWND)); // Change link Properties should have updated tcp buffer size. LinkProperties lp = new LinkProperties(); lp.setTcpBufferSizes(testTcpBufferSizes); mCellNetworkAgent.sendLinkProperties(lp); networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); verifyTcpBufferSizeChange(testTcpBufferSizes); - verify(mSystemProperties, times(2)).setTcpInitRwnd(eq(TEST_TCP_INIT_RWND)); // Clean up. mCellNetworkAgent.disconnect(); networkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); @@ -10118,9 +10160,12 @@ public class ConnectivityServiceTest { Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED); mSystemDefaultNetworkCallback = new TestNetworkCallback(); mDefaultNetworkCallback = new TestNetworkCallback(); + mProfileDefaultNetworkCallback = new TestNetworkCallback(); mCm.registerSystemDefaultNetworkCallback(mSystemDefaultNetworkCallback, new Handler(ConnectivityThread.getInstanceLooper())); mCm.registerDefaultNetworkCallback(mDefaultNetworkCallback); + registerDefaultNetworkCallbackAsUid(mProfileDefaultNetworkCallback, + TEST_WORK_PROFILE_APP_UID); mServiceContext.setPermission( Manifest.permission.NETWORK_SETTINGS, PERMISSION_DENIED); } @@ -10132,6 +10177,9 @@ public class ConnectivityServiceTest { if (null != mSystemDefaultNetworkCallback) { mCm.unregisterNetworkCallback(mSystemDefaultNetworkCallback); } + if (null != mProfileDefaultNetworkCallback) { + mCm.unregisterNetworkCallback(mProfileDefaultNetworkCallback); + } } private void setupMultipleDefaultNetworksForOemNetworkPreferenceNotCurrentUidTest( @@ -10187,7 +10235,7 @@ public class ConnectivityServiceTest { oemPrefListener.expectOnComplete(); } - private static class TestOemListenerCallback implements IOnSetOemNetworkPreferenceListener { + private static class TestOemListenerCallback implements IOnCompleteListener { final CompletableFuture<Object> mDone = new CompletableFuture<>(); @Override @@ -11220,4 +11268,400 @@ public class ConnectivityServiceTest { mCellNetworkAgent.disconnect(); bestMatchingCb.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); } + + private UidRangeParcel[] uidRangeFor(final UserHandle handle) { + UidRange range = UidRange.createForUser(handle); + return new UidRangeParcel[] { new UidRangeParcel(range.start, range.stop) }; + } + + private static class TestOnCompleteListener implements Runnable { + final class OnComplete {} + final ArrayTrackRecord<OnComplete>.ReadHead mHistory = + new ArrayTrackRecord<OnComplete>().newReadHead(); + + @Override + public void run() { + mHistory.add(new OnComplete()); + } + + public void expectOnComplete() { + assertNotNull(mHistory.poll(TIMEOUT_MS, it -> true)); + } + } + + private TestNetworkAgentWrapper makeEnterpriseNetworkAgent() throws Exception { + final NetworkCapabilities workNc = new NetworkCapabilities(); + workNc.addCapability(NET_CAPABILITY_ENTERPRISE); + workNc.removeCapability(NET_CAPABILITY_NOT_RESTRICTED); + return new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, new LinkProperties(), workNc); + } + + private TestNetworkCallback mEnterpriseCallback; + private UserHandle setupEnterpriseNetwork() { + final UserHandle userHandle = UserHandle.of(TEST_WORK_PROFILE_USER_ID); + mServiceContext.setWorkProfile(userHandle, true); + + // File a request to avoid the enterprise network being disconnected as soon as the default + // request goes away – it would make impossible to test that networkRemoveUidRanges + // is called, as the network would disconnect first for lack of a request. + mEnterpriseCallback = new TestNetworkCallback(); + final NetworkRequest keepUpRequest = new NetworkRequest.Builder() + .addCapability(NET_CAPABILITY_ENTERPRISE) + .build(); + mCm.requestNetwork(keepUpRequest, mEnterpriseCallback); + return userHandle; + } + + private void maybeTearDownEnterpriseNetwork() { + if (null != mEnterpriseCallback) { + mCm.unregisterNetworkCallback(mEnterpriseCallback); + } + } + + /** + * Make sure per-profile networking preference behaves as expected when the enterprise network + * goes up and down while the preference is active. Make sure they behave as expected whether + * there is a general default network or not. + */ + @Test + public void testPreferenceForUserNetworkUpDown() throws Exception { + final InOrder inOrder = inOrder(mMockNetd); + final UserHandle testHandle = setupEnterpriseNetwork(); + registerDefaultNetworkCallbacks(); + + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + + mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + mProfileDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + inOrder.verify(mMockNetd).networkCreatePhysical(mCellNetworkAgent.getNetwork().netId, + INetd.PERMISSION_NONE); + + final TestOnCompleteListener listener = new TestOnCompleteListener(); + mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_ENTERPRISE, + r -> r.run(), listener); + listener.expectOnComplete(); + + // Setting a network preference for this user will create a new set of routing rules for + // the UID range that corresponds to this user, so as to define the default network + // for these apps separately. This is true because the multi-layer request relevant to + // this UID range contains a TRACK_DEFAULT, so the range will be moved through UID-specific + // rules to the correct network – in this case the system default network. The case where + // the default network for the profile happens to be the same as the system default + // is not handled specially, the rules are always active as long as a preference is set. + inOrder.verify(mMockNetd).networkAddUidRanges(mCellNetworkAgent.getNetwork().netId, + uidRangeFor(testHandle)); + + // The enterprise network is not ready yet. + assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback, + mProfileDefaultNetworkCallback); + + final TestNetworkAgentWrapper workAgent = makeEnterpriseNetworkAgent(); + workAgent.connect(false); + + mProfileDefaultNetworkCallback.expectAvailableCallbacksUnvalidated(workAgent); + mSystemDefaultNetworkCallback.assertNoCallback(); + mDefaultNetworkCallback.assertNoCallback(); + inOrder.verify(mMockNetd).networkCreatePhysical(workAgent.getNetwork().netId, + INetd.PERMISSION_SYSTEM); + inOrder.verify(mMockNetd).networkAddUidRanges(workAgent.getNetwork().netId, + uidRangeFor(testHandle)); + inOrder.verify(mMockNetd).networkRemoveUidRanges(mCellNetworkAgent.getNetwork().netId, + uidRangeFor(testHandle)); + + // Make sure changes to the work agent send callbacks to the app in the work profile, but + // not to the other apps. + workAgent.setNetworkValid(true /* isStrictMode */); + workAgent.mNetworkMonitor.forceReevaluation(Process.myUid()); + mProfileDefaultNetworkCallback.expectCapabilitiesThat(workAgent, + nc -> nc.hasCapability(NET_CAPABILITY_VALIDATED) + && nc.hasCapability(NET_CAPABILITY_ENTERPRISE)); + assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback); + + workAgent.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED); + mProfileDefaultNetworkCallback.expectCapabilitiesThat(workAgent, nc -> + nc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)); + assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback); + + // Conversely, change a capability on the system-wide default network and make sure + // that only the apps outside of the work profile receive the callbacks. + mCellNetworkAgent.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED); + mSystemDefaultNetworkCallback.expectCapabilitiesThat(mCellNetworkAgent, nc -> + nc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)); + mDefaultNetworkCallback.expectCapabilitiesThat(mCellNetworkAgent, nc -> + nc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)); + mProfileDefaultNetworkCallback.assertNoCallback(); + + // Disconnect and reconnect the system-wide default network and make sure that the + // apps on this network see the appropriate callbacks, and the app on the work profile + // doesn't because it continues to use the enterprise network. + mCellNetworkAgent.disconnect(); + mSystemDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + mDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + mProfileDefaultNetworkCallback.assertNoCallback(); + waitForIdle(); + inOrder.verify(mMockNetd).networkDestroy(mCellNetworkAgent.getNetwork().netId); + + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + mProfileDefaultNetworkCallback.assertNoCallback(); + inOrder.verify(mMockNetd).networkCreatePhysical(mCellNetworkAgent.getNetwork().netId, + INetd.PERMISSION_NONE); + + // When the agent disconnects, test that the app on the work profile falls back to the + // default network. + workAgent.disconnect(); + mProfileDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, workAgent); + mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); + assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback); + inOrder.verify(mMockNetd).networkAddUidRanges(mCellNetworkAgent.getNetwork().netId, + uidRangeFor(testHandle)); + waitForIdle(); + inOrder.verify(mMockNetd).networkDestroy(workAgent.getNetwork().netId); + + mCellNetworkAgent.disconnect(); + mSystemDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + mDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + mProfileDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + + waitForIdle(); + inOrder.verify(mMockNetd).networkDestroy(mCellNetworkAgent.getNetwork().netId); + + // If the control comes here, callbacks seem to behave correctly in the presence of + // a default network when the enterprise network goes up and down. Now, make sure they + // also behave correctly in the absence of a system-wide default network. + final TestNetworkAgentWrapper workAgent2 = makeEnterpriseNetworkAgent(); + workAgent2.connect(false); + + mProfileDefaultNetworkCallback.expectAvailableCallbacksUnvalidated(workAgent2); + assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback); + inOrder.verify(mMockNetd).networkCreatePhysical(workAgent2.getNetwork().netId, + INetd.PERMISSION_SYSTEM); + inOrder.verify(mMockNetd).networkAddUidRanges(workAgent2.getNetwork().netId, + uidRangeFor(testHandle)); + + workAgent2.setNetworkValid(true /* isStrictMode */); + workAgent2.mNetworkMonitor.forceReevaluation(Process.myUid()); + mProfileDefaultNetworkCallback.expectCapabilitiesThat(workAgent2, + nc -> nc.hasCapability(NET_CAPABILITY_ENTERPRISE) + && !nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)); + assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback); + inOrder.verify(mMockNetd, never()).networkAddUidRanges(anyInt(), any()); + + // When the agent disconnects, test that the app on the work profile falls back to the + // default network. + workAgent2.disconnect(); + mProfileDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, workAgent2); + assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback); + waitForIdle(); + inOrder.verify(mMockNetd).networkDestroy(workAgent2.getNetwork().netId); + + assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback, + mProfileDefaultNetworkCallback); + + // Callbacks will be unregistered by tearDown() + } + + /** + * Test that, in a given networking context, calling setPreferenceForUser to set per-profile + * defaults on then off works as expected. + */ + @Test + public void testSetPreferenceForUserOnOff() throws Exception { + final InOrder inOrder = inOrder(mMockNetd); + final UserHandle testHandle = setupEnterpriseNetwork(); + + // Connect both a regular cell agent and an enterprise network first. + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + + final TestNetworkAgentWrapper workAgent = makeEnterpriseNetworkAgent(); + workAgent.connect(true); + + final TestOnCompleteListener listener = new TestOnCompleteListener(); + mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_ENTERPRISE, + r -> r.run(), listener); + listener.expectOnComplete(); + inOrder.verify(mMockNetd).networkCreatePhysical(mCellNetworkAgent.getNetwork().netId, + INetd.PERMISSION_NONE); + inOrder.verify(mMockNetd).networkAddUidRanges(workAgent.getNetwork().netId, + uidRangeFor(testHandle)); + + registerDefaultNetworkCallbacks(); + + mSystemDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); + mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); + mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(workAgent); + + mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_DEFAULT, + r -> r.run(), listener); + listener.expectOnComplete(); + + mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); + assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback); + inOrder.verify(mMockNetd).networkRemoveUidRanges(workAgent.getNetwork().netId, + uidRangeFor(testHandle)); + + workAgent.disconnect(); + mCellNetworkAgent.disconnect(); + + // Callbacks will be unregistered by tearDown() + } + + /** + * Test per-profile default networks for two different profiles concurrently. + */ + @Test + public void testSetPreferenceForTwoProfiles() throws Exception { + final InOrder inOrder = inOrder(mMockNetd); + final UserHandle testHandle2 = setupEnterpriseNetwork(); + final UserHandle testHandle4 = UserHandle.of(TEST_WORK_PROFILE_USER_ID + 2); + mServiceContext.setWorkProfile(testHandle4, true); + registerDefaultNetworkCallbacks(); + + final TestNetworkCallback app4Cb = new TestNetworkCallback(); + final int testWorkProfileAppUid4 = + UserHandle.getUid(testHandle4.getIdentifier(), TEST_APP_ID); + registerDefaultNetworkCallbackAsUid(app4Cb, testWorkProfileAppUid4); + + // Connect both a regular cell agent and an enterprise network first. + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + + final TestNetworkAgentWrapper workAgent = makeEnterpriseNetworkAgent(); + workAgent.connect(true); + + mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + mProfileDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + app4Cb.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + inOrder.verify(mMockNetd).networkCreatePhysical(mCellNetworkAgent.getNetwork().netId, + INetd.PERMISSION_NONE); + inOrder.verify(mMockNetd).networkCreatePhysical(workAgent.getNetwork().netId, + INetd.PERMISSION_SYSTEM); + + final TestOnCompleteListener listener = new TestOnCompleteListener(); + mCm.setProfileNetworkPreference(testHandle2, PROFILE_NETWORK_PREFERENCE_ENTERPRISE, + r -> r.run(), listener); + listener.expectOnComplete(); + inOrder.verify(mMockNetd).networkAddUidRanges(workAgent.getNetwork().netId, + uidRangeFor(testHandle2)); + + mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(workAgent); + assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback, + app4Cb); + + mCm.setProfileNetworkPreference(testHandle4, PROFILE_NETWORK_PREFERENCE_ENTERPRISE, + r -> r.run(), listener); + listener.expectOnComplete(); + inOrder.verify(mMockNetd).networkAddUidRanges(workAgent.getNetwork().netId, + uidRangeFor(testHandle4)); + + app4Cb.expectAvailableCallbacksValidated(workAgent); + assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback, + mProfileDefaultNetworkCallback); + + mCm.setProfileNetworkPreference(testHandle2, PROFILE_NETWORK_PREFERENCE_DEFAULT, + r -> r.run(), listener); + listener.expectOnComplete(); + inOrder.verify(mMockNetd).networkRemoveUidRanges(workAgent.getNetwork().netId, + uidRangeFor(testHandle2)); + + mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); + assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback, + app4Cb); + + workAgent.disconnect(); + mCellNetworkAgent.disconnect(); + + mCm.unregisterNetworkCallback(app4Cb); + // Other callbacks will be unregistered by tearDown() + } + + @Test + public void testProfilePreferenceRemovedUponUserRemoved() throws Exception { + final InOrder inOrder = inOrder(mMockNetd); + final UserHandle testHandle = setupEnterpriseNetwork(); + + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + + final TestOnCompleteListener listener = new TestOnCompleteListener(); + mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_ENTERPRISE, + r -> r.run(), listener); + listener.expectOnComplete(); + inOrder.verify(mMockNetd).networkCreatePhysical(mCellNetworkAgent.getNetwork().netId, + INetd.PERMISSION_NONE); + inOrder.verify(mMockNetd).networkAddUidRanges(mCellNetworkAgent.getNetwork().netId, + uidRangeFor(testHandle)); + + final Intent removedIntent = new Intent(ACTION_USER_REMOVED); + removedIntent.putExtra(Intent.EXTRA_USER, testHandle); + processBroadcast(removedIntent); + + inOrder.verify(mMockNetd).networkRemoveUidRanges(mCellNetworkAgent.getNetwork().netId, + uidRangeFor(testHandle)); + } + + /** + * Make sure that OEM preference and per-profile preference can't be used at the same + * time and throw ISE if tried + */ + @Test + public void testOemPreferenceAndProfilePreferenceExclusive() throws Exception { + final UserHandle testHandle = UserHandle.of(TEST_WORK_PROFILE_USER_ID); + mServiceContext.setWorkProfile(testHandle, true); + final TestOnCompleteListener listener = new TestOnCompleteListener(); + + setupMultipleDefaultNetworksForOemNetworkPreferenceNotCurrentUidTest( + OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY); + assertThrows("Should not be able to set per-profile pref while OEM prefs present", + IllegalStateException.class, () -> + mCm.setProfileNetworkPreference(testHandle, + PROFILE_NETWORK_PREFERENCE_ENTERPRISE, + r -> r.run(), listener)); + + // Empty the OEM prefs + final TestOemListenerCallback oemPrefListener = new TestOemListenerCallback(); + final OemNetworkPreferences emptyOemPref = new OemNetworkPreferences.Builder().build(); + mService.setOemNetworkPreference(emptyOemPref, oemPrefListener); + oemPrefListener.expectOnComplete(); + + mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_ENTERPRISE, + r -> r.run(), listener); + listener.expectOnComplete(); + assertThrows("Should not be able to set OEM prefs while per-profile pref is on", + IllegalStateException.class , () -> + mService.setOemNetworkPreference(emptyOemPref, oemPrefListener)); + } + + /** + * Make sure wrong preferences for per-profile default networking are rejected. + */ + @Test + public void testProfileNetworkPrefWrongPreference() throws Exception { + final UserHandle testHandle = UserHandle.of(TEST_WORK_PROFILE_USER_ID); + mServiceContext.setWorkProfile(testHandle, true); + assertThrows("Should not be able to set an illegal preference", + IllegalArgumentException.class, + () -> mCm.setProfileNetworkPreference(testHandle, + PROFILE_NETWORK_PREFERENCE_ENTERPRISE + 1, null, null)); + } + + /** + * Make sure requests for per-profile default networking for a non-work profile are + * rejected + */ + @Test + public void testProfileNetworkPrefWrongProfile() throws Exception { + final UserHandle testHandle = UserHandle.of(TEST_WORK_PROFILE_USER_ID); + mServiceContext.setWorkProfile(testHandle, false); + assertThrows("Should not be able to set a user pref for a non-work profile", + IllegalArgumentException.class , () -> + mCm.setProfileNetworkPreference(testHandle, + PROFILE_NETWORK_PREFERENCE_ENTERPRISE, null, null)); + } } diff --git a/tests/net/java/com/android/server/LegacyTypeTrackerTest.kt b/tests/net/java/com/android/server/LegacyTypeTrackerTest.kt index a10a3c81bc86..5ec111954fcc 100644 --- a/tests/net/java/com/android/server/LegacyTypeTrackerTest.kt +++ b/tests/net/java/com/android/server/LegacyTypeTrackerTest.kt @@ -21,13 +21,29 @@ package com.android.server +import android.content.Context +import android.content.pm.PackageManager +import android.content.pm.PackageManager.FEATURE_WIFI +import android.content.pm.PackageManager.FEATURE_WIFI_DIRECT import android.net.ConnectivityManager.TYPE_ETHERNET import android.net.ConnectivityManager.TYPE_MOBILE +import android.net.ConnectivityManager.TYPE_MOBILE_CBS +import android.net.ConnectivityManager.TYPE_MOBILE_DUN +import android.net.ConnectivityManager.TYPE_MOBILE_EMERGENCY +import android.net.ConnectivityManager.TYPE_MOBILE_FOTA +import android.net.ConnectivityManager.TYPE_MOBILE_HIPRI +import android.net.ConnectivityManager.TYPE_MOBILE_IA +import android.net.ConnectivityManager.TYPE_MOBILE_IMS +import android.net.ConnectivityManager.TYPE_MOBILE_MMS import android.net.ConnectivityManager.TYPE_MOBILE_SUPL +import android.net.ConnectivityManager.TYPE_VPN import android.net.ConnectivityManager.TYPE_WIFI +import android.net.ConnectivityManager.TYPE_WIFI_P2P import android.net.ConnectivityManager.TYPE_WIMAX +import android.net.EthernetManager import android.net.NetworkInfo.DetailedState.CONNECTED import android.net.NetworkInfo.DetailedState.DISCONNECTED +import android.telephony.TelephonyManager import androidx.test.filters.SmallTest import androidx.test.runner.AndroidJUnit4 import com.android.server.ConnectivityService.LegacyTypeTracker @@ -36,7 +52,6 @@ import org.junit.Assert.assertFalse import org.junit.Assert.assertNull import org.junit.Assert.assertSame import org.junit.Assert.assertTrue -import org.junit.Assert.fail import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.any @@ -52,88 +67,130 @@ const val UNSUPPORTED_TYPE = TYPE_WIMAX @RunWith(AndroidJUnit4::class) @SmallTest class LegacyTypeTrackerTest { - private val supportedTypes = arrayOf(TYPE_MOBILE, TYPE_WIFI, TYPE_ETHERNET, TYPE_MOBILE_SUPL) + private val supportedTypes = arrayOf(TYPE_WIFI, TYPE_WIFI_P2P, TYPE_ETHERNET, TYPE_MOBILE, + TYPE_MOBILE_SUPL, TYPE_MOBILE_MMS, TYPE_MOBILE_SUPL, TYPE_MOBILE_DUN, TYPE_MOBILE_HIPRI, + TYPE_MOBILE_FOTA, TYPE_MOBILE_IMS, TYPE_MOBILE_CBS, TYPE_MOBILE_IA, + TYPE_MOBILE_EMERGENCY, TYPE_VPN) private val mMockService = mock(ConnectivityService::class.java).apply { doReturn(false).`when`(this).isDefaultNetwork(any()) } - private val mTracker = LegacyTypeTracker(mMockService).apply { - supportedTypes.forEach { - addSupportedType(it) - } + private val mPm = mock(PackageManager::class.java) + private val mContext = mock(Context::class.java).apply { + doReturn(true).`when`(mPm).hasSystemFeature(FEATURE_WIFI) + doReturn(true).`when`(mPm).hasSystemFeature(FEATURE_WIFI_DIRECT) + doReturn(mPm).`when`(this).packageManager + doReturn(mock(EthernetManager::class.java)).`when`(this).getSystemService( + Context.ETHERNET_SERVICE) + } + private val mTm = mock(TelephonyManager::class.java).apply { + doReturn(true).`when`(this).isDataCapable + } + + private fun makeTracker() = LegacyTypeTracker(mMockService).apply { + loadSupportedTypes(mContext, mTm) } @Test fun testSupportedTypes() { - try { - mTracker.addSupportedType(supportedTypes[0]) - fail("Expected IllegalStateException") - } catch (expected: IllegalStateException) {} + val tracker = makeTracker() supportedTypes.forEach { - assertTrue(mTracker.isTypeSupported(it)) + assertTrue(tracker.isTypeSupported(it)) + } + assertFalse(tracker.isTypeSupported(UNSUPPORTED_TYPE)) + } + + @Test + fun testSupportedTypes_NoEthernet() { + doReturn(null).`when`(mContext).getSystemService(Context.ETHERNET_SERVICE) + assertFalse(makeTracker().isTypeSupported(TYPE_ETHERNET)) + } + + @Test + fun testSupportedTypes_NoTelephony() { + doReturn(false).`when`(mTm).isDataCapable + val tracker = makeTracker() + val nonMobileTypes = arrayOf(TYPE_WIFI, TYPE_WIFI_P2P, TYPE_ETHERNET, TYPE_VPN) + nonMobileTypes.forEach { + assertTrue(tracker.isTypeSupported(it)) + } + supportedTypes.toSet().minus(nonMobileTypes).forEach { + assertFalse(tracker.isTypeSupported(it)) + } + } + + @Test + fun testSupportedTypes_NoWifiDirect() { + doReturn(false).`when`(mPm).hasSystemFeature(FEATURE_WIFI_DIRECT) + val tracker = makeTracker() + assertFalse(tracker.isTypeSupported(TYPE_WIFI_P2P)) + supportedTypes.toSet().minus(TYPE_WIFI_P2P).forEach { + assertTrue(tracker.isTypeSupported(it)) } - assertFalse(mTracker.isTypeSupported(UNSUPPORTED_TYPE)) } @Test fun testSupl() { + val tracker = makeTracker() val mobileNai = mock(NetworkAgentInfo::class.java) - mTracker.add(TYPE_MOBILE, mobileNai) + tracker.add(TYPE_MOBILE, mobileNai) verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, CONNECTED, TYPE_MOBILE) reset(mMockService) - mTracker.add(TYPE_MOBILE_SUPL, mobileNai) + tracker.add(TYPE_MOBILE_SUPL, mobileNai) verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, CONNECTED, TYPE_MOBILE_SUPL) reset(mMockService) - mTracker.remove(TYPE_MOBILE_SUPL, mobileNai, false /* wasDefault */) + tracker.remove(TYPE_MOBILE_SUPL, mobileNai, false /* wasDefault */) verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, DISCONNECTED, TYPE_MOBILE_SUPL) reset(mMockService) - mTracker.add(TYPE_MOBILE_SUPL, mobileNai) + tracker.add(TYPE_MOBILE_SUPL, mobileNai) verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, CONNECTED, TYPE_MOBILE_SUPL) reset(mMockService) - mTracker.remove(mobileNai, false) + tracker.remove(mobileNai, false) verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, DISCONNECTED, TYPE_MOBILE_SUPL) verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, DISCONNECTED, TYPE_MOBILE) } @Test fun testAddNetwork() { + val tracker = makeTracker() val mobileNai = mock(NetworkAgentInfo::class.java) val wifiNai = mock(NetworkAgentInfo::class.java) - mTracker.add(TYPE_MOBILE, mobileNai) - mTracker.add(TYPE_WIFI, wifiNai) - assertSame(mTracker.getNetworkForType(TYPE_MOBILE), mobileNai) - assertSame(mTracker.getNetworkForType(TYPE_WIFI), wifiNai) + tracker.add(TYPE_MOBILE, mobileNai) + tracker.add(TYPE_WIFI, wifiNai) + assertSame(tracker.getNetworkForType(TYPE_MOBILE), mobileNai) + assertSame(tracker.getNetworkForType(TYPE_WIFI), wifiNai) // Make sure adding a second NAI does not change the results. val secondMobileNai = mock(NetworkAgentInfo::class.java) - mTracker.add(TYPE_MOBILE, secondMobileNai) - assertSame(mTracker.getNetworkForType(TYPE_MOBILE), mobileNai) - assertSame(mTracker.getNetworkForType(TYPE_WIFI), wifiNai) + tracker.add(TYPE_MOBILE, secondMobileNai) + assertSame(tracker.getNetworkForType(TYPE_MOBILE), mobileNai) + assertSame(tracker.getNetworkForType(TYPE_WIFI), wifiNai) // Make sure removing a network that wasn't added for this type is a no-op. - mTracker.remove(TYPE_MOBILE, wifiNai, false /* wasDefault */) - assertSame(mTracker.getNetworkForType(TYPE_MOBILE), mobileNai) - assertSame(mTracker.getNetworkForType(TYPE_WIFI), wifiNai) + tracker.remove(TYPE_MOBILE, wifiNai, false /* wasDefault */) + assertSame(tracker.getNetworkForType(TYPE_MOBILE), mobileNai) + assertSame(tracker.getNetworkForType(TYPE_WIFI), wifiNai) // Remove the top network for mobile and make sure the second one becomes the network // of record for this type. - mTracker.remove(TYPE_MOBILE, mobileNai, false /* wasDefault */) - assertSame(mTracker.getNetworkForType(TYPE_MOBILE), secondMobileNai) - assertSame(mTracker.getNetworkForType(TYPE_WIFI), wifiNai) + tracker.remove(TYPE_MOBILE, mobileNai, false /* wasDefault */) + assertSame(tracker.getNetworkForType(TYPE_MOBILE), secondMobileNai) + assertSame(tracker.getNetworkForType(TYPE_WIFI), wifiNai) // Make sure adding a network for an unsupported type does not register it. - mTracker.add(UNSUPPORTED_TYPE, mobileNai) - assertNull(mTracker.getNetworkForType(UNSUPPORTED_TYPE)) + tracker.add(UNSUPPORTED_TYPE, mobileNai) + assertNull(tracker.getNetworkForType(UNSUPPORTED_TYPE)) } @Test fun testBroadcastOnDisconnect() { + val tracker = makeTracker() val mobileNai1 = mock(NetworkAgentInfo::class.java) val mobileNai2 = mock(NetworkAgentInfo::class.java) doReturn(false).`when`(mMockService).isDefaultNetwork(mobileNai1) - mTracker.add(TYPE_MOBILE, mobileNai1) + tracker.add(TYPE_MOBILE, mobileNai1) verify(mMockService).sendLegacyNetworkBroadcast(mobileNai1, CONNECTED, TYPE_MOBILE) reset(mMockService) doReturn(false).`when`(mMockService).isDefaultNetwork(mobileNai2) - mTracker.add(TYPE_MOBILE, mobileNai2) + tracker.add(TYPE_MOBILE, mobileNai2) verify(mMockService, never()).sendLegacyNetworkBroadcast(any(), any(), anyInt()) - mTracker.remove(TYPE_MOBILE, mobileNai1, false /* wasDefault */) + tracker.remove(TYPE_MOBILE, mobileNai1, false /* wasDefault */) verify(mMockService).sendLegacyNetworkBroadcast(mobileNai1, DISCONNECTED, TYPE_MOBILE) verify(mMockService).sendLegacyNetworkBroadcast(mobileNai2, CONNECTED, TYPE_MOBILE) } diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java index a02002752c38..814cad4ab448 100644 --- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java +++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java @@ -74,7 +74,7 @@ import android.telephony.TelephonyManager; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.internal.util.LocationPermissionChecker; +import com.android.net.module.util.LocationPermissionChecker; import com.android.server.VcnManagementService.VcnCallback; import com.android.server.VcnManagementService.VcnStatusCallbackInfo; import com.android.server.vcn.TelephonySubscriptionTracker; |